From 0cccd04c72e278300c603d746b13d0a07c444ef8 Mon Sep 17 00:00:00 2001
From: Kerwin
Date: Tue, 14 Mar 2023 23:42:01 +0800
Subject: [PATCH 001/147] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=B3=A8?=
=?UTF-8?q?=E5=86=8C=E7=99=BB=E5=BD=95&=E5=90=8C=E6=AD=A5=E8=81=8A?=
=?UTF-8?q?=E5=A4=A9=E6=95=B0=E6=8D=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
docker-compose/docker-compose.yml | 41 ++-
package.json | 1 +
pnpm-lock.yaml | 6 +
service/.env.example | 39 ++-
service/package.json | 4 +
service/pnpm-lock.yaml | 345 ++++++++++++++-------
service/src/index.ts | 214 ++++++++++++-
service/src/middleware/auth.ts | 10 +-
service/src/storage/model.ts | 81 +++++
service/src/storage/mongo.ts | 143 +++++++++
service/src/types.ts | 1 +
service/src/utils/mail.ts | 30 ++
service/src/utils/security.ts | 32 ++
src/api/index.ts | 68 +++-
src/components/common/UserAvatar/index.vue | 13 +-
src/locales/en-US.ts | 5 +-
src/locales/zh-CN.ts | 5 +-
src/locales/zh-TW.ts | 5 +-
src/store/modules/auth/index.ts | 16 +-
src/store/modules/chat/index.ts | 44 ++-
src/store/modules/user/helper.ts | 6 +-
src/typings/chat.d.ts | 1 +
src/views/chat/index.vue | 10 +-
src/views/chat/layout/Permission.vue | 105 ++++++-
src/views/chat/layout/sider/Footer.vue | 17 +-
src/views/chat/layout/sider/List.vue | 15 +-
27 files changed, 1095 insertions(+), 163 deletions(-)
create mode 100644 service/src/storage/model.ts
create mode 100644 service/src/storage/mongo.ts
create mode 100644 service/src/utils/mail.ts
create mode 100644 service/src/utils/security.ts
diff --git a/.gitignore b/.gitignore
index 897b8d4033..0fedcce528 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,4 @@ coverage
# Environment variables files
/service/.env
+/docker-compose/nginx/html
\ No newline at end of file
diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml
index 91dee3c0a8..48456817eb 100644
--- a/docker-compose/docker-compose.yml
+++ b/docker-compose/docker-compose.yml
@@ -5,6 +5,8 @@ services:
image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可
ports:
- 3002:3002
+ depends_on:
+ - database
environment:
# 二选一
OPENAI_API_KEY: xxxx
@@ -16,7 +18,7 @@ services:
OPENAI_API_MODEL: xxxx
# 反向代理,可选
API_REVERSE_PROXY: xxx
- # 访问权限密钥,可选
+ # 访问jwt加密参数,可选 不为空则允许登录 同时需要设置 MONGODB_URL
AUTH_SECRET_KEY: xxx
# 超时,单位毫秒,可选
TIMEOUT_MS: 60000
@@ -26,6 +28,40 @@ services:
SOCKS_PROXY_PORT: xxxx
# HTTPS_PROXY 代理,可选
HTTPS_PROXY: http://xxxx:7890
+ # mongodb 的连接字符串
+ MONGODB_URL: 'mongodb://chatgpt:xxxx@database:27017'
+ # 网站是否开启注册
+ REGISTER_ENABLED: false
+ # 开启注册之后 网站注册允许的邮箱后缀 如果空 则允许任意后缀
+ REGISTER_MAILS: '@qq.com,@sina.com,@163.com'
+ # 开启注册之后 密码加密的盐
+ PASSWORD_MD5_SALT: anysalt
+ # 开启注册之后 超级管理邮箱
+ ROOT_USER: xxx@qq.com
+ # 开启注册之后 网站域名 不含 / 注册的时候发送验证邮箱使用
+ SITE_DOMAIN: http://127.0.0.1:1002
+ # 开启注册之后 发送验证邮箱配置
+ SMTP_HOST: smtp.exmail.qq.com
+ SMTP_PORT: 465
+ SMTP_TSL: true
+ SMTP_USERNAME: ${SMTP_USERNAME}
+ SMTP_PASSWORD: ${SMTP_PASSWORD}
+ links:
+ - database
+
+ database:
+ image: mongo
+ ports:
+ - '27017:27017'
+ expose:
+ - '27017'
+ volumes:
+ - mongodb:/data/db
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: chatgpt
+ MONGO_INITDB_ROOT_PASSWORD: xxxx
+ MONGO_INITDB_DATABASE: chatgpt
+
nginx:
image: nginx:alpine
ports:
@@ -37,3 +73,6 @@ services:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
links:
- app
+
+volumes:
+ mongodb: {}
diff --git a/package.json b/package.json
index 9dad0f4e71..58e3a696f2 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@vueuse/core": "^9.13.0",
"highlight.js": "^11.7.0",
"html2canvas": "^1.4.1",
+ "jwt-decode": "^3.1.2",
"katex": "^0.16.4",
"markdown-it": "^13.0.1",
"naive-ui": "^2.34.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c2f4362465..c4c6532402 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -19,6 +19,7 @@ specifiers:
highlight.js: ^11.7.0
html2canvas: ^1.4.1
husky: ^8.0.3
+ jwt-decode: ^3.1.2
katex: ^0.16.4
less: ^4.1.3
lint-staged: ^13.1.2
@@ -42,6 +43,7 @@ dependencies:
'@vueuse/core': 9.13.0_vue@3.2.47
highlight.js: 11.7.0
html2canvas: 1.4.1
+ jwt-decode: 3.1.2
katex: 0.16.4
markdown-it: 13.0.1
naive-ui: 2.34.3_vue@3.2.47
@@ -4593,6 +4595,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /jwt-decode/3.1.2:
+ resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
+ dev: false
+
/katex/0.16.4:
resolution: {integrity: sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw==}
hasBin: true
diff --git a/service/.env.example b/service/.env.example
index 7ca4a4db2d..ac2366f6e3 100644
--- a/service/.env.example
+++ b/service/.env.example
@@ -16,9 +16,6 @@ API_REVERSE_PROXY=
# timeout
TIMEOUT_MS=100000
-# Secret key
-AUTH_SECRET_KEY=
-
# Socks Proxy Host
SOCKS_PROXY_HOST=
@@ -27,3 +24,39 @@ SOCKS_PROXY_PORT=
# HTTPS PROXY
HTTPS_PROXY=
+
+# Databse connection string
+# MONGODB_URL=mongodb://chatgpt:xxxx@yourip:port
+MONGODB_URL=mongodb://chatgpt:xxxx@database:27017
+
+# Secret key for jwt
+# If not empty, will need login
+AUTH_SECRET_KEY=
+
+# ----- Only valid after setting AUTH_SECRET_KEY begin ----
+
+# Allow anyone register
+REGISTER_ENABLED=false
+
+# The site domain, Only for registration account verification
+# without end /
+SITE_DOMAIN=http://127.0.0.1:1002
+
+# Allowed Email Providers, If it is empty, any mailbox is allowed
+# REGISTER_MAILS=@qq.com,@sina.com,@163.com
+REGISTER_MAILS=@qq.com,@sina.com,@163.com
+
+# The roon user only email
+ROOT_USER=
+
+# Password salt
+PASSWORD_MD5_SALT=anysalt
+
+# User register email verify
+SMTP_HOST=smtp.exmail.qq.com
+SMTP_PORT=465
+SMTP_TSL=true
+SMTP_USERNAME=yourname@example.com
+SMTP_PASSWORD=yourpassword
+
+# ----- Only valid after setting AUTH_SECRET_KEY end ----
\ No newline at end of file
diff --git a/service/package.json b/service/package.json
index 4b74e5a284..e9535cb1de 100644
--- a/service/package.json
+++ b/service/package.json
@@ -30,14 +30,18 @@
"express": "^4.18.2",
"https-proxy-agent": "^5.0.1",
"isomorphic-fetch": "^3.0.0",
+ "mongodb": "^5.1.0",
"node-fetch": "^3.3.0",
+ "nodemailer": "^6.9.1",
"socks-proxy-agent": "^7.0.0"
},
"devDependencies": {
"@antfu/eslint-config": "^0.35.3",
"@types/express": "^4.17.17",
+ "@types/mongodb": "^4.0.7",
"@types/node": "^18.14.6",
"eslint": "^8.35.0",
+ "jsonwebtoken": "^9.0.0",
"rimraf": "^4.3.0",
"tsup": "^6.6.3",
"typescript": "^4.9.5"
diff --git a/service/pnpm-lock.yaml b/service/pnpm-lock.yaml
index e2e251f56c..90636bb318 100644
--- a/service/pnpm-lock.yaml
+++ b/service/pnpm-lock.yaml
@@ -3,6 +3,7 @@ lockfileVersion: 5.4
specifiers:
'@antfu/eslint-config': ^0.35.3
'@types/express': ^4.17.17
+ '@types/mongodb': ^4.0.7
'@types/node': ^18.14.6
chatgpt: ^5.0.10
dotenv: ^16.0.3
@@ -11,7 +12,10 @@ specifiers:
express: ^4.18.2
https-proxy-agent: ^5.0.1
isomorphic-fetch: ^3.0.0
+ jsonwebtoken: ^9.0.0
+ mongodb: ^5.1.0
node-fetch: ^3.3.0
+ nodemailer: ^6.9.1
rimraf: ^4.3.0
socks-proxy-agent: ^7.0.0
tsup: ^6.6.3
@@ -24,15 +28,19 @@ dependencies:
express: 4.18.2
https-proxy-agent: 5.0.1
isomorphic-fetch: 3.0.0
+ mongodb: 5.1.0
node-fetch: 3.3.0
+ nodemailer: 6.9.1
socks-proxy-agent: 7.0.0
devDependencies:
'@antfu/eslint-config': 0.35.3_ycpbpc6yetojsgtrx3mwntkhsu
'@types/express': 4.17.17
+ '@types/mongodb': 4.0.7
'@types/node': 18.14.6
eslint: 8.35.0
- rimraf: 4.3.0
+ jsonwebtoken: 9.0.0
+ rimraf: 4.3.1
tsup: 6.6.3_typescript@4.9.5
typescript: 4.9.5
@@ -166,7 +174,7 @@ packages:
/@esbuild-kit/core-utils/3.1.0:
resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==}
dependencies:
- esbuild: 0.17.11
+ esbuild: 0.17.8
source-map-support: 0.5.21
dev: false
@@ -177,187 +185,187 @@ packages:
get-tsconfig: 4.4.0
dev: false
- /@esbuild/android-arm/0.17.11:
- resolution: {integrity: sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==}
+ /@esbuild/android-arm/0.17.8:
+ resolution: {integrity: sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
optional: true
- /@esbuild/android-arm64/0.17.11:
- resolution: {integrity: sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==}
+ /@esbuild/android-arm64/0.17.8:
+ resolution: {integrity: sha512-oa/N5j6v1svZQs7EIRPqR8f+Bf8g6HBDjD/xHC02radE/NjKHK7oQmtmLxPs1iVwYyvE+Kolo6lbpfEQ9xnhxQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
optional: true
- /@esbuild/android-x64/0.17.11:
- resolution: {integrity: sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==}
+ /@esbuild/android-x64/0.17.8:
+ resolution: {integrity: sha512-bTliMLqD7pTOoPg4zZkXqCDuzIUguEWLpeqkNfC41ODBHwoUgZ2w5JBeYimv4oP6TDVocoYmEhZrCLQTrH89bg==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
optional: true
- /@esbuild/darwin-arm64/0.17.11:
- resolution: {integrity: sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==}
+ /@esbuild/darwin-arm64/0.17.8:
+ resolution: {integrity: sha512-ghAbV3ia2zybEefXRRm7+lx8J/rnupZT0gp9CaGy/3iolEXkJ6LYRq4IpQVI9zR97ID80KJVoUlo3LSeA/sMAg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
optional: true
- /@esbuild/darwin-x64/0.17.11:
- resolution: {integrity: sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==}
+ /@esbuild/darwin-x64/0.17.8:
+ resolution: {integrity: sha512-n5WOpyvZ9TIdv2V1K3/iIkkJeKmUpKaCTdun9buhGRWfH//osmUjlv4Z5mmWdPWind/VGcVxTHtLfLCOohsOXw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
optional: true
- /@esbuild/freebsd-arm64/0.17.11:
- resolution: {integrity: sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==}
+ /@esbuild/freebsd-arm64/0.17.8:
+ resolution: {integrity: sha512-a/SATTaOhPIPFWvHZDoZYgxaZRVHn0/LX1fHLGfZ6C13JqFUZ3K6SMD6/HCtwOQ8HnsNaEeokdiDSFLuizqv5A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
optional: true
- /@esbuild/freebsd-x64/0.17.11:
- resolution: {integrity: sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==}
+ /@esbuild/freebsd-x64/0.17.8:
+ resolution: {integrity: sha512-xpFJb08dfXr5+rZc4E+ooZmayBW6R3q59daCpKZ/cDU96/kvDM+vkYzNeTJCGd8rtO6fHWMq5Rcv/1cY6p6/0Q==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
optional: true
- /@esbuild/linux-arm/0.17.11:
- resolution: {integrity: sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==}
+ /@esbuild/linux-arm/0.17.8:
+ resolution: {integrity: sha512-6Ij8gfuGszcEwZpi5jQIJCVIACLS8Tz2chnEBfYjlmMzVsfqBP1iGmHQPp7JSnZg5xxK9tjCc+pJ2WtAmPRFVA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/linux-arm64/0.17.11:
- resolution: {integrity: sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==}
+ /@esbuild/linux-arm64/0.17.8:
+ resolution: {integrity: sha512-v3iwDQuDljLTxpsqQDl3fl/yihjPAyOguxuloON9kFHYwopeJEf1BkDXODzYyXEI19gisEsQlG1bM65YqKSIww==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/linux-ia32/0.17.11:
- resolution: {integrity: sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==}
+ /@esbuild/linux-ia32/0.17.8:
+ resolution: {integrity: sha512-8svILYKhE5XetuFk/B6raFYIyIqydQi+GngEXJgdPdI7OMKUbSd7uzR02wSY4kb53xBrClLkhH4Xs8P61Q2BaA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/linux-loong64/0.17.11:
- resolution: {integrity: sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==}
+ /@esbuild/linux-loong64/0.17.8:
+ resolution: {integrity: sha512-B6FyMeRJeV0NpyEOYlm5qtQfxbdlgmiGdD+QsipzKfFky0K5HW5Td6dyK3L3ypu1eY4kOmo7wW0o94SBqlqBSA==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/linux-mips64el/0.17.11:
- resolution: {integrity: sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==}
+ /@esbuild/linux-mips64el/0.17.8:
+ resolution: {integrity: sha512-CCb67RKahNobjm/eeEqeD/oJfJlrWyw29fgiyB6vcgyq97YAf3gCOuP6qMShYSPXgnlZe/i4a8WFHBw6N8bYAA==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/linux-ppc64/0.17.11:
- resolution: {integrity: sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==}
+ /@esbuild/linux-ppc64/0.17.8:
+ resolution: {integrity: sha512-bytLJOi55y55+mGSdgwZ5qBm0K9WOCh0rx+vavVPx+gqLLhxtSFU0XbeYy/dsAAD6xECGEv4IQeFILaSS2auXw==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/linux-riscv64/0.17.11:
- resolution: {integrity: sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==}
+ /@esbuild/linux-riscv64/0.17.8:
+ resolution: {integrity: sha512-2YpRyQJmKVBEHSBLa8kBAtbhucaclb6ex4wchfY0Tj3Kg39kpjeJ9vhRU7x4mUpq8ISLXRXH1L0dBYjAeqzZAw==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/linux-s390x/0.17.11:
- resolution: {integrity: sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==}
+ /@esbuild/linux-s390x/0.17.8:
+ resolution: {integrity: sha512-QgbNY/V3IFXvNf11SS6exkpVcX0LJcob+0RWCgV9OiDAmVElnxciHIisoSix9uzYzScPmS6dJFbZULdSAEkQVw==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/linux-x64/0.17.11:
- resolution: {integrity: sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==}
+ /@esbuild/linux-x64/0.17.8:
+ resolution: {integrity: sha512-mM/9S0SbAFDBc4OPoyP6SEOo5324LpUxdpeIUUSrSTOfhHU9hEfqRngmKgqILqwx/0DVJBzeNW7HmLEWp9vcOA==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
optional: true
- /@esbuild/netbsd-x64/0.17.11:
- resolution: {integrity: sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==}
+ /@esbuild/netbsd-x64/0.17.8:
+ resolution: {integrity: sha512-eKUYcWaWTaYr9zbj8GertdVtlt1DTS1gNBWov+iQfWuWyuu59YN6gSEJvFzC5ESJ4kMcKR0uqWThKUn5o8We6Q==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
optional: true
- /@esbuild/openbsd-x64/0.17.11:
- resolution: {integrity: sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==}
+ /@esbuild/openbsd-x64/0.17.8:
+ resolution: {integrity: sha512-Vc9J4dXOboDyMXKD0eCeW0SIeEzr8K9oTHJU+Ci1mZc5njPfhKAqkRt3B/fUNU7dP+mRyralPu8QUkiaQn7iIg==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
optional: true
- /@esbuild/sunos-x64/0.17.11:
- resolution: {integrity: sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==}
+ /@esbuild/sunos-x64/0.17.8:
+ resolution: {integrity: sha512-0xvOTNuPXI7ft1LYUgiaXtpCEjp90RuBBYovdd2lqAFxje4sEucurg30M1WIm03+3jxByd3mfo+VUmPtRSVuOw==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
optional: true
- /@esbuild/win32-arm64/0.17.11:
- resolution: {integrity: sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==}
+ /@esbuild/win32-arm64/0.17.8:
+ resolution: {integrity: sha512-G0JQwUI5WdEFEnYNKzklxtBheCPkuDdu1YrtRrjuQv30WsYbkkoixKxLLv8qhJmNI+ATEWquZe/N0d0rpr55Mg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
optional: true
- /@esbuild/win32-ia32/0.17.11:
- resolution: {integrity: sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==}
+ /@esbuild/win32-ia32/0.17.8:
+ resolution: {integrity: sha512-Fqy63515xl20OHGFykjJsMnoIWS+38fqfg88ClvPXyDbLtgXal2DTlhb1TfTX34qWi3u4I7Cq563QcHpqgLx8w==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
optional: true
- /@esbuild/win32-x64/0.17.11:
- resolution: {integrity: sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==}
+ /@esbuild/win32-x64/0.17.8:
+ resolution: {integrity: sha512-1iuezdyDNngPnz8rLRDO2C/ZZ/emJLb72OsZeqQ6gL6Avko/XCXZw+NuxBSNhBAP13Hie418V7VMt9et1FMvpg==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
optional: true
- /@eslint-community/eslint-utils/4.2.0_eslint@8.35.0:
- resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==}
+ /@eslint-community/eslint-utils/4.1.2_eslint@8.35.0:
+ resolution: {integrity: sha512-7qELuQWWjVDdVsFQ5+beUl+KPczrEDA7S3zM4QUd/bJl7oXgsmpXaEVqrRTnOBqenOV4rWf2kVZk2Ot085zPWA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
- eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
eslint: 8.35.0
eslint-visitor-keys: 3.3.0
@@ -453,7 +461,7 @@ packages:
'@types/body-parser': 1.19.2
'@types/express-serve-static-core': 4.17.33
'@types/qs': 6.9.7
- '@types/serve-static': 1.15.1
+ '@types/serve-static': 1.15.0
dev: true
/@types/json-schema/7.0.11:
@@ -474,9 +482,19 @@ packages:
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
dev: true
+ /@types/mongodb/4.0.7:
+ resolution: {integrity: sha512-lPUYPpzA43baXqnd36cZ9xxorprybxXDzteVKCPAdp14ppHtFJHnXYvNpmBvtMUTb5fKXVv6sVbzo1LHkWhJlw==}
+ deprecated: mongodb provides its own types. @types/mongodb is no longer needed.
+ dependencies:
+ mongodb: 5.1.0
+ transitivePeerDependencies:
+ - '@aws-sdk/credential-providers'
+ - mongodb-client-encryption
+ - snappy
+ dev: true
+
/@types/node/18.14.6:
resolution: {integrity: sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==}
- dev: true
/@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -493,8 +511,8 @@ packages:
resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
dev: true
- /@types/serve-static/1.15.1:
- resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
+ /@types/serve-static/1.15.0:
+ resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==}
dependencies:
'@types/mime': 3.0.1
'@types/node': 18.14.6
@@ -504,6 +522,15 @@ packages:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: true
+ /@types/webidl-conversions/7.0.0:
+ resolution: {integrity: sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==}
+
+ /@types/whatwg-url/8.2.2:
+ resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==}
+ dependencies:
+ '@types/node': 18.14.6
+ '@types/webidl-conversions': 7.0.0
+
/@typescript-eslint/eslint-plugin/5.54.0_6mj2wypvdnknez7kws2nfdgupi:
resolution: {integrity: sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -831,6 +858,14 @@ packages:
fill-range: 7.0.1
dev: true
+ /bson/5.0.1:
+ resolution: {integrity: sha512-y09gBGusgHtinMon/GVbv1J6FrXhnr/+6hqLlSmEFzkz6PodqF6TxjyvfvY3AfO+oG1mgUtbC86xSbOlwvM62Q==}
+ engines: {node: '>=14.20.1'}
+
+ /buffer-equal-constant-time/1.0.1:
+ resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
+ dev: true
+
/buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: false
@@ -846,13 +881,13 @@ packages:
semver: 7.3.8
dev: true
- /bundle-require/4.0.1_esbuild@0.17.11:
+ /bundle-require/4.0.1_esbuild@0.17.8:
resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
peerDependencies:
esbuild: '>=0.17'
dependencies:
- esbuild: 0.17.11
+ esbuild: 0.17.8
load-tsconfig: 0.2.3
dev: true
@@ -1152,6 +1187,12 @@ packages:
engines: {node: '>=12'}
dev: false
+ /ecdsa-sig-formatter/1.0.11:
+ resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+ dependencies:
+ safe-buffer: 5.2.1
+ dev: true
+
/ee-first/1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
@@ -1195,7 +1236,7 @@ packages:
has-proto: 1.0.1
has-symbols: 1.0.3
internal-slot: 1.0.5
- is-array-buffer: 3.0.2
+ is-array-buffer: 3.0.1
is-callable: 1.2.7
is-negative-zero: 2.0.2
is-regex: 1.1.4
@@ -1239,34 +1280,34 @@ packages:
is-symbol: 1.0.4
dev: true
- /esbuild/0.17.11:
- resolution: {integrity: sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==}
+ /esbuild/0.17.8:
+ resolution: {integrity: sha512-g24ybC3fWhZddZK6R3uD2iF/RIPnRpwJAqLov6ouX3hMbY4+tKolP0VMF3zuIYCaXun+yHwS5IPQ91N2BT191g==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
- '@esbuild/android-arm': 0.17.11
- '@esbuild/android-arm64': 0.17.11
- '@esbuild/android-x64': 0.17.11
- '@esbuild/darwin-arm64': 0.17.11
- '@esbuild/darwin-x64': 0.17.11
- '@esbuild/freebsd-arm64': 0.17.11
- '@esbuild/freebsd-x64': 0.17.11
- '@esbuild/linux-arm': 0.17.11
- '@esbuild/linux-arm64': 0.17.11
- '@esbuild/linux-ia32': 0.17.11
- '@esbuild/linux-loong64': 0.17.11
- '@esbuild/linux-mips64el': 0.17.11
- '@esbuild/linux-ppc64': 0.17.11
- '@esbuild/linux-riscv64': 0.17.11
- '@esbuild/linux-s390x': 0.17.11
- '@esbuild/linux-x64': 0.17.11
- '@esbuild/netbsd-x64': 0.17.11
- '@esbuild/openbsd-x64': 0.17.11
- '@esbuild/sunos-x64': 0.17.11
- '@esbuild/win32-arm64': 0.17.11
- '@esbuild/win32-ia32': 0.17.11
- '@esbuild/win32-x64': 0.17.11
+ '@esbuild/android-arm': 0.17.8
+ '@esbuild/android-arm64': 0.17.8
+ '@esbuild/android-x64': 0.17.8
+ '@esbuild/darwin-arm64': 0.17.8
+ '@esbuild/darwin-x64': 0.17.8
+ '@esbuild/freebsd-arm64': 0.17.8
+ '@esbuild/freebsd-x64': 0.17.8
+ '@esbuild/linux-arm': 0.17.8
+ '@esbuild/linux-arm64': 0.17.8
+ '@esbuild/linux-ia32': 0.17.8
+ '@esbuild/linux-loong64': 0.17.8
+ '@esbuild/linux-mips64el': 0.17.8
+ '@esbuild/linux-ppc64': 0.17.8
+ '@esbuild/linux-riscv64': 0.17.8
+ '@esbuild/linux-s390x': 0.17.8
+ '@esbuild/linux-x64': 0.17.8
+ '@esbuild/netbsd-x64': 0.17.8
+ '@esbuild/openbsd-x64': 0.17.8
+ '@esbuild/sunos-x64': 0.17.8
+ '@esbuild/win32-arm64': 0.17.8
+ '@esbuild/win32-ia32': 0.17.8
+ '@esbuild/win32-x64': 0.17.8
/escape-html/1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
@@ -1384,7 +1425,7 @@ packages:
object.values: 1.1.6
resolve: 1.22.1
semver: 6.3.0
- tsconfig-paths: 3.14.2
+ tsconfig-paths: 3.14.1
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@@ -1474,11 +1515,11 @@ packages:
eslint: '>=8.28.0'
dependencies:
'@babel/helper-validator-identifier': 7.19.1
- '@eslint-community/eslint-utils': 4.2.0_eslint@8.35.0
+ '@eslint-community/eslint-utils': 4.1.2_eslint@8.35.0
ci-info: 3.8.0
clean-regexp: 1.0.0
eslint: 8.35.0
- esquery: 1.5.0
+ esquery: 1.4.2
indent-string: 4.0.0
is-builtin-module: 3.2.1
jsesc: 3.0.2
@@ -1613,7 +1654,7 @@ packages:
eslint-utils: 3.0.0_eslint@8.35.0
eslint-visitor-keys: 3.3.0
espree: 9.4.1
- esquery: 1.5.0
+ esquery: 1.4.2
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1
@@ -1658,8 +1699,8 @@ packages:
eslint-visitor-keys: 3.3.0
dev: true
- /esquery/1.5.0:
- resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
+ /esquery/1.4.2:
+ resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==}
engines: {node: '>=0.10'}
dependencies:
estraverse: 5.3.0
@@ -2147,7 +2188,6 @@ packages:
/ip/2.0.0:
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
- dev: false
/ipaddr.js/1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
@@ -2165,8 +2205,8 @@ packages:
is-decimal: 1.0.4
dev: true
- /is-array-buffer/3.0.2:
- resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
+ /is-array-buffer/3.0.1:
+ resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==}
dependencies:
call-bind: 1.0.2
get-intrinsic: 1.2.0
@@ -2396,6 +2436,31 @@ packages:
semver: 7.3.8
dev: true
+ /jsonwebtoken/9.0.0:
+ resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==}
+ engines: {node: '>=12', npm: '>=6'}
+ dependencies:
+ jws: 3.2.2
+ lodash: 4.17.21
+ ms: 2.1.3
+ semver: 7.3.8
+ dev: true
+
+ /jwa/1.4.1:
+ resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
+ dependencies:
+ buffer-equal-constant-time: 1.0.1
+ ecdsa-sig-formatter: 1.0.11
+ safe-buffer: 5.2.1
+ dev: true
+
+ /jws/3.2.2:
+ resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
+ dependencies:
+ jwa: 1.4.1
+ safe-buffer: 5.2.1
+ dev: true
+
/keyv/4.5.2:
resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==}
dependencies:
@@ -2410,8 +2475,8 @@ packages:
type-check: 0.4.0
dev: true
- /lilconfig/2.1.0:
- resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
+ /lilconfig/2.0.6:
+ resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==}
engines: {node: '>=10'}
dev: true
@@ -2467,8 +2532,8 @@ packages:
dependencies:
yallist: 4.0.0
- /lru-cache/7.18.3:
- resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
+ /lru-cache/7.18.1:
+ resolution: {integrity: sha512-8/HcIENyQnfUTCDizRu9rrDyG6XG/21M4X7/YEGZeD76ZJilFPAUVb/2zysFf7VVO1LEjCDFyHp8pMMvozIrvg==}
engines: {node: '>=12'}
dev: true
@@ -2493,6 +2558,10 @@ packages:
engines: {node: '>= 0.6'}
dev: false
+ /memory-pager/1.5.0:
+ resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
+ optional: true
+
/merge-descriptors/1.0.1:
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
dev: false
@@ -2583,6 +2652,33 @@ packages:
engines: {node: '>=8'}
dev: true
+ /mongodb-connection-string-url/2.6.0:
+ resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==}
+ dependencies:
+ '@types/whatwg-url': 8.2.2
+ whatwg-url: 11.0.0
+
+ /mongodb/5.1.0:
+ resolution: {integrity: sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==}
+ engines: {node: '>=14.20.1'}
+ peerDependencies:
+ '@aws-sdk/credential-providers': ^3.201.0
+ mongodb-client-encryption: ^2.3.0
+ snappy: ^7.2.2
+ peerDependenciesMeta:
+ '@aws-sdk/credential-providers':
+ optional: true
+ mongodb-client-encryption:
+ optional: true
+ snappy:
+ optional: true
+ dependencies:
+ bson: 5.0.1
+ mongodb-connection-string-url: 2.6.0
+ socks: 2.7.1
+ optionalDependencies:
+ saslprep: 1.0.3
+
/ms/2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false
@@ -2640,6 +2736,11 @@ packages:
formdata-polyfill: 4.0.10
dev: false
+ /nodemailer/6.9.1:
+ resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==}
+ engines: {node: '>=6.0.0'}
+ dev: false
+
/normalize-package-data/2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
@@ -2853,7 +2954,7 @@ packages:
resolution: {integrity: sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==}
engines: {node: '>=14'}
dependencies:
- lru-cache: 7.18.3
+ lru-cache: 7.18.1
minipass: 4.2.4
dev: true
@@ -2893,7 +2994,7 @@ packages:
ts-node:
optional: true
dependencies:
- lilconfig: 2.1.0
+ lilconfig: 2.0.6
yaml: 1.10.2
dev: true
@@ -3060,16 +3161,16 @@ packages:
glob: 7.2.3
dev: true
- /rimraf/4.3.0:
- resolution: {integrity: sha512-5qVDXPbByA1qSJEWMv1qAwKsoS22vVpsL2QyxCKBw4gf6XiFo1K3uRLY6uSOOBFDwnqAZtnbILqWKKlzh8bkGg==}
+ /rimraf/4.3.1:
+ resolution: {integrity: sha512-GfHJHBzFQra23IxDzIdBqhOWfbtdgS1/dCHrDy+yvhpoJY5TdwdT28oWaHWfRpKFDLd3GZnGTx6Mlt4+anbsxQ==}
engines: {node: '>=14'}
hasBin: true
dependencies:
glob: 9.2.1
dev: true
- /rollup/3.18.0:
- resolution: {integrity: sha512-J8C6VfEBjkvYPESMQYxKHxNOh4A5a3FlP+0BETGo34HEcE4eTlgCrO2+eWzlu2a/sHs2QUkZco+wscH7jhhgWg==}
+ /rollup/3.15.0:
+ resolution: {integrity: sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
@@ -3084,7 +3185,6 @@ packages:
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
- dev: false
/safe-regex-test/1.0.0:
resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
@@ -3104,6 +3204,14 @@ packages:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
+ /saslprep/1.0.3:
+ resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ sparse-bitfield: 3.0.3
+ optional: true
+
/semver/5.7.1:
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
hasBin: true
@@ -3189,7 +3297,6 @@ packages:
/smart-buffer/4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
- dev: false
/socks-proxy-agent/7.0.0:
resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==}
@@ -3208,7 +3315,6 @@ packages:
dependencies:
ip: 2.0.0
smart-buffer: 4.2.0
- dev: false
/source-map-support/0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
@@ -3229,6 +3335,12 @@ packages:
whatwg-url: 7.1.0
dev: true
+ /sparse-bitfield/3.0.3:
+ resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
+ dependencies:
+ memory-pager: 1.5.0
+ optional: true
+
/spdx-correct/3.1.1:
resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
dependencies:
@@ -3371,6 +3483,12 @@ packages:
punycode: 2.3.0
dev: true
+ /tr46/3.0.0:
+ resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
+ engines: {node: '>=12'}
+ dependencies:
+ punycode: 2.3.0
+
/tree-kill/1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
@@ -3380,8 +3498,8 @@ packages:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true
- /tsconfig-paths/3.14.2:
- resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==}
+ /tsconfig-paths/3.14.1:
+ resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==}
dependencies:
'@types/json5': 0.0.29
json5: 1.0.2
@@ -3409,17 +3527,17 @@ packages:
typescript:
optional: true
dependencies:
- bundle-require: 4.0.1_esbuild@0.17.11
+ bundle-require: 4.0.1_esbuild@0.17.8
cac: 6.7.14
chokidar: 3.5.3
debug: 4.3.4
- esbuild: 0.17.11
+ esbuild: 0.17.8
execa: 5.1.1
globby: 11.1.0
joycon: 3.1.1
postcss-load-config: 3.1.4
resolve-from: 5.0.0
- rollup: 3.18.0
+ rollup: 3.15.0
source-map: 0.8.0-beta.0
sucrase: 3.29.0
tree-kill: 1.2.2
@@ -3560,7 +3678,7 @@ packages:
eslint-scope: 7.1.1
eslint-visitor-keys: 3.3.0
espree: 9.4.1
- esquery: 1.5.0
+ esquery: 1.4.2
lodash: 4.17.21
semver: 7.3.8
transitivePeerDependencies:
@@ -3580,10 +3698,21 @@ packages:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
dev: true
+ /webidl-conversions/7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
/whatwg-fetch/3.6.2:
resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==}
dev: false
+ /whatwg-url/11.0.0:
+ resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ tr46: 3.0.0
+ webidl-conversions: 7.0.0
+
/whatwg-url/5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
diff --git a/service/src/index.ts b/service/src/index.ts
index 402deaa424..b3dbe81e01 100644
--- a/service/src/index.ts
+++ b/service/src/index.ts
@@ -1,7 +1,13 @@
import express from 'express'
+import jwt from 'jsonwebtoken'
import type { ChatContext, ChatMessage } from './chatgpt'
import { chatConfig, chatReplyProcess } from './chatgpt'
import { auth } from './middleware/auth'
+import type { ChatOptions } from './storage/model'
+import { Status } from './storage/model'
+import { clearChat, createChatRoom, createUser, deleteChat, deleteChatRoom, existsChatRoom, getChat, getChatRooms, getChats, getUser, insertChat, renameChatRoom, updateChat, verifyUser } from './storage/mongo'
+import { sendMail } from './utils/mail'
+import { checkUserVerify, getUserVerifyUrl, md5 } from './utils/security'
const app = express()
const router = express.Router()
@@ -16,16 +22,147 @@ app.all('*', (_, res, next) => {
next()
})
+router.get('/chatrooms', auth, async (req, res) => {
+ const userId = req.headers.userId
+ const rooms = await getChatRooms(userId)
+ const result = []
+ rooms.forEach((r) => {
+ result.push({
+ uuid: r.roomId,
+ title: r.title,
+ isEdit: false,
+ })
+ })
+ res.send({ status: 'Success', message: null, data: result })
+})
+
+router.post('/room-create', auth, async (req, res) => {
+ const userId = req.headers.userId
+ 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 })
+})
+
+router.post('/room-rename', auth, async (req, res) => {
+ const userId = req.headers.userId
+ 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 })
+})
+
+router.post('/room-delete', auth, async (req, res) => {
+ const userId = req.headers.userId
+ 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 })
+})
+
+router.get('/chat-hisroty', auth, async (req, res) => {
+ const userId = req.headers.userId
+ const roomId = +req.query.roomid
+ const lastTime = req.query.lasttime
+ 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, !lastTime ? null : parseInt(lastTime))
+
+ const result = []
+ chats.forEach((c) => {
+ if (c.status !== Status.InversionDeleted) {
+ result.push({
+ 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) {
+ result.push({
+ dateTime: new Date(c.dateTime).toLocaleString(),
+ text: c.response,
+ inversion: false,
+ error: false,
+ loading: false,
+ conversationOptions: {
+ parentMessageId: c.options.messageId,
+ },
+ requestOptions: {
+ prompt: c.prompt,
+ parentMessageId: c.options.parentMessageId,
+ },
+ })
+ }
+ })
+
+ res.send({ status: 'Success', message: null, data: result })
+})
+
+router.post('/chat-delete', auth, async (req, res) => {
+ const userId = req.headers.userId
+ 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 })
+})
+
+router.post('/chat-clear', auth, async (req, res) => {
+ const userId = req.headers.userId
+ 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 })
+})
+
+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')
+ await updateChat(message._id, response.data.text, response.data.id)
+ res.send(response)
+ }
+ catch (error) {
+ res.send(error)
+ }
+})
+
router.post('/chat-process', auth, async (req, res) => {
res.setHeader('Content-type', 'application/octet-stream')
try {
- const { prompt, options = {} } = req.body as { prompt: string; options?: ChatContext }
+ 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)
let firstChunk = true
- await chatReplyProcess(prompt, options, (chat: ChatMessage) => {
+ const result = await chatReplyProcess(prompt, options, (chat: ChatMessage) => {
res.write(firstChunk ? JSON.stringify(chat) : `\n${JSON.stringify(chat)}`)
firstChunk = false
})
+ if (result.status === 'Success')
+ await updateChat(message._id, result.data.text, result.data.id)
}
catch (error) {
res.write(JSON.stringify(error))
@@ -35,6 +172,45 @@ router.post('/chat-process', auth, async (req, res) => {
}
})
+router.post('/user-register', async (req, res) => {
+ const { username, password } = req.body as { username: string; password: string }
+
+ if (process.env.REGISTER_ENABLED !== 'true') {
+ res.send({ status: 'Fail', message: '注册账号功能未启用 | Register account is disabled!', data: null })
+ return
+ }
+ if (typeof process.env.REGISTER_MAILS === 'string' && process.env.REGISTER_MAILS.length > 0) {
+ let allowSuffix = false
+ const emailSuffixs = process.env.REGISTER_MAILS.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) {
+ res.send({ status: 'Fail', message: '邮箱已存在 | The email exists', data: null })
+ return
+ }
+ const newPassword = md5(password)
+ await createUser(username, newPassword)
+
+ if (username.toLowerCase() === process.env.ROOT_USER) {
+ res.send({ status: 'Success', message: '注册成功 | Register success', data: null })
+ }
+ else {
+ sendMail(username, getUserVerifyUrl(username))
+ res.send({ status: 'Success', message: '注册成功, 去邮箱中验证吧 | Registration is successful, you need to go to email verification', data: null })
+ }
+})
+
router.post('/config', async (req, res) => {
try {
const response = await chatConfig()
@@ -49,7 +225,31 @@ router.post('/session', async (req, res) => {
try {
const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY
const hasAuth = typeof AUTH_SECRET_KEY === 'string' && AUTH_SECRET_KEY.length > 0
- res.send({ status: 'Success', message: '', data: { auth: hasAuth } })
+ const allowRegister = process.env.REGISTER_ENABLED === 'true'
+ res.send({ status: 'Success', message: '', data: { auth: hasAuth, allowRegister } })
+ }
+ catch (error) {
+ res.send({ status: 'Fail', message: error.message, data: null })
+ }
+})
+
+router.post('/user-login', async (req, res) => {
+ try {
+ const { username, password } = req.body as { username: string; password: string }
+ if (!username || !password)
+ throw new Error('用户名或密码为空 | Username or password is empty')
+
+ const user = await getUser(username)
+ if (user == null
+ || user.status !== Status.Normal
+ || user.password !== md5(password)) {
+ if (user != null && user.status === Status.PreVerify)
+ throw new Error('请去邮箱中验证 | Please verify in the mailbox')
+ throw new Error('用户不存在或密码错误 | User does not exist or incorrect password.')
+ }
+
+ const token = jwt.sign({ email: username, userId: user._id }, process.env.AUTH_SECRET_KEY)
+ res.send({ status: 'Success', message: '登录成功 | Login successfully', data: { token } })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
@@ -61,11 +261,9 @@ router.post('/verify', async (req, res) => {
const { token } = req.body as { token: string }
if (!token)
throw new Error('Secret key is empty')
-
- if (process.env.AUTH_SECRET_KEY !== token)
- throw new Error('密钥无效 | Secret key is invalid')
-
- res.send({ status: 'Success', message: 'Verify successfully', data: null })
+ const username = await checkUserVerify(token)
+ await verifyUser(username)
+ res.send({ status: 'Success', message: '验证成功 | Verify successfully', data: null })
}
catch (error) {
res.send({ status: 'Fail', message: error.message, data: null })
diff --git a/service/src/middleware/auth.ts b/service/src/middleware/auth.ts
index 50bf02a257..6f1dba0a3f 100644
--- a/service/src/middleware/auth.ts
+++ b/service/src/middleware/auth.ts
@@ -1,10 +1,12 @@
+import jwt from 'jsonwebtoken'
+
const auth = async (req, res, next) => {
const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY
if (typeof AUTH_SECRET_KEY === 'string' && AUTH_SECRET_KEY.length > 0) {
try {
- const Authorization = req.header('Authorization')
- if (!Authorization || Authorization.replace('Bearer ', '').trim() !== AUTH_SECRET_KEY.trim())
- throw new Error('Error: 无访问权限 | No access rights')
+ const token = req.header('Authorization').replace('Bearer ', '')
+ const info = jwt.verify(token, AUTH_SECRET_KEY.trim())
+ req.headers.userId = info.userId
next()
}
catch (error) {
@@ -12,6 +14,8 @@ const auth = async (req, res, next) => {
}
}
else {
+ // fake userid
+ req.headers.userId = '6406d8c50aedd633885fa16f'
next()
}
}
diff --git a/service/src/storage/model.ts b/service/src/storage/model.ts
new file mode 100644
index 0000000000..14786fa4b5
--- /dev/null
+++ b/service/src/storage/model.ts
@@ -0,0 +1,81 @@
+import type { ObjectId } from 'mongodb'
+
+enum Status {
+ Normal = 0,
+ Deleted = 1,
+ InversionDeleted = 2,
+ ResponseDeleted = 3,
+ PreVerify = 4,
+}
+
+class UserInfo {
+ _id: ObjectId
+ name: string
+ email: string
+ password: string
+ status: Status
+ createTime: string
+ verifyTime?: string
+ constructor(email: string, password: string) {
+ this.name = email
+ this.email = email
+ this.password = password
+ this.status = Status.PreVerify
+ this.createTime = new Date().toLocaleString()
+ this.verifyTime = null
+ }
+}
+
+class UserOauth {
+ userId: number
+ oauthType: OauthType
+ oauthId: string
+
+ constructor(userId: number, oauthType: OauthType, oauthId: string) {
+ this.userId = userId
+ this.oauthType = oauthType
+ this.oauthId = oauthId
+ }
+}
+
+class ChatRoom {
+ _id: ObjectId
+ roomId: number
+ userId: number
+ title: string
+ status: Status = Status.Normal
+ constructor(userId: number, title: string, roomId: number) {
+ this.userId = userId
+ this.title = title
+ this.roomId = roomId
+ }
+}
+
+class ChatOptions {
+ parentMessageId?: string
+ messageId?: string
+ constructor(parentMessageId?: string, messageId?: string) {
+ this.parentMessageId = parentMessageId
+ this.messageId = messageId
+ }
+}
+
+class ChatInfo {
+ _id: ObjectId
+ roomId: number
+ uuid: number
+ dateTime: number
+ prompt: string
+ response?: string
+ status: Status = Status.Normal
+ options: ChatOptions
+ constructor(roomId: number, uuid: number, prompt: string, options: ChatOptions) {
+ this.roomId = roomId
+ this.uuid = uuid
+ this.prompt = prompt
+ this.options = options
+ this.dateTime = new Date().getTime()
+ }
+}
+
+export { UserInfo, UserOauth, ChatRoom, ChatInfo, ChatOptions, Status }
diff --git a/service/src/storage/mongo.ts b/service/src/storage/mongo.ts
new file mode 100644
index 0000000000..fa5762383f
--- /dev/null
+++ b/service/src/storage/mongo.ts
@@ -0,0 +1,143 @@
+import { MongoClient, ObjectId } from 'mongodb'
+import { ChatInfo, ChatRoom, Status, UserInfo } from './model'
+import type { ChatOptions } from './model'
+
+const url = process.env.MONGODB_URL
+const client = new MongoClient(url)
+const chatCol = client.db('chatgpt').collection('chat')
+const roomCol = client.db('chatgpt').collection('chat_room')
+const userCol = client.db('chatgpt').collection('user')
+
+/**
+ * 插入聊天信息
+ * @param text 内容 prompt or response
+ * @param roomId
+ * @param options
+ * @returns model
+ */
+export async function insertChat(uuid: number, text: string, roomId: number, options?: ChatOptions) {
+ const chatInfo = new ChatInfo(roomId, uuid, text, options)
+ await chatCol.insertOne(chatInfo)
+ return chatInfo
+}
+
+export async function getChat(roomId: number, uuid: number) {
+ return await chatCol.findOne({ roomId, uuid })
+}
+
+export async function updateChat(chatId: string, response: string, messageId: string) {
+ const query = { _id: new ObjectId(chatId) }
+ const update = {
+ $set: { 'response': response, 'options.messageId': messageId },
+ }
+ await chatCol.updateOne(query, update)
+}
+
+export async function createChatRoom(userId: ObjectId, title: string, roomId: number) {
+ const room = new ChatRoom(userId, title, roomId)
+ await roomCol.insertOne(room)
+ return room
+}
+export async function renameChatRoom(userId: ObjectId, title: string, roomId: number) {
+ const query = { userId, roomId }
+ const update = {
+ $set: {
+ title,
+ },
+ }
+ const result = await roomCol.updateOne(query, update)
+ return result
+}
+
+export async function deleteChatRoom(userId: ObjectId, roomId: number) {
+ const result = await roomCol.updateOne({ roomId, userId }, { $set: { status: Status.Deleted } })
+ await clearChat(roomId)
+ return result
+}
+
+export async function getChatRooms(userId: ObjectId) {
+ const cursor = await roomCol.find({ userId, status: { $ne: Status.Deleted } })
+ const rooms = []
+ await cursor.forEach(doc => rooms.push(doc))
+ return rooms
+}
+
+export async function existsChatRoom(userId: ObjectId, roomId: number) {
+ const room = await roomCol.findOne({ roomId, userId })
+ return !!room
+}
+
+export async function getChats(roomId: number, lastTime?: number) {
+ if (!lastTime)
+ lastTime = new Date().getTime()
+ const query = { roomId, dateTime: { $lt: lastTime }, status: { $ne: Status.Deleted } }
+ const sort = { dateTime: -1 }
+ const limit = 200
+ const cursor = await chatCol.find(query).sort(sort).limit(limit)
+ const chats = []
+ await cursor.forEach(doc => chats.push(doc))
+ chats.reverse()
+ return chats
+}
+
+export async function clearChat(roomId: number) {
+ const query = { roomId }
+ const update = {
+ $set: {
+ status: Status.Deleted,
+ },
+ }
+ await chatCol.updateMany(query, update)
+}
+
+export async function deleteChat(roomId: number, uuid: number, inversion: boolean) {
+ const query = { roomId, uuid }
+ let update = {
+ $set: {
+ status: Status.Deleted,
+ },
+ }
+ const chat = await chatCol.findOne(query)
+ if (chat.status === Status.InversionDeleted && !inversion) { /* empty */ }
+ else if (chat.status === Status.ResponseDeleted && inversion) { /* empty */ }
+ else if (inversion) {
+ update = {
+ $set: {
+ status: Status.InversionDeleted,
+ },
+ }
+ }
+ else {
+ update = {
+ $set: {
+ status: Status.ResponseDeleted,
+ },
+ }
+ }
+ chatCol.updateOne(query, update)
+}
+
+export async function createUser(email: string, password: string) {
+ email = email.toLowerCase()
+ const userInfo = new UserInfo(email, password)
+ if (email === process.env.ROOT_USER)
+ userInfo.status = Status.Normal
+
+ await userCol.insertOne(userInfo)
+ return userInfo
+}
+
+export async function updateUserName(userId: ObjectId, name: string) {
+ const result = userCol.updateOne({ _id: userId }, { name: { $set: name } })
+ return result
+}
+
+export async function getUser(email: string) {
+ email = email.toLowerCase()
+ return await userCol.findOne({ email })
+}
+
+export async function verifyUser(email: string) {
+ email = email.toLowerCase()
+ return await userCol.updateOne({ email }, { $set: { status: Status.Normal, verifyTime: new Date().toLocaleString() } })
+}
diff --git a/service/src/types.ts b/service/src/types.ts
index 995894cd4c..5eb450729c 100644
--- a/service/src/types.ts
+++ b/service/src/types.ts
@@ -20,6 +20,7 @@ export interface ModelConfig {
timeoutMs?: number
socksProxy?: string
httpsProxy?: string
+ allowRegister?: boolean
}
export type ApiModel = 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI' | undefined
diff --git a/service/src/utils/mail.ts b/service/src/utils/mail.ts
new file mode 100644
index 0000000000..1256fd4fec
--- /dev/null
+++ b/service/src/utils/mail.ts
@@ -0,0 +1,30 @@
+import nodemailer from 'nodemailer'
+
+// create reusable transporter object using SMTP transport
+const transporter = nodemailer.createTransport({
+ host: process.env.SMTP_HOST,
+ port: process.env.SMTP_PORT,
+ secure: process.env.SMTP_TSL,
+ auth: {
+ user: process.env.SMTP_USERNAME,
+ pass: process.env.SMTP_PASSWORD,
+ },
+})
+
+export function sendMail(toMail: string, verifyUrl: string) {
+ const mailOptions = {
+ from: process.env.SMTP_USERNAME, // sender address
+ to: toMail, // list of receivers
+ subject: '账号验证', // Subject line
+ // text: 'Hello world?', // plain text body
+ html: `你正在注册网站,你的邮箱验证链接为(12小时内有效)
点我验证`, // html body
+ }
+
+ // send mail with defined transport object
+ transporter.sendMail(mailOptions, (error, info) => {
+ if (error)
+ throw error
+ else
+ return info.messageId
+ })
+}
diff --git a/service/src/utils/security.ts b/service/src/utils/security.ts
new file mode 100644
index 0000000000..965d97f6cf
--- /dev/null
+++ b/service/src/utils/security.ts
@@ -0,0 +1,32 @@
+import { createHash } from 'crypto'
+
+export function md5(input: string) {
+ input = input + process.env.PASSWORD_MD5_SALT
+ const md5 = createHash('md5')
+ md5.update(input)
+ return md5.digest('hex')
+}
+
+// 可以换 aes 等方式
+export function getUserVerifyUrl(username: string) {
+ const sign = getUserVerify(username)
+ return `${process.env.SITE_DOMAIN}/#/chat/?verifytoken=${sign}`
+}
+
+function getUserVerify(username: string) {
+ const expired = new Date().getTime() + (12 * 60 * 60 * 1000)
+ const sign = `${username}-${expired}`
+ return `${sign}-${md5(sign)}`
+}
+
+export function checkUserVerify(verify: string) {
+ const vs = verify.split('-')
+ const sign = vs[vs.length - 1]
+ const expired = vs[vs.length - 2]
+ vs.splice(vs.length - 2, 2)
+ const username = vs.join('-')
+ // 简单点没校验有效期
+ if (sign === md5(`${username}-${expired}`))
+ return username
+ throw new Error('Verify failed')
+}
diff --git a/src/api/index.ts b/src/api/index.ts
index e576ab1319..5f7e324c6d 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -1,5 +1,5 @@
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
-import { post } from '@/utils/request'
+import { get, post } from '@/utils/request'
export function fetchChatAPI(
prompt: string,
@@ -21,6 +21,9 @@ export function fetchChatConfig() {
export function fetchChatAPIProcess(
params: {
+ roomId: number
+ uuid: number
+ regenerate?: boolean
prompt: string
options?: { conversationId?: string; parentMessageId?: string }
signal?: GenericAbortSignal
@@ -28,7 +31,7 @@ export function fetchChatAPIProcess(
) {
return post({
url: '/chat-process',
- data: { prompt: params.prompt, options: params.options },
+ data: { roomId: params.roomId, uuid: params.uuid, regenerate: params.regenerate || false, prompt: params.prompt, options: params.options },
signal: params.signal,
onDownloadProgress: params.onDownloadProgress,
})
@@ -46,3 +49,64 @@ export function fetchVerify(token: string) {
data: { token },
})
}
+
+export function fetchLogin(username: string, password: string) {
+ return post({
+ url: '/user-login',
+ data: { username, password },
+ })
+}
+
+export function fetchRegister(username: string, password: string) {
+ return post({
+ url: '/user-register',
+ data: { username, password },
+ })
+}
+
+export function fetchGetChatRooms() {
+ return get({
+ url: '/chatrooms',
+ })
+}
+
+export function fetchCreateChatRoom(title: string, roomId: number) {
+ return post({
+ url: '/room-create',
+ data: { title, roomId },
+ })
+}
+
+export function fetchRenameChatRoom(title: string, roomId: number) {
+ return post({
+ url: '/room-rename',
+ data: { title, roomId },
+ })
+}
+
+export function fetchDeleteChatRoom(roomId: number) {
+ return post({
+ url: '/room-delete',
+ data: { roomId },
+ })
+}
+
+export function fetchGetChatHistory(roomId: number) {
+ return get({
+ url: `/chat-hisroty?roomid=${roomId}`,
+ })
+}
+
+export function fetchClearChat(roomId: number) {
+ return post({
+ url: '/chat-clear',
+ data: { roomId },
+ })
+}
+
+export function fetchDeleteChat(roomId: number, uuid: number, inversion?: boolean) {
+ return post({
+ url: '/chat-delete',
+ data: { roomId, uuid, inversion },
+ })
+}
diff --git a/src/components/common/UserAvatar/index.vue b/src/components/common/UserAvatar/index.vue
index 19b18b4ba0..a6973fa026 100644
--- a/src/components/common/UserAvatar/index.vue
+++ b/src/components/common/UserAvatar/index.vue
@@ -26,15 +26,12 @@ const userInfo = computed(() => userStore.userInfo)
-
- {{ userInfo.name ?? 'ChenZhaoYu' }}
+
+ {{ userInfo.name }}
+
+
+ {{ $t('common.notLoggedIn') }}
-
-
-
diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts
index b1b88ba052..3c19d18f9a 100644
--- a/src/locales/en-US.ts
+++ b/src/locales/en-US.ts
@@ -24,7 +24,10 @@ export default {
wrong: 'Something went wrong, please try again later.',
success: 'Success',
failed: 'Failed',
- verify: 'Verify',
+ register: 'Register',
+ login: 'Login',
+ notLoggedIn: 'Login / Register',
+ logOut: 'Login Out',
unauthorizedTips: 'Unauthorized, please verify first.',
},
chat: {
diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
index 813b637e6f..5a91770039 100644
--- a/src/locales/zh-CN.ts
+++ b/src/locales/zh-CN.ts
@@ -24,7 +24,10 @@ export default {
wrong: '好像出错了,请稍后再试。',
success: '操作成功',
failed: '操作失败',
- verify: '验证',
+ register: '注册',
+ login: '登录',
+ notLoggedIn: '登录 / 注册',
+ logOut: '退出登录',
unauthorizedTips: '未经授权,请先进行验证。',
},
chat: {
diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts
index 3ed65df9b1..1073821841 100644
--- a/src/locales/zh-TW.ts
+++ b/src/locales/zh-TW.ts
@@ -24,7 +24,10 @@ export default {
wrong: '發生錯誤,請稍後再試。',
success: '操作成功',
failed: '操作失敗',
- verify: '驗證',
+ register: '註冊',
+ login: '登錄',
+ notLoggedIn: '登錄 / 註冊',
+ logOut: '退出登錄',
unauthorizedTips: '未經授權,請先進行驗證。',
},
chat: {
diff --git a/src/store/modules/auth/index.ts b/src/store/modules/auth/index.ts
index 5b8c8d83e9..904a4313f9 100644
--- a/src/store/modules/auth/index.ts
+++ b/src/store/modules/auth/index.ts
@@ -1,11 +1,12 @@
import { defineStore } from 'pinia'
+import jwt_decode from 'jwt-decode'
import { getToken, removeToken, setToken } from './helper'
-import { store } from '@/store'
+import { store, useUserStore } from '@/store'
import { fetchSession } from '@/api'
export interface AuthState {
token: string | undefined
- session: { auth: boolean } | null
+ session: { auth: boolean; allowRegister: boolean } | null
}
export const useAuthStore = defineStore('auth-store', {
@@ -17,7 +18,7 @@ export const useAuthStore = defineStore('auth-store', {
actions: {
async getSession() {
try {
- const { data } = await fetchSession<{ auth: boolean }>()
+ const { data } = await fetchSession<{ auth: boolean; allowRegister: boolean }>()
this.session = { ...data }
return Promise.resolve(data)
}
@@ -28,11 +29,20 @@ export const useAuthStore = defineStore('auth-store', {
setToken(token: string) {
this.token = token
+ const decoded = jwt_decode(token) as { email: string }
+ const userStore = useUserStore()
+ userStore.updateUserInfo({
+ avatar: '',
+ name: decoded.email,
+ description: '',
+ })
setToken(token)
},
removeToken() {
this.token = undefined
+ const userStore = useUserStore()
+ userStore.resetUserInfo()
removeToken()
},
},
diff --git a/src/store/modules/chat/index.ts b/src/store/modules/chat/index.ts
index 540954de15..9ac4a4175b 100644
--- a/src/store/modules/chat/index.ts
+++ b/src/store/modules/chat/index.ts
@@ -1,6 +1,7 @@
import { defineStore } from 'pinia'
import { getLocalState, setLocalState } from './helper'
import { router } from '@/router'
+import { fetchClearChat, fetchCreateChatRoom, fetchDeleteChat, fetchDeleteChatRoom, fetchGetChatHistory, fetchGetChatRooms, fetchRenameChatRoom } from '@/api'
export const useChatStore = defineStore('chat-store', {
state: (): Chat.ChatState => getLocalState(),
@@ -23,7 +24,31 @@ export const useChatStore = defineStore('chat-store', {
},
actions: {
+ async syncHistory() {
+ const rooms = (await fetchGetChatRooms()).data
+ // if (rooms.length <= 0)
+ // return
+
+ let uuid = null
+ this.history = []
+ this.chat = []
+ await rooms.forEach(async (r: Chat.History) => {
+ this.history.unshift(r)
+ uuid = r.uuid
+ const chatData = (await fetchGetChatHistory(r.uuid)).data
+ this.chat.unshift({ uuid: r.uuid, data: chatData })
+ })
+ if (uuid == null) {
+ uuid = Date.now()
+ this.addHistory({ title: 'New Chat', uuid: Date.now(), isEdit: false })
+ }
+
+ this.active = uuid
+ this.reloadRoute(uuid)
+ },
+
addHistory(history: Chat.History, chatData: Chat.Chat[] = []) {
+ fetchCreateChatRoom(history.title, history.uuid)
this.history.unshift(history)
this.chat.unshift({ uuid: history.uuid, data: chatData })
this.active = history.uuid
@@ -35,10 +60,12 @@ export const useChatStore = defineStore('chat-store', {
if (index !== -1) {
this.history[index] = { ...this.history[index], ...edit }
this.recordState()
+ fetchRenameChatRoom(this.history[index].title, this.history[index].uuid)
}
},
async deleteHistory(index: number) {
+ fetchDeleteChatRoom(this.history[index].uuid)
this.history.splice(index, 1)
this.chat.splice(index, 1)
@@ -91,6 +118,7 @@ export const useChatStore = defineStore('chat-store', {
if (!uuid || uuid === 0) {
if (this.history.length === 0) {
const uuid = Date.now()
+ fetchCreateChatRoom(chat.text, uuid)
this.history.push({ uuid, title: chat.text, isEdit: false })
this.chat.push({ uuid, data: [chat] })
this.active = uuid
@@ -98,8 +126,10 @@ export const useChatStore = defineStore('chat-store', {
}
else {
this.chat[0].data.push(chat)
- if (this.history[0].title === 'New Chat')
+ if (this.history[0].title === 'New Chat') {
this.history[0].title = chat.text
+ fetchRenameChatRoom(chat.text, this.history[0].uuid)
+ }
this.recordState()
}
}
@@ -107,8 +137,10 @@ export const useChatStore = defineStore('chat-store', {
const index = this.chat.findIndex(item => item.uuid === uuid)
if (index !== -1) {
this.chat[index].data.push(chat)
- if (this.history[index].title === 'New Chat')
+ if (this.history[index].title === 'New Chat') {
this.history[index].title = chat.text
+ fetchRenameChatRoom(chat.text, this.history[index].uuid)
+ }
this.recordState()
}
},
@@ -116,6 +148,7 @@ export const useChatStore = defineStore('chat-store', {
updateChatByUuid(uuid: number, index: number, chat: Chat.Chat) {
if (!uuid || uuid === 0) {
if (this.chat.length) {
+ chat.uuid = this.chat[0].data[index].uuid
this.chat[0].data[index] = chat
this.recordState()
}
@@ -124,6 +157,7 @@ export const useChatStore = defineStore('chat-store', {
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
if (chatIndex !== -1) {
+ chat.uuid = this.chat[chatIndex].data[index].uuid
this.chat[chatIndex].data[index] = chat
this.recordState()
}
@@ -132,6 +166,7 @@ export const useChatStore = defineStore('chat-store', {
updateChatSomeByUuid(uuid: number, index: number, chat: Partial) {
if (!uuid || uuid === 0) {
if (this.chat.length) {
+ chat.uuid = this.chat[0].data[index].uuid
this.chat[0].data[index] = { ...this.chat[0].data[index], ...chat }
this.recordState()
}
@@ -140,6 +175,7 @@ export const useChatStore = defineStore('chat-store', {
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
if (chatIndex !== -1) {
+ chat.uuid = this.chat[chatIndex].data[index].uuid
this.chat[chatIndex].data[index] = { ...this.chat[chatIndex].data[index], ...chat }
this.recordState()
}
@@ -148,6 +184,7 @@ export const useChatStore = defineStore('chat-store', {
deleteChatByUuid(uuid: number, index: number) {
if (!uuid || uuid === 0) {
if (this.chat.length) {
+ fetchDeleteChat(uuid, this.chat[0].data[index].uuid || 0, this.chat[0].data[index].inversion)
this.chat[0].data.splice(index, 1)
this.recordState()
}
@@ -156,6 +193,7 @@ export const useChatStore = defineStore('chat-store', {
const chatIndex = this.chat.findIndex(item => item.uuid === uuid)
if (chatIndex !== -1) {
+ fetchDeleteChat(uuid, this.chat[chatIndex].data[index].uuid || 0, this.chat[0].data[index].inversion)
this.chat[chatIndex].data.splice(index, 1)
this.recordState()
}
@@ -164,6 +202,7 @@ export const useChatStore = defineStore('chat-store', {
clearChatByUuid(uuid: number) {
if (!uuid || uuid === 0) {
if (this.chat.length) {
+ fetchClearChat(this.chat[0].uuid)
this.chat[0].data = []
this.recordState()
}
@@ -172,6 +211,7 @@ export const useChatStore = defineStore('chat-store', {
const index = this.chat.findIndex(item => item.uuid === uuid)
if (index !== -1) {
+ fetchClearChat(uuid)
this.chat[index].data = []
this.recordState()
}
diff --git a/src/store/modules/user/helper.ts b/src/store/modules/user/helper.ts
index cf0f04f1dd..8791fd74ed 100644
--- a/src/store/modules/user/helper.ts
+++ b/src/store/modules/user/helper.ts
@@ -15,9 +15,9 @@ export interface UserState {
export function defaultSetting(): UserState {
return {
userInfo: {
- avatar: 'https://raw.githubusercontent.com/Chanzhaoyu/chatgpt-web/main/src/assets/avatar.jpg',
- name: 'ChenZhaoYu',
- description: 'Star on Github',
+ avatar: '',
+ name: '',
+ description: '',
},
}
}
diff --git a/src/typings/chat.d.ts b/src/typings/chat.d.ts
index c0b80c9632..8b2e32f941 100644
--- a/src/typings/chat.d.ts
+++ b/src/typings/chat.d.ts
@@ -1,6 +1,7 @@
declare namespace Chat {
interface Chat {
+ uuid?: number
dateTime: string
text: string
inversion?: boolean
diff --git a/src/views/chat/index.vue b/src/views/chat/index.vue
index 9d1e1c691a..c24c0bf104 100644
--- a/src/views/chat/index.vue
+++ b/src/views/chat/index.vue
@@ -61,9 +61,11 @@ async function onConversation() {
controller = new AbortController()
+ const chatUuid = Date.now()
addChat(
+uuid,
{
+ uuid: chatUuid,
dateTime: new Date().toLocaleString(),
text: message,
inversion: true,
@@ -86,6 +88,7 @@ async function onConversation() {
addChat(
+uuid,
{
+ uuid: chatUuid,
dateTime: new Date().toLocaleString(),
text: '',
loading: true,
@@ -101,6 +104,8 @@ async function onConversation() {
let lastText = ''
const fetchChatAPIOnce = async () => {
await fetchChatAPIProcess({
+ roomId: +uuid,
+ uuid: chatUuid,
prompt: message,
options,
signal: controller.signal,
@@ -212,7 +217,7 @@ async function onRegenerate(index: number) {
options = { ...requestOptions.options }
loading.value = true
-
+ const chatUuid = dataSources.value[index].uuid
updateChat(
+uuid,
index,
@@ -231,6 +236,9 @@ async function onRegenerate(index: number) {
let lastText = ''
const fetchChatAPIOnce = async () => {
await fetchChatAPIProcess({
+ roomId: +uuid,
+ uuid: chatUuid || Date.now(),
+ regenerate: true,
prompt: message,
options,
signal: controller.signal,
diff --git a/src/views/chat/layout/Permission.vue b/src/views/chat/layout/Permission.vue
index 9e9b26e74e..3fcaa22154 100644
--- a/src/views/chat/layout/Permission.vue
+++ b/src/views/chat/layout/Permission.vue
@@ -1,7 +1,8 @@
@@ -64,15 +114,38 @@ function handlePress(event: KeyboardEvent) {
-
+
+
+
+
+
+ {{ $t('common.register') }}
+
+
+ {{ $t('common.login') }}
+
+
- {{ $t('common.verify') }}
+ {{ $t('common.login') }}
diff --git a/src/views/chat/layout/sider/Footer.vue b/src/views/chat/layout/sider/Footer.vue
index 583754775c..70a6df1950 100644
--- a/src/views/chat/layout/sider/Footer.vue
+++ b/src/views/chat/layout/sider/Footer.vue
@@ -1,10 +1,19 @@
@@ -12,6 +21,12 @@ const show = ref(false)
+
+
+
+
+
+
diff --git a/src/views/chat/layout/sider/List.vue b/src/views/chat/layout/sider/List.vue
index 2a282e0a1d..796bb461a8 100644
--- a/src/views/chat/layout/sider/List.vue
+++ b/src/views/chat/layout/sider/List.vue
@@ -1,17 +1,30 @@
+
+
+
+
+
+
+
{{ $t('setting.smtpHost') }}
+
+ { if (config) config.smtpHost = val }" />
+
+
+
+
{{ $t('setting.smtpPort') }}
+
+ { if (config) config.smtpPort = typeof val === 'string' ? Number(val) : undefined }" />
+
+
+
+
{{ $t('setting.smtpTsl') }}
+
+ { if (config) config.smtpTsl = typeof val === 'string' ? Boolean(val) : undefined }" />
+
+
+
+
{{ $t('setting.smtpUserName') }}
+
+ { if (config) config.smtpUserName = val }" />
+
+
+
+
{{ $t('setting.smtpPassword') }}
+
+ { if (config) config.smtpPassword = val }" />
+
+
+
+
+
+ {{ $t('common.save') }}
+
+
+
+
+
+
diff --git a/src/components/common/Setting/Site.Vue b/src/components/common/Setting/Site.Vue
new file mode 100644
index 0000000000..c298aee4e3
--- /dev/null
+++ b/src/components/common/Setting/Site.Vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
{{ $t('setting.siteTitle') }}
+
+ { if (config) config.siteTitle = val }" />
+
+
+
+
{{ $t('setting.siteDomain') }}
+
+ { if (config) config.siteDomain = val }" />
+
+
+
+
{{ $t('setting.registerEnabled') }}
+
+ { if (config) config.registerEnabled = typeof val === 'string' ? Boolean(val) : undefined }" />
+
+
+
+
{{ $t('setting.registerMails') }}
+
+ { if (config) config.registerMails = val }" />
+
+
+
+
+
+ {{ $t('common.save') }}
+
+
+
+
+
+
diff --git a/src/components/common/Setting/index.vue b/src/components/common/Setting/index.vue
index f0dcefaa99..c5c21c032f 100644
--- a/src/components/common/Setting/index.vue
+++ b/src/components/common/Setting/index.vue
@@ -3,6 +3,8 @@ import { computed, ref } from 'vue'
import { NModal, NTabPane, NTabs } from 'naive-ui'
import General from './General.vue'
import About from './About.vue'
+import Site from './Site.vue'
+import Mail from './Mail.vue'
import { SvgIcon } from '@/components/common'
import { useUserStore } from '@/store'
@@ -52,6 +54,20 @@ const show = computed({
+
+
+
+ {{ $t('setting.siteConfig') }}
+
+
+
+
+
+
+ {{ $t('setting.mailConfig') }}
+
+
+
diff --git a/src/components/common/Setting/model.ts b/src/components/common/Setting/model.ts
new file mode 100644
index 0000000000..444d73bb6a
--- /dev/null
+++ b/src/components/common/Setting/model.ts
@@ -0,0 +1,28 @@
+export class ConfigState {
+ timeoutMs?: number
+ apiKey?: string
+ accessToken?: string
+ apiBaseUrl?: string
+ apiModel?: string
+ reverseProxy?: string
+ socksProxy?: string
+ httpsProxy?: string
+ balance?: number
+ siteConfig?: SiteConfig
+ mailConfig?: MailConfig
+}
+
+export class SiteConfig {
+ siteTitle?: string
+ registerEnabled?: boolean
+ registerMails?: string
+ siteDomain?: string
+}
+
+export class MailConfig {
+ smtpHost?: string
+ smtpPort?: number
+ smtpTsl?: boolean
+ smtpUserName?: string
+ smtpPassword?: string
+}
diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts
index 7444804ada..bff87980cc 100644
--- a/src/locales/en-US.ts
+++ b/src/locales/en-US.ts
@@ -52,7 +52,9 @@ export default {
setting: {
setting: 'Setting',
general: 'General',
- config: 'Config',
+ config: 'Base Config',
+ siteConfig: 'Site Config',
+ mailConfig: 'Mail Config',
avatarLink: 'Avatar Link',
name: 'Name',
description: 'Description',
@@ -62,10 +64,22 @@ export default {
language: 'Language',
api: 'API',
reverseProxy: 'Reverse Proxy',
- timeout: 'Timeout',
+ timeout: 'Timeout(ms)',
socks: 'Socks',
httpsProxy: 'HTTPS Proxy',
balance: 'API Balance',
+ smtpHost: 'Host',
+ smtpPort: 'Port',
+ smtpTsl: 'Tsl',
+ smtpUserName: 'UserName',
+ smtpPassword: 'Password',
+ siteTitle: 'Title',
+ siteDomain: 'Domain',
+ registerEnabled: 'Register Enabled',
+ registerMails: 'Register Mails',
+ apiBaseUrl: 'Api Base Url',
+ apiModel: 'Api Model',
+ accessToken: 'Access Token',
},
store: {
local: 'Local',
diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
index ee51bbb361..9825ea02b7 100644
--- a/src/locales/zh-CN.ts
+++ b/src/locales/zh-CN.ts
@@ -52,7 +52,9 @@ export default {
setting: {
setting: '设置',
general: '总览',
- config: '配置',
+ config: '基本配置',
+ siteConfig: '网站配置',
+ mailConfig: '邮箱配置',
avatarLink: '头像链接',
name: '名称',
description: '描述',
@@ -62,10 +64,22 @@ export default {
language: '语言',
api: 'API',
reverseProxy: '反向代理',
- timeout: '超时',
+ timeout: '超时(ms)',
socks: 'Socks',
httpsProxy: 'HTTPS Proxy',
balance: 'API余额',
+ smtpHost: 'Host',
+ smtpPort: 'Port',
+ smtpTsl: 'Tsl',
+ smtpUserName: '账号',
+ smtpPassword: '密码/专用密码',
+ siteTitle: '网站标题',
+ siteDomain: '域名 不含/',
+ registerEnabled: '新用户',
+ registerMails: '邮箱后缀',
+ apiBaseUrl: '接口地址',
+ apiModel: 'Api 模型',
+ accessToken: 'Access Token',
},
store: {
local: '本地',
diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts
index ef4ca8d4aa..4c293cdff2 100644
--- a/src/locales/zh-TW.ts
+++ b/src/locales/zh-TW.ts
@@ -52,7 +52,9 @@ export default {
setting: {
setting: '設定',
general: '總覽',
- config: '設定',
+ config: '基本設定',
+ siteConfig: '网站配置',
+ mailConfig: '邮箱配置',
avatarLink: '頭貼連結',
name: '名稱',
description: '描述',
@@ -62,10 +64,22 @@ export default {
language: '語言',
api: 'API',
reverseProxy: '反向代理',
- timeout: '逾時',
+ timeout: '逾時(ms)',
socks: 'Socks',
httpsProxy: 'HTTPS Proxy',
balance: 'API余額',
+ smtpHost: 'Host',
+ smtpPort: 'Port',
+ smtpTsl: 'Tsl',
+ smtpUserName: '账号',
+ smtpPassword: '密码/专用密码',
+ siteTitle: '网站标题',
+ siteDomain: '域名 不含/',
+ registerEnabled: '新用户',
+ registerMails: '邮箱后缀',
+ apiBaseUrl: '接口地址',
+ apiModel: 'Api 模型',
+ accessToken: 'Access Token',
},
store: {
local: '本機',
diff --git a/src/store/modules/chat/index.ts b/src/store/modules/chat/index.ts
index db62635ac5..4e7ac2798e 100644
--- a/src/store/modules/chat/index.ts
+++ b/src/store/modules/chat/index.ts
@@ -29,6 +29,9 @@ export const useChatStore = defineStore('chat-store', {
let uuid = this.active
this.history = []
this.chat = []
+ if (rooms.findIndex((item: { uuid: number | null }) => item.uuid === uuid) <= -1 && rooms.length > 0)
+ uuid = null
+
for (const r of rooms) {
this.history.unshift(r)
if (uuid == null)
From 4670df4614d8f134c94d733ffdcb145111ac79e9 Mon Sep 17 00:00:00 2001
From: Kerwin
Date: Tue, 21 Mar 2023 23:15:09 +0800
Subject: [PATCH 011/147] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0readme?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.en.md | 2 ++
README.md | 2 ++
docs/basesettings.jpg | Bin 0 -> 199594 bytes
docs/login.jpg | Bin 0 -> 150583 bytes
docs/mailsettings.jpg | Bin 0 -> 194224 bytes
docs/sitesettings.jpg | Bin 0 -> 208663 bytes
6 files changed, 4 insertions(+)
create mode 100644 docs/basesettings.jpg
create mode 100644 docs/login.jpg
create mode 100644 docs/mailsettings.jpg
create mode 100644 docs/sitesettings.jpg
diff --git a/README.en.md b/README.en.md
index e3f72fe5d3..0dc1b0aeea 100644
--- a/README.en.md
+++ b/README.en.md
@@ -13,8 +13,10 @@
## Screenshots
> Disclaimer: This project is only released on GitHub, under the MIT License, free and for open-source learning purposes. There will be no account selling, paid services, discussion groups, or forums. Beware of fraud.
+![cover3](./docs/login.jpg)
![cover](./docs/c1.png)
![cover2](./docs/c2.png)
+![cover3](./docs/basesettings.jpg)
- [ChatGPT Web](#chatgpt-web)
- [Introduction](#introduction)
diff --git a/README.md b/README.md
index 37ca6e8ba9..a36c514b92 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,10 @@
## 截图
> 声明:此项目只发布于 Github,基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号、付费服务、讨论群、讨论组等行为。谨防受骗。
+![cover3](./docs/login.jpg)
![cover](./docs/c1.png)
![cover2](./docs/c2.png)
+![cover3](./docs/basesettings.jpg)
- [ChatGPT Web](#chatgpt-web)
- [介绍](#介绍)
diff --git a/docs/basesettings.jpg b/docs/basesettings.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..75a9a2f79d47d744fff61cca40809f4dc717ccba
GIT binary patch
literal 199594
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