diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28ea39dc..f6499ee3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1639,8 +1639,8 @@ importers: specifier: workspace:^ version: link:../packages/sdk tushan: - specifier: ^0.2.12 - version: 0.2.12(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.41.5)(ts-node@10.9.1) + specifier: ^0.2.17 + version: 0.2.17(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.41.5)(ts-node@10.9.1) vite-express: specifier: 0.8.0 version: 0.8.0(patch_hash=u6touqej4dt3zxnslnszarl7vq)(express@4.18.2)(vite@4.2.0) @@ -2397,8 +2397,8 @@ packages: color: 3.2.1 dev: false - /@arco-design/web-react@2.47.1(@types/react@18.0.20)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-fVjW59iN3aP48yiJchqGOnCh8bEmoOtoC40S/gdfzmwK7Ef3vh5GPRUydscLhReePck2vi6JLyV0aXEZRKkorA==} + /@arco-design/web-react@2.49.0(@types/react@18.0.20)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-g+B8d8am2inpMd3zGoWZ8TNDBDFZO2AiTzZurN+2q69bzpbsbiX41BG8ujV4GQPJSAt/GVGini0Hv8+LzQdmtw==} peerDependencies: react: '>=16' react-dom: '>=16' @@ -34528,10 +34528,10 @@ packages: domino: 2.1.6 dev: false - /tushan@0.2.12(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.41.5)(ts-node@10.9.1): - resolution: {integrity: sha512-hQwlHTWoOx45J46+VSK71BbVRnI4+MzO0buEIOFX3dd+r+oSqua/kTTfhN3YycpZO26ReH691WcnAJmM6jEi2g==} + /tushan@0.2.17(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.41.5)(ts-node@10.9.1): + resolution: {integrity: sha512-tGWSG3qUKN0n7V5vevVsVWTZCUFh+DfVmDExSNgzRzRVTks3isNPXzGlTTz24WDV6Rvuj3uWG8fGaQzg8lcOgQ==} dependencies: - '@arco-design/web-react': 2.47.1(@types/react@18.0.20)(react-dom@18.2.0)(react@18.2.0) + '@arco-design/web-react': 2.49.0(@types/react@18.0.20)(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query': 4.29.3(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query-devtools': 4.29.3(@tanstack/react-query@4.29.3)(react-dom@18.2.0)(react@18.2.0) '@types/node': 18.16.1 @@ -34539,6 +34539,7 @@ packages: '@types/react-dom': 18.0.11 axios: 0.27.2 clsx: 1.2.1 + eventemitter-strict: 1.0.1 i18next: 22.5.0 i18next-browser-languagedetector: 7.0.1 immer: 9.0.21 diff --git a/server/admin-next/package.json b/server/admin-next/package.json index 49dd251c..c9329939 100644 --- a/server/admin-next/package.json +++ b/server/admin-next/package.json @@ -25,7 +25,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "tailchat-server-sdk": "workspace:^", - "tushan": "^0.2.16", + "tushan": "^0.2.17", "vite-express": "0.8.0" }, "devDependencies": { diff --git a/server/admin-next/src/client/i18n.ts b/server/admin-next/src/client/i18n.ts index f2a6b017..205c1ca0 100644 --- a/server/admin-next/src/client/i18n.ts +++ b/server/admin-next/src/client/i18n.ts @@ -15,6 +15,8 @@ export const i18n: TushanContextProps['i18n'] = { banUser: 'Ban User', banUserDesc: 'Banning a user disconnects the user from the current connection and prevents future logins', + unbanUser: 'Unban User', + unbanUserDesc: 'After lifting the ban, the user can login normally', }, dashboard: { file: 'File', @@ -153,6 +155,8 @@ export const i18n: TushanContextProps['i18n'] = { resetPassword: '重置密码', banUser: '封禁用户', banUserDesc: '封禁用户会将用户从当前连接断开并阻止之后的登录操作', + unbanUser: '解除封禁用户', + unbanUserDesc: '解除封禁后用户可以正常登录', }, dashboard: { file: '文件', diff --git a/server/admin-next/src/client/resources/user.tsx b/server/admin-next/src/client/resources/user.tsx index 4d402224..d3a99200 100644 --- a/server/admin-next/src/client/resources/user.tsx +++ b/server/admin-next/src/client/resources/user.tsx @@ -33,11 +33,11 @@ export const UserList: React.FC = React.memo(() => { delete: true, refresh: true, export: true, - custom: [ + custom: (record) => [ { key: 'resetPassword', label: t('custom.action.resetPassword'), - onClick: (record) => { + onClick: () => { const { close } = Modal.confirm({ title: t('tushan.common.confirmTitle'), content: t('tushan.common.confirmContent'), @@ -60,30 +60,53 @@ export const UserList: React.FC = React.memo(() => { }); }, }, - { - key: 'banUser', - label: t('custom.action.banUser'), - onClick: (record) => { - const { close } = Modal.confirm({ - title: t('tushan.common.confirmTitle'), - content: t('custom.action.banUserDesc'), - onConfirm: async () => { - try { - await request.post('/user/ban', { - userId: record.id, - }); - Message.success(t('tushan.common.success')); - refreshUser(); - close(); - } catch (err) { - console.error(err); - Message.error(String(err)); - } + !record.banned + ? { + key: 'banUser', + label: t('custom.action.banUser'), + onClick: () => { + const { close } = Modal.confirm({ + title: t('tushan.common.confirmTitle'), + content: t('custom.action.banUserDesc'), + onConfirm: async () => { + try { + await request.post('/user/ban', { + userId: record.id, + }); + Message.success(t('tushan.common.success')); + refreshUser(); + close(); + } catch (err) { + console.error(err); + Message.error(String(err)); + } + }, + }); }, - }); - }, - }, - // TODO: unban + } + : { + key: 'unbanUser', + label: t('custom.action.unbanUser'), + onClick: () => { + const { close } = Modal.confirm({ + title: t('tushan.common.confirmTitle'), + content: t('custom.action.unbanUserDesc'), + onConfirm: async () => { + try { + await request.post('/user/unban', { + userId: record.id, + }); + Message.success(t('tushan.common.success')); + refreshUser(); + close(); + } catch (err) { + console.error(err); + Message.error(String(err)); + } + }, + }); + }, + }, ], }} /> diff --git a/server/admin-next/src/server/router/api.ts b/server/admin-next/src/server/router/api.ts index e147eed9..c276cd62 100644 --- a/server/admin-next/src/server/router/api.ts +++ b/server/admin-next/src/server/router/api.ts @@ -110,6 +110,17 @@ router.post('/user/ban', auth(), async (req, res) => { ret, }); }); +router.post('/user/unban', auth(), async (req, res) => { + const { userId } = req.body; + + const ret = await broker.call('user.unbanUser', { + userId, + }); + + res.json({ + ret, + }); +}); router.use( '/users', auth(), diff --git a/server/services/core/user/user.service.ts b/server/services/core/user/user.service.ts index 467039bf..534117a9 100644 --- a/server/services/core/user/user.service.ts +++ b/server/services/core/user/user.service.ts @@ -151,6 +151,12 @@ class UserService extends TcService { }, visibility: 'public', }); + this.registerAction('unbanUser', this.unbanUser, { + params: { + userId: 'string', + }, + visibility: 'public', + }); this.registerAction('whoami', this.whoami); this.registerAction( 'searchUserWithUniqueName', @@ -707,6 +713,33 @@ class UserService extends TcService { }); } + /** + * 解除封禁用户 + */ + async unbanUser( + ctx: TcContext<{ + userId: string; + }> + ) { + const { userId } = ctx.params; + await this.adapter.model.updateOne( + { + _id: userId, + }, + { + banned: false, + } + ); + + this.cleanUserInfoCache(userId); + const tokens = await ctx.call('gateway.getUserSocketToken', { + userId, + }); + if (Array.isArray(tokens)) { + tokens.map((token) => this.cleanActionCache('resolveToken', [token])); + } + } + async whoami(ctx: TcContext) { return ctx.meta ?? null; }