Spaces:
Runtime error
Runtime error
import express from 'express' | |
import jwt from 'jsonwebtoken' | |
import * as dotenv from 'dotenv' | |
import { ObjectId } from 'mongodb' | |
import type { RequestProps } from './types' | |
import type { ChatContext, ChatMessage } from './chatgpt' | |
import { abortChatProcess, chatConfig, chatReplyProcess, containsSensitiveWords, initAuditService } from './chatgpt' | |
import { auth, getUserId } from './middleware/auth' | |
import { clearApiKeyCache, clearConfigCache, getApiKeys, getCacheApiKeys, getCacheConfig, getOriginConfig } from './storage/config' | |
import type { AuditConfig, CHATMODEL, ChatInfo, ChatOptions, Config, KeyConfig, MailConfig, SiteConfig, UsageResponse, UserInfo } from './storage/model' | |
import { Status, UserRole, chatModelOptions } from './storage/model' | |
import { | |
clearChat, | |
createChatRoom, | |
createUser, | |
deleteAllChatRooms, | |
deleteChat, | |
deleteChatRoom, | |
existsChatRoom, | |
getChat, | |
getChatRoom, | |
getChatRooms, | |
getChats, | |
getUser, | |
getUserById, | |
getUserStatisticsByDay, | |
getUsers, | |
insertChat, | |
insertChatUsage, | |
renameChatRoom, | |
updateApiKeyStatus, | |
updateChat, | |
updateConfig, | |
updateRoomPrompt, | |
updateRoomUsingContext, | |
updateUserChatModel, | |
updateUserInfo, | |
updateUserPassword, | |
updateUserRole, | |
updateUserStatus, | |
upsertKey, | |
verifyUser, | |
} from './storage/mongo' | |
import { authLimiter, limiter } from './middleware/limiter' | |
import { hasAnyRole, isEmail, isNotEmptyString } from './utils/is' | |
import { sendNoticeMail, sendResetPasswordMail, sendTestMail, sendVerifyMail, sendVerifyMailAdmin } from './utils/mail' | |
import { checkUserResetPassword, checkUserVerify, checkUserVerifyAdmin, getUserResetPasswordUrl, getUserVerifyUrl, getUserVerifyUrlAdmin, md5 } from './utils/security' | |
import { rootAuth } from './middleware/rootAuth' | |
dotenv.config() | |
const app = express() | |
const router = express.Router() | |
app.use(express.static('public')) | |
app.use(express.json()) | |
app.all('*', (_, res, next) => { | |
res.header('Access-Control-Allow-Origin', '*') | |
res.header('Access-Control-Allow-Headers', 'authorization, Content-Type') | |
res.header('Access-Control-Allow-Methods', '*') | |
next() | |
}) | |
router.get('/chatrooms', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const rooms = await getChatRooms(userId) | |
const result = [] | |
rooms.forEach((r) => { | |
result.push({ | |
uuid: r.roomId, | |
title: r.title, | |
isEdit: false, | |
prompt: r.prompt, | |
usingContext: r.usingContext === undefined ? true : r.usingContext, | |
}) | |
}) | |
res.send({ status: 'Success', message: null, data: result }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Load error', data: [] }) | |
} | |
}) | |
router.post('/room-create', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const { title, roomId } = req.body as { title: string; roomId: number } | |
const room = await createChatRoom(userId, title, roomId) | |
res.send({ status: 'Success', message: null, data: room }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Create error', data: null }) | |
} | |
}) | |
router.post('/room-rename', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const { title, roomId } = req.body as { title: string; roomId: number } | |
const room = await renameChatRoom(userId, title, roomId) | |
res.send({ status: 'Success', message: null, data: room }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Rename error', data: null }) | |
} | |
}) | |
router.post('/room-prompt', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const { prompt, roomId } = req.body as { prompt: string; roomId: number } | |
const success = await updateRoomPrompt(userId, roomId, prompt) | |
if (success) | |
res.send({ status: 'Success', message: 'Saved successfully', data: null }) | |
else | |
res.send({ status: 'Fail', message: 'Saved Failed', data: null }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Rename error', data: null }) | |
} | |
}) | |
router.post('/room-context', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const { using, roomId } = req.body as { using: boolean; roomId: number } | |
const success = await updateRoomUsingContext(userId, roomId, using) | |
if (success) | |
res.send({ status: 'Success', message: 'Saved successfully', data: null }) | |
else | |
res.send({ status: 'Fail', message: 'Saved Failed', data: null }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Rename error', data: null }) | |
} | |
}) | |
router.post('/room-delete', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const { roomId } = req.body as { roomId: number } | |
if (!roomId || !await existsChatRoom(userId, roomId)) { | |
res.send({ status: 'Fail', message: 'Unknow room', data: null }) | |
return | |
} | |
await deleteChatRoom(userId, roomId) | |
res.send({ status: 'Success', message: null }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Delete error', data: null }) | |
} | |
}) | |
router.get('/chat-history', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const roomId = +req.query.roomId | |
const lastId = req.query.lastId as string | |
if (!roomId || !await existsChatRoom(userId, roomId)) { | |
res.send({ status: 'Success', message: null, data: [] }) | |
// res.send({ status: 'Fail', message: 'Unknow room', data: null }) | |
return | |
} | |
const chats = await getChats(roomId, !isNotEmptyString(lastId) ? null : parseInt(lastId)) | |
const result = [] | |
chats.forEach((c) => { | |
if (c.status !== Status.InversionDeleted) { | |
result.push({ | |
uuid: c.uuid, | |
dateTime: new Date(c.dateTime).toLocaleString(), | |
text: c.prompt, | |
inversion: true, | |
error: false, | |
conversationOptions: null, | |
requestOptions: { | |
prompt: c.prompt, | |
options: null, | |
}, | |
}) | |
} | |
if (c.status !== Status.ResponseDeleted) { | |
const usage = c.options.completion_tokens | |
? { | |
completion_tokens: c.options.completion_tokens || null, | |
prompt_tokens: c.options.prompt_tokens || null, | |
total_tokens: c.options.total_tokens || null, | |
estimated: c.options.estimated || null, | |
} | |
: undefined | |
result.push({ | |
uuid: c.uuid, | |
dateTime: new Date(c.dateTime).toLocaleString(), | |
text: c.response, | |
inversion: false, | |
error: false, | |
loading: false, | |
responseCount: (c.previousResponse?.length ?? 0) + 1, | |
conversationOptions: { | |
parentMessageId: c.options.messageId, | |
conversationId: c.options.conversationId, | |
}, | |
requestOptions: { | |
prompt: c.prompt, | |
parentMessageId: c.options.parentMessageId, | |
options: { | |
parentMessageId: c.options.messageId, | |
conversationId: c.options.conversationId, | |
}, | |
}, | |
usage, | |
}) | |
} | |
}) | |
res.send({ status: 'Success', message: null, data: result }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Load error', data: null }) | |
} | |
}) | |
router.get('/chat-response-history', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const roomId = +req.query.roomId | |
const uuid = +req.query.uuid | |
const index = +req.query.index | |
if (!roomId || !await existsChatRoom(userId, roomId)) { | |
res.send({ status: 'Success', message: null, data: [] }) | |
// res.send({ status: 'Fail', message: 'Unknow room', data: null }) | |
return | |
} | |
const chat = await getChat(roomId, uuid) | |
if (chat.previousResponse === undefined || chat.previousResponse.length < index) { | |
res.send({ status: 'Fail', message: 'Error', data: [] }) | |
return | |
} | |
const response = index >= chat.previousResponse.length | |
? chat | |
: chat.previousResponse[index] | |
const usage = response.options.completion_tokens | |
? { | |
completion_tokens: response.options.completion_tokens || null, | |
prompt_tokens: response.options.prompt_tokens || null, | |
total_tokens: response.options.total_tokens || null, | |
estimated: response.options.estimated || null, | |
} | |
: undefined | |
res.send({ | |
status: 'Success', | |
message: null, | |
data: { | |
uuid: chat.uuid, | |
dateTime: new Date(chat.dateTime).toLocaleString(), | |
text: response.response, | |
inversion: false, | |
error: false, | |
loading: false, | |
responseCount: (chat.previousResponse?.length ?? 0) + 1, | |
conversationOptions: { | |
parentMessageId: response.options.messageId, | |
conversationId: response.options.conversationId, | |
}, | |
requestOptions: { | |
prompt: chat.prompt, | |
parentMessageId: response.options.parentMessageId, | |
options: { | |
parentMessageId: response.options.messageId, | |
conversationId: response.options.conversationId, | |
}, | |
}, | |
usage, | |
}, | |
}) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Load error', data: null }) | |
} | |
}) | |
router.post('/chat-delete', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const { roomId, uuid, inversion } = req.body as { roomId: number; uuid: number; inversion: boolean } | |
if (!roomId || !await existsChatRoom(userId, roomId)) { | |
res.send({ status: 'Fail', message: 'Unknow room', data: null }) | |
return | |
} | |
await deleteChat(roomId, uuid, inversion) | |
res.send({ status: 'Success', message: null, data: null }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Delete error', data: null }) | |
} | |
}) | |
router.post('/chat-clear-all', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
await deleteAllChatRooms(userId) | |
res.send({ status: 'Success', message: null, data: null }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Delete error', data: null }) | |
} | |
}) | |
router.post('/chat-clear', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId as string | |
const { roomId } = req.body as { roomId: number } | |
if (!roomId || !await existsChatRoom(userId, roomId)) { | |
res.send({ status: 'Fail', message: 'Unknow room', data: null }) | |
return | |
} | |
await clearChat(roomId) | |
res.send({ status: 'Success', message: null, data: null }) | |
} | |
catch (error) { | |
console.error(error) | |
res.send({ status: 'Fail', message: 'Delete error', data: null }) | |
} | |
}) | |
router.post('/chat', auth, async (req, res) => { | |
try { | |
const { roomId, uuid, regenerate, prompt, options = {} } = req.body as | |
{ roomId: number; uuid: number; regenerate: boolean; prompt: string; options?: ChatContext } | |
const message = regenerate | |
? await getChat(roomId, uuid) | |
: await insertChat(uuid, prompt, roomId, options as ChatOptions) | |
const response = await chatReply(prompt, options) | |
if (response.status === 'Success') { | |
if (regenerate && message.options.messageId) { | |
const previousResponse = message.previousResponse || [] | |
previousResponse.push({ response: message.response, options: message.options }) | |
await updateChat(message._id as unknown as string, | |
response.data.text, | |
response.data.id, | |
response.data.detail?.usage as UsageResponse, | |
previousResponse as []) | |
} | |
else { | |
await updateChat(message._id as unknown as string, | |
response.data.text, | |
response.data.id, | |
response.data.detail?.usage as UsageResponse) | |
} | |
if (response.data.usage) { | |
await insertChatUsage(new ObjectId(req.headers.userId as string), | |
roomId, | |
message._id, | |
response.data.id, | |
response.data.detail?.usage as UsageResponse) | |
} | |
} | |
res.send(response) | |
} | |
catch (error) { | |
res.send(error) | |
} | |
}) | |
router.post('/chat-process', [auth, limiter], async (req, res) => { | |
res.setHeader('Content-type', 'application/octet-stream') | |
let { roomId, uuid, regenerate, prompt, options = {}, systemMessage, temperature, top_p } = req.body as RequestProps | |
const userId = req.headers.userId as string | |
const room = await getChatRoom(userId, roomId) | |
if (room == null) | |
global.console.error(`Unable to get chat room \t ${userId}\t ${roomId}`) | |
if (room != null && isNotEmptyString(room.prompt)) | |
systemMessage = room.prompt | |
let lastResponse | |
let result | |
let message: ChatInfo | |
try { | |
const config = await getCacheConfig() | |
const userId = req.headers.userId.toString() | |
const user = await getUserById(userId) | |
if (config.auditConfig.enabled || config.auditConfig.customizeEnabled) { | |
if (!user.roles.includes(UserRole.Admin) && await containsSensitiveWords(config.auditConfig, prompt)) { | |
res.send({ status: 'Fail', message: '含有敏感词 | Contains sensitive words', data: null }) | |
return | |
} | |
} | |
message = regenerate | |
? await getChat(roomId, uuid) | |
: await insertChat(uuid, prompt, roomId, options as ChatOptions) | |
let firstChunk = true | |
result = await chatReplyProcess({ | |
message: prompt, | |
lastContext: options, | |
process: (chat: ChatMessage) => { | |
lastResponse = chat | |
const chuck = { | |
id: chat.id, | |
conversationId: chat.conversationId, | |
text: chat.text, | |
detail: { | |
choices: [ | |
{ | |
finish_reason: undefined, | |
}, | |
], | |
}, | |
} | |
if (chat.detail && chat.detail.choices.length > 0) | |
chuck.detail.choices[0].finish_reason = chat.detail.choices[0].finish_reason | |
res.write(firstChunk ? JSON.stringify(chuck) : `\n${JSON.stringify(chuck)}`) | |
firstChunk = false | |
}, | |
systemMessage, | |
temperature, | |
top_p, | |
user, | |
messageId: message._id.toString(), | |
tryCount: 0, | |
room, | |
}) | |
// return the whole response including usage | |
res.write(`\n${JSON.stringify(result.data)}`) | |
} | |
catch (error) { | |
res.write(JSON.stringify({ message: error?.message })) | |
} | |
finally { | |
res.end() | |
try { | |
if (result == null || result === undefined || result.status !== 'Success') { | |
if (result && result.status !== 'Success') | |
lastResponse = { text: result.message } | |
result = { data: lastResponse } | |
} | |
if (result.data === undefined) | |
// eslint-disable-next-line no-unsafe-finally | |
return | |
if (regenerate && message.options.messageId) { | |
const previousResponse = message.previousResponse || [] | |
previousResponse.push({ response: message.response, options: message.options }) | |
await updateChat(message._id as unknown as string, | |
result.data.text, | |
result.data.id, | |
result.data.conversationId, | |
result.data.detail?.usage as UsageResponse, | |
previousResponse as []) | |
} | |
else { | |
await updateChat(message._id as unknown as string, | |
result.data.text, | |
result.data.id, | |
result.data.conversationId, | |
result.data.detail?.usage as UsageResponse) | |
} | |
if (result.data.detail?.usage) { | |
await insertChatUsage(new ObjectId(req.headers.userId), | |
roomId, | |
message._id, | |
result.data.id, | |
result.data.detail?.usage as UsageResponse) | |
} | |
} | |
catch (error) { | |
global.console.error(error) | |
} | |
} | |
}) | |
router.post('/chat-abort', [auth, limiter], async (req, res) => { | |
try { | |
const userId = req.headers.userId.toString() | |
const { text, messageId, conversationId } = req.body as { text: string; messageId: string; conversationId: string } | |
const msgId = await abortChatProcess(userId) | |
await updateChat(msgId, | |
text, | |
messageId, | |
conversationId, | |
null) | |
res.send({ status: 'Success', message: 'OK', data: null }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: '重置邮件已发送 | Reset email has been sent', data: null }) | |
} | |
}) | |
router.post('/user-register', authLimiter, async (req, res) => { | |
try { | |
const { username, password } = req.body as { username: string; password: string } | |
const config = await getCacheConfig() | |
if (!config.siteConfig.registerEnabled) { | |
res.send({ status: 'Fail', message: '注册账号功能未启用 | Register account is disabled!', data: null }) | |
return | |
} | |
if (!isEmail(username)) { | |
res.send({ status: 'Fail', message: '请输入正确的邮箱 | Please enter a valid email address.', data: null }) | |
return | |
} | |
if (isNotEmptyString(config.siteConfig.registerMails)) { | |
let allowSuffix = false | |
const emailSuffixs = config.siteConfig.registerMails.split(',') | |
for (let index = 0; index < emailSuffixs.length; index++) { | |
const element = emailSuffixs[index] | |
allowSuffix = username.toLowerCase().endsWith(element) | |
if (allowSuffix) | |
break | |
} | |
if (!allowSuffix) { | |
res.send({ status: 'Fail', message: '该邮箱后缀不支持 | The email service provider is not allowed', data: null }) | |
return | |
} | |
} | |
const user = await getUser(username) | |
if (user != null) { | |
if (user.status === Status.PreVerify) { | |
await sendVerifyMail(username, await getUserVerifyUrl(username)) | |
throw new Error('请去邮箱中验证 | Please verify in the mailbox') | |
} | |
if (user.status === Status.AdminVerify) | |
throw new Error('请等待管理员开通 | Please wait for the admin to activate') | |
res.send({ status: 'Fail', message: '账号已存在 | The email exists', data: null }) | |
return | |
} | |
const newPassword = md5(password) | |
const isRoot = username.toLowerCase() === process.env.ROOT_USER | |
await createUser(username, newPassword, isRoot) | |
if (isRoot) { | |
res.send({ status: 'Success', message: '注册成功 | Register success', data: null }) | |
} | |
else { | |
await sendVerifyMail(username, await getUserVerifyUrl(username)) | |
res.send({ status: 'Success', message: '注册成功, 去邮箱中验证吧 | Registration is successful, you need to go to email verification', data: null }) | |
} | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/config', rootAuth, async (req, res) => { | |
try { | |
const userId = req.headers.userId.toString() | |
const user = await getUserById(userId) | |
if (user == null || user.status !== Status.Normal || !user.roles.includes(UserRole.Admin)) | |
throw new Error('无权限 | No permission.') | |
const response = await chatConfig() | |
res.send(response) | |
} | |
catch (error) { | |
res.send(error) | |
} | |
}) | |
router.post('/session', async (req, res) => { | |
try { | |
const config = await getCacheConfig() | |
const hasAuth = config.siteConfig.loginEnabled | |
const allowRegister = (await getCacheConfig()).siteConfig.registerEnabled | |
if (config.apiModel !== 'ChatGPTAPI' && config.apiModel !== 'ChatGPTUnofficialProxyAPI') | |
config.apiModel = 'ChatGPTAPI' | |
const userId = await getUserId(req) | |
const chatModels: { | |
label | |
key: string | |
value: string | |
}[] = [] | |
if (userId != null) { | |
const user = await getUserById(userId) | |
const keys = (await getCacheApiKeys()).filter(d => hasAnyRole(d.userRoles, user.roles)) | |
const count: { key: string; count: number }[] = [] | |
chatModelOptions.forEach((chatModel) => { | |
keys.forEach((key) => { | |
if (key.chatModels.includes(chatModel.value)) { | |
if (count.filter(d => d.key === chatModel.value).length <= 0) { | |
count.push({ key: chatModel.value, count: 1 }) | |
} | |
else { | |
const thisCount = count.filter(d => d.key === chatModel.value)[0] | |
thisCount.count++ | |
} | |
} | |
}) | |
}) | |
count.forEach((c) => { | |
const thisChatModel = chatModelOptions.filter(d => d.value === c.key)[0] | |
const suffix = c.count > 1 ? ` (${c.count})` : '' | |
chatModels.push({ | |
label: `${thisChatModel.label}${suffix}`, | |
key: c.key, | |
value: c.key, | |
}) | |
}) | |
} | |
res.send({ | |
status: 'Success', | |
message: '', | |
data: { | |
auth: hasAuth, | |
allowRegister, | |
model: config.apiModel, | |
title: config.siteConfig.siteTitle, | |
chatModels, | |
allChatModels: chatModelOptions, | |
}, | |
}) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/user-login', authLimiter, async (req, res) => { | |
try { | |
const { username, password } = req.body as { username: string; password: string } | |
if (!username || !password || !isEmail(username)) | |
throw new Error('用户名或密码为空 | Username or password is empty') | |
const user = await getUser(username) | |
if (user == null || user.password !== md5(password)) | |
throw new Error('用户不存在或密码错误 | User does not exist or incorrect password.') | |
if (user.status === Status.PreVerify) | |
throw new Error('请去邮箱中验证 | Please verify in the mailbox') | |
if (user != null && user.status === Status.AdminVerify) | |
throw new Error('请等待管理员开通 | Please wait for the admin to activate') | |
if (user.status !== Status.Normal) | |
throw new Error('账户状态异常 | Account status abnormal.') | |
const config = await getCacheConfig() | |
const token = jwt.sign({ | |
name: user.name ? user.name : user.email, | |
avatar: user.avatar, | |
description: user.description, | |
userId: user._id, | |
root: user.roles.includes(UserRole.Admin), | |
config: user.config, | |
}, config.siteConfig.loginSalt.trim()) | |
res.send({ status: 'Success', message: '登录成功 | Login successfully', data: { token } }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/user-send-reset-mail', authLimiter, async (req, res) => { | |
try { | |
const { username } = req.body as { username: string } | |
if (!username || !isEmail(username)) | |
throw new Error('请输入格式正确的邮箱 | Please enter a correctly formatted email address.') | |
const user = await getUser(username) | |
if (user == null || user.status !== Status.Normal) | |
throw new Error('账户状态异常 | Account status abnormal.') | |
await sendResetPasswordMail(username, await getUserResetPasswordUrl(username)) | |
res.send({ status: 'Success', message: '重置邮件已发送 | Reset email has been sent', data: null }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/user-reset-password', authLimiter, async (req, res) => { | |
try { | |
const { username, password, sign } = req.body as { username: string; password: string; sign: string } | |
if (!username || !password || !isEmail(username)) | |
throw new Error('用户名或密码为空 | Username or password is empty') | |
if (!sign || !checkUserResetPassword(sign, username)) | |
throw new Error('链接失效, 请重新发送 | The link is invalid, please resend.') | |
const user = await getUser(username) | |
if (user == null || user.status !== Status.Normal) | |
throw new Error('账户状态异常 | Account status abnormal.') | |
updateUserPassword(user._id.toString(), md5(password)) | |
res.send({ status: 'Success', message: '密码重置成功 | Password reset successful', data: null }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/user-info', auth, async (req, res) => { | |
try { | |
const { name, avatar, description } = req.body as UserInfo | |
const userId = req.headers.userId.toString() | |
const user = await getUserById(userId) | |
if (user == null || user.status !== Status.Normal) | |
throw new Error('用户不存在 | User does not exist.') | |
await updateUserInfo(userId, { name, avatar, description } as UserInfo) | |
res.send({ status: 'Success', message: '更新成功 | Update successfully' }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/user-chat-model', auth, async (req, res) => { | |
try { | |
const { chatModel } = req.body as { chatModel: CHATMODEL } | |
const userId = req.headers.userId.toString() | |
const user = await getUserById(userId) | |
if (user == null || user.status !== Status.Normal) | |
throw new Error('用户不存在 | User does not exist.') | |
await updateUserChatModel(userId, chatModel) | |
res.send({ status: 'Success', message: '更新成功 | Update successfully' }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.get('/users', rootAuth, async (req, res) => { | |
try { | |
const page = +req.query.page | |
const size = +req.query.size | |
const data = await getUsers(page, size) | |
res.send({ status: 'Success', message: '获取成功 | Get successfully', data }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/user-status', rootAuth, async (req, res) => { | |
try { | |
const { userId, status } = req.body as { userId: string; status: Status } | |
const user = await getUserById(userId) | |
await updateUserStatus(userId, status) | |
if ((user.status === Status.PreVerify || user.status === Status.AdminVerify) && status === Status.Normal) | |
await sendNoticeMail(user.email) | |
res.send({ status: 'Success', message: '更新成功 | Update successfully' }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/user-role', rootAuth, async (req, res) => { | |
try { | |
const { userId, roles } = req.body as { userId: string; roles: UserRole[] } | |
await updateUserRole(userId, roles) | |
res.send({ status: 'Success', message: '更新成功 | Update successfully' }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/verify', authLimiter, async (req, res) => { | |
try { | |
const { token } = req.body as { token: string } | |
if (!token) | |
throw new Error('Secret key is empty') | |
const username = await checkUserVerify(token) | |
const user = await getUser(username) | |
if (user != null && user.status === Status.Normal) { | |
res.send({ status: 'Fail', message: '账号已存在 | The email exists', data: null }) | |
return | |
} | |
const config = await getCacheConfig() | |
let message = '验证成功 | Verify successfully' | |
if (config.siteConfig.registerReview) { | |
await verifyUser(username, Status.AdminVerify) | |
await sendVerifyMailAdmin(process.env.ROOT_USER, username, await getUserVerifyUrlAdmin(username)) | |
message = '验证成功, 请等待管理员开通 | Verify successfully, Please wait for the admin to activate' | |
} | |
else { | |
await verifyUser(username, Status.Normal) | |
} | |
res.send({ status: 'Success', message, data: null }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/verifyadmin', authLimiter, async (req, res) => { | |
try { | |
const { token } = req.body as { token: string } | |
if (!token) | |
throw new Error('Secret key is empty') | |
const username = await checkUserVerifyAdmin(token) | |
const user = await getUser(username) | |
if (user != null && user.status === Status.Normal) { | |
res.send({ status: 'Fail', message: '账户已开通 | The email has been opened.', data: null }) | |
return | |
} | |
await verifyUser(username, Status.Normal) | |
await sendNoticeMail(username) | |
res.send({ status: 'Success', message: '账户已激活 | Account has been activated.', data: null }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/setting-base', rootAuth, async (req, res) => { | |
try { | |
const { apiKey, apiModel, apiBaseUrl, accessToken, timeoutMs, reverseProxy, socksProxy, socksAuth, httpsProxy } = req.body as Config | |
const thisConfig = await getOriginConfig() | |
thisConfig.apiKey = apiKey | |
thisConfig.apiModel = apiModel | |
thisConfig.apiBaseUrl = apiBaseUrl | |
thisConfig.accessToken = accessToken | |
thisConfig.reverseProxy = reverseProxy | |
thisConfig.timeoutMs = timeoutMs | |
thisConfig.socksProxy = socksProxy | |
thisConfig.socksAuth = socksAuth | |
thisConfig.httpsProxy = httpsProxy | |
await updateConfig(thisConfig) | |
clearConfigCache() | |
const response = await chatConfig() | |
res.send({ status: 'Success', message: '操作成功 | Successfully', data: response.data }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/setting-site', rootAuth, async (req, res) => { | |
try { | |
const config = req.body as SiteConfig | |
const thisConfig = await getOriginConfig() | |
thisConfig.siteConfig = config | |
const result = await updateConfig(thisConfig) | |
clearConfigCache() | |
res.send({ status: 'Success', message: '操作成功 | Successfully', data: result.siteConfig }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/setting-mail', rootAuth, async (req, res) => { | |
try { | |
const config = req.body as MailConfig | |
const thisConfig = await getOriginConfig() | |
thisConfig.mailConfig = config | |
const result = await updateConfig(thisConfig) | |
clearConfigCache() | |
res.send({ status: 'Success', message: '操作成功 | Successfully', data: result.mailConfig }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/mail-test', rootAuth, async (req, res) => { | |
try { | |
const config = req.body as MailConfig | |
const userId = req.headers.userId as string | |
const user = await getUserById(userId) | |
await sendTestMail(user.email, config) | |
res.send({ status: 'Success', message: '发送成功 | Successfully', data: null }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/setting-audit', rootAuth, async (req, res) => { | |
try { | |
const config = req.body as AuditConfig | |
const thisConfig = await getOriginConfig() | |
thisConfig.auditConfig = config | |
const result = await updateConfig(thisConfig) | |
clearConfigCache() | |
if (config.enabled) | |
initAuditService(config) | |
res.send({ status: 'Success', message: '操作成功 | Successfully', data: result.auditConfig }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/audit-test', rootAuth, async (req, res) => { | |
try { | |
const { audit, text } = req.body as { audit: AuditConfig; text: string } | |
const config = await getCacheConfig() | |
if (audit.enabled) | |
initAuditService(audit) | |
const result = await containsSensitiveWords(audit, text) | |
if (audit.enabled) | |
initAuditService(config.auditConfig) | |
res.send({ status: 'Success', message: result ? '含敏感词 | Contains sensitive words' : '不含敏感词 | Does not contain sensitive words.', data: null }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.get('/setting-keys', rootAuth, async (req, res) => { | |
try { | |
const result = await getApiKeys() | |
res.send({ status: 'Success', message: null, data: result }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/setting-key-status', rootAuth, async (req, res) => { | |
try { | |
const { id, status } = req.body as { id: string; status: Status } | |
await updateApiKeyStatus(id, status) | |
clearApiKeyCache() | |
res.send({ status: 'Success', message: '更新成功 | Update successfully' }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/setting-key-upsert', rootAuth, async (req, res) => { | |
try { | |
const keyConfig = req.body as KeyConfig | |
if (keyConfig._id !== undefined) | |
keyConfig._id = new ObjectId(keyConfig._id) | |
await upsertKey(keyConfig) | |
clearApiKeyCache() | |
res.send({ status: 'Success', message: '成功 | Successfully' }) | |
} | |
catch (error) { | |
res.send({ status: 'Fail', message: error.message, data: null }) | |
} | |
}) | |
router.post('/statistics/by-day', auth, async (req, res) => { | |
try { | |
const userId = req.headers.userId | |
const { start, end } = req.body as { start: number; end: number } | |
const data = await getUserStatisticsByDay(new ObjectId(userId as string), start, end) | |
res.send({ status: 'Success', message: '', data }) | |
} | |
catch (error) { | |
res.send(error) | |
} | |
}) | |
app.use('', router) | |
app.use('/api', router) | |
app.listen(3002, () => globalThis.console.log('Server is running on port 3002')) | |