Skip to content

Commit f161051

Browse files
author
Kerwin
committed
feat: users manager
1 parent 7d15760 commit f161051

File tree

14 files changed

+281
-5
lines changed

14 files changed

+281
-5
lines changed

README.en.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
[] Set unique prompts for each chat room
2121

22+
[] Users manager
23+
2224
</br>
2325

2426
## Screenshots
@@ -29,6 +31,7 @@
2931
![cover2](./docs/c2.png)
3032
![cover3](./docs/basesettings.jpg)
3133
![cover3](./docs/prompt_en.jpg)
34+
![cover3](./docs/user-manager.jpg)
3235

3336
- [ChatGPT Web](#chatgpt-web)
3437
- [Introduction](#introduction)

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
[] 自定义敏感词
1919

2020
[] 每个会话设置独有 Prompt
21+
22+
[] 用户管理
2123
</br>
2224

2325
## 截图
@@ -28,6 +30,7 @@
2830
![cover2](./docs/c2.png)
2931
![cover3](./docs/basesettings.jpg)
3032
![cover3](./docs/prompt.jpg)
33+
![cover3](./docs/user-manager.jpg)
3134

3235
- [ChatGPT Web](#chatgpt-web)
3336
- [介绍](#介绍)

docs/user-manager.jpg

255 KB
Loading

service/src/index.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
getUser,
2525
getUserById,
2626
getUserStatisticsByDay,
27+
getUsers,
2728
insertChat,
2829
insertChatUsage,
2930
renameChatRoom,
@@ -34,6 +35,7 @@ import {
3435
updateUserChatModel,
3536
updateUserInfo,
3637
updateUserPassword,
38+
updateUserStatus,
3739
verifyUser,
3840
} from './storage/mongo'
3941
import { limiter } from './middleware/limiter'
@@ -647,7 +649,7 @@ router.post('/user-info', auth, async (req, res) => {
647649

648650
router.post('/user-chat-model', auth, async (req, res) => {
649651
try {
650-
const { chatModel } = req.body as { chatModel: CHATMODEL }
652+
const { chatModel } = req.query as { chatModel: CHATMODEL }
651653
const userId = req.headers.userId.toString()
652654

653655
const user = await getUserById(userId)
@@ -661,6 +663,29 @@ router.post('/user-chat-model', auth, async (req, res) => {
661663
}
662664
})
663665

666+
router.get('/users', rootAuth, async (req, res) => {
667+
try {
668+
const page = +req.query.page
669+
const size = +req.query.size
670+
const data = await getUsers(page, size)
671+
res.send({ status: 'Success', message: '获取成功 | Get successfully', data })
672+
}
673+
catch (error) {
674+
res.send({ status: 'Fail', message: error.message, data: null })
675+
}
676+
})
677+
678+
router.post('/user-status', rootAuth, async (req, res) => {
679+
try {
680+
const { userId, status } = req.body as { userId: string; status: Status }
681+
await updateUserStatus(userId, status)
682+
res.send({ status: 'Success', message: '更新成功 | Update successfully' })
683+
}
684+
catch (error) {
685+
res.send({ status: 'Fail', message: error.message, data: null })
686+
}
687+
})
688+
664689
router.post('/verify', async (req, res) => {
665690
try {
666691
const { token } = req.body as { token: string }

service/src/storage/model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export class UserInfo {
2222
description?: string
2323
updateTime?: string
2424
config?: UserConfig
25+
root?: boolean
2526
constructor(email: string, password: string) {
2627
this.name = email
2728
this.email = email
@@ -30,6 +31,7 @@ export class UserInfo {
3031
this.createTime = new Date().toLocaleString()
3132
this.verifyTime = null
3233
this.updateTime = new Date().toLocaleString()
34+
this.root = false
3335
}
3436
}
3537

service/src/storage/mongo.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,8 @@ export async function getChats(roomId: number, lastId?: number) {
131131
if (!lastId)
132132
lastId = new Date().getTime()
133133
const query = { roomId, uuid: { $lt: lastId }, status: { $ne: Status.Deleted } }
134-
const sort = { dateTime: -1 }
135134
const limit = 20
136-
const cursor = await chatCol.find(query).sort(sort).limit(limit)
135+
const cursor = await chatCol.find(query).sort({ dateTime: -1 }).limit(limit)
137136
const chats = []
138137
await cursor.forEach(doc => chats.push(doc))
139138
chats.reverse()
@@ -207,6 +206,21 @@ export async function getUser(email: string): Promise<UserInfo> {
207206
return await userCol.findOne({ email }) as UserInfo
208207
}
209208

209+
export async function getUsers(page: number, size: number): Promise<{ users: UserInfo[]; total: number }> {
210+
const cursor = userCol.find({}).sort({ createTime: -1 })
211+
const total = await cursor.count()
212+
const skip = (page - 1) * size
213+
const limit = size
214+
const pagedCursor = cursor.skip(skip).limit(limit)
215+
const users: UserInfo[] = []
216+
await pagedCursor.forEach(doc => users.push(doc))
217+
users.forEach((user) => {
218+
if (user.root == null)
219+
user.root = process.env.ROOT_USER === user.email.toLowerCase()
220+
})
221+
return { users, total }
222+
}
223+
210224
export async function getUserById(userId: string): Promise<UserInfo> {
211225
const userInfo = await userCol.findOne({ _id: new ObjectId(userId) }) as UserInfo
212226
if (userInfo.config == null) {
@@ -221,6 +235,10 @@ export async function verifyUser(email: string, status: Status) {
221235
return await userCol.updateOne({ email }, { $set: { status, verifyTime: new Date().toLocaleString() } })
222236
}
223237

238+
export async function updateUserStatus(userId: string, status: Status) {
239+
return await userCol.updateOne({ _id: new ObjectId(userId) }, { $set: { status, verifyTime: new Date().toLocaleString() } })
240+
}
241+
224242
export async function getConfig(): Promise<Config> {
225243
return await configCol.findOne() as Config
226244
}

src/api/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
22
import { get, post } from '@/utils/request'
3-
import type { AuditConfig, CHATMODEL, ConfigState, MailConfig, SiteConfig } from '@/components/common/Setting/model'
3+
import type { AuditConfig, CHATMODEL, ConfigState, MailConfig, SiteConfig, Status } from '@/components/common/Setting/model'
44
import { useAuthStore, useSettingStore } from '@/store'
55

66
export function fetchChatAPI<T = any>(
@@ -128,6 +128,20 @@ export function fetchUpdateUserChatModel<T = any>(chatModel: CHATMODEL) {
128128
})
129129
}
130130

131+
export function fetchGetUsers<T = any>(page: number, size: number) {
132+
return get<T>({
133+
url: '/users',
134+
data: { page, size },
135+
})
136+
}
137+
138+
export function fetchUpdateUserStatus<T = any>(userId: string, status: Status) {
139+
return post<T>({
140+
url: '/user-status',
141+
data: { userId, status },
142+
})
143+
}
144+
131145
export function fetchGetChatRooms<T = any>() {
132146
return get<T>({
133147
url: '/chatrooms',
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<script lang="ts" setup>
2+
import { h, onMounted, reactive, ref } from 'vue'
3+
import { NButton, NDataTable, NTag, useDialog, useMessage } from 'naive-ui'
4+
import { Status } from './model'
5+
import { fetchGetUsers, fetchUpdateUserStatus } from '@/api'
6+
import { t } from '@/locales'
7+
8+
const ms = useMessage()
9+
const dialog = useDialog()
10+
const loading = ref(false)
11+
12+
const users = ref([])
13+
const columns = [
14+
{
15+
title: 'Email',
16+
key: 'email',
17+
resizable: true,
18+
width: 200,
19+
minWidth: 100,
20+
maxWidth: 200,
21+
},
22+
{
23+
title: '注册时间',
24+
key: 'createTime',
25+
width: 220,
26+
},
27+
{
28+
title: '验证时间',
29+
key: 'verifyTime',
30+
width: 220,
31+
},
32+
{
33+
title: '管理员',
34+
key: 'status',
35+
width: 200,
36+
render(row: any) {
37+
if (row.root) {
38+
return h(
39+
NTag,
40+
{
41+
style: {
42+
marginRight: '6px',
43+
},
44+
type: 'info',
45+
bordered: false,
46+
},
47+
{
48+
default: () => 'Admin',
49+
},
50+
)
51+
}
52+
return '-'
53+
},
54+
},
55+
{
56+
title: '状态',
57+
key: 'status',
58+
width: 200,
59+
render(row: any) {
60+
return Status[row.status]
61+
},
62+
},
63+
{
64+
title: 'Action',
65+
key: '_id',
66+
width: 200,
67+
render(row: any) {
68+
const actions: any[] = []
69+
if (row.root)
70+
return actions
71+
72+
if (row.status === Status.Normal) {
73+
actions.push(h(
74+
NButton,
75+
{
76+
size: 'small',
77+
type: 'error',
78+
onClick: () => handleUpdateUserStatus(row._id, Status.Deleted),
79+
},
80+
{ default: () => t('chat.deleteUser') },
81+
))
82+
}
83+
if (row.status === Status.PreVerify || row.status === Status.AdminVerify) {
84+
actions.push(h(
85+
NButton,
86+
{
87+
size: 'small',
88+
type: 'info',
89+
onClick: () => handleUpdateUserStatus(row._id, Status.Normal),
90+
},
91+
{ default: () => t('chat.verifiedUser') },
92+
))
93+
}
94+
return actions
95+
},
96+
},
97+
]
98+
const pagination = reactive ({
99+
page: 1,
100+
pageSize: 25,
101+
pageCount: 1,
102+
itemCount: 1,
103+
prefix({ itemCount }: { itemCount: number | undefined }) {
104+
return `Total is ${itemCount}.`
105+
},
106+
showSizePicker: true,
107+
pageSizes: [25, 50, 100],
108+
onChange: (page: number) => {
109+
pagination.page = page
110+
handleGetUsers(pagination.page)
111+
},
112+
onUpdatePageSize: (pageSize: number) => {
113+
pagination.pageSize = pageSize
114+
pagination.page = 1
115+
handleGetUsers(pagination.page)
116+
},
117+
})
118+
119+
async function handleGetUsers(page: number) {
120+
if (loading.value)
121+
return
122+
users.value.length = 0
123+
loading.value = true
124+
const size = pagination.pageSize
125+
const data = (await fetchGetUsers(page, size)).data
126+
data.users.forEach((user: never) => {
127+
users.value.push(user)
128+
})
129+
pagination.page = page
130+
pagination.pageCount = data.total / size + (data.total % size === 0 ? 0 : 1)
131+
pagination.itemCount = data.total
132+
loading.value = false
133+
}
134+
135+
async function handleUpdateUserStatus(userId: string, status: Status) {
136+
if (status === Status.Deleted) {
137+
dialog.warning({
138+
title: t('chat.deleteUser'),
139+
content: t('chat.deleteUserConfirm'),
140+
positiveText: t('common.yes'),
141+
negativeText: t('common.no'),
142+
onPositiveClick: async () => {
143+
await fetchUpdateUserStatus(userId, status)
144+
ms.info('OK')
145+
await handleGetUsers(pagination.page)
146+
},
147+
})
148+
}
149+
else {
150+
await fetchUpdateUserStatus(userId, status)
151+
ms.info('OK')
152+
await handleGetUsers(pagination.page)
153+
}
154+
}
155+
156+
onMounted(async () => {
157+
await handleGetUsers(pagination.page)
158+
})
159+
</script>
160+
161+
<template>
162+
<div class="p-4 space-y-5 min-h-[200px]">
163+
<div class="space-y-6">
164+
<NDataTable
165+
ref="table"
166+
remote
167+
:loading="loading"
168+
:row-key="(rowData) => rowData._id"
169+
:columns="columns"
170+
:data="users"
171+
:pagination="pagination"
172+
:max-height="444"
173+
striped
174+
@update:page="handleGetUsers"
175+
/>
176+
</div>
177+
</div>
178+
</template>

src/components/common/Setting/index.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import About from './About.vue'
88
import Site from './Site.vue'
99
import Mail from './Mail.vue'
1010
import Audit from './Audit.vue'
11+
import User from './User.vue'
1112
import { SvgIcon } from '@/components/common'
1213
import { useAuthStore, useUserStore } from '@/store'
1314
@@ -41,7 +42,7 @@ const show = computed({
4142
</script>
4243

4344
<template>
44-
<NModal v-model:show="show" :auto-focus="false" preset="card" style="width: 95%; max-width: 1024px">
45+
<NModal v-model:show="show" :auto-focus="false" preset="card" style="width: 80%;">
4546
<div>
4647
<NTabs v-model:value="active" type="line" animated>
4748
<NTabPane name="General" tab="General">
@@ -99,6 +100,13 @@ const show = computed({
99100
</template>
100101
<Audit />
101102
</NTabPane>
103+
<NTabPane name="UserConfig" tab="UserConfig">
104+
<template #tab>
105+
<SvgIcon class="text-lg" icon="ri-user-5-line" />
106+
<span class="ml-2">{{ $t('setting.userConfig') }}</span>
107+
</template>
108+
<User />
109+
</NTabPane>
102110
</NTabs>
103111
</div>
104112
</NModal>

0 commit comments

Comments
 (0)