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 @@