diff --git a/.github/workflows/frontend-bundle-analysis.yml b/.github/workflows/frontend-bundle-analysis.yml index d1378ef0..76437eae 100644 --- a/.github/workflows/frontend-bundle-analysis.yml +++ b/.github/workflows/frontend-bundle-analysis.yml @@ -12,7 +12,12 @@ defaults: jobs: analyze: + name: Frontend Analyze Bundle runs-on: ubuntu-latest + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@0.12.0 @@ -21,6 +26,14 @@ jobs: - uses: actions/checkout@v4 + - name: Cache turbo build setup + uses: actions/cache@v3 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + - uses: pnpm/action-setup@v2.4.0 - name: Install Node.js @@ -30,7 +43,7 @@ jobs: cache: 'pnpm' - name: Install Dependencies - run: pnpm i + run: pnpm install - name: Restore Next.js Build uses: actions/cache@v3 @@ -46,7 +59,7 @@ jobs: - name: Build Next.js App # change this if your site requires a custom build command - run: cd packages/frontend && ./node_modules/.bin/next build + run: pnpm run build:frontend # Here's the first place where next-bundle-analysis' own script is used # This step pulls the raw bundle stats for the current bundle diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml new file mode 100644 index 00000000..db0ff76a --- /dev/null +++ b/.github/workflows/test-suite.yml @@ -0,0 +1,50 @@ +name: Test Suite + +on: + push: + branches: ["v3","v3-dev"] + pull_request: + types: [opened, synchronize] + +jobs: + test: + name: Build and Test + timeout-minutes: 15 + runs-on: ubuntu-latest + # To use Remote Caching, uncomment the next lines and follow the steps below. + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + + steps: + - name: Check out code + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - name: Cache turbo build setup + uses: actions/cache@v3 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + + - uses: pnpm/action-setup@v2.0.1 + with: + version: 6.32.2 + + - name: Setup Node.js environment + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Build + run: pnpm build + + - name: Test + run: pnpm test diff --git a/README.md b/README.md index 8912941f..4e3a9f85 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ English GitHub closed issues
- +
CAW 是一个自托管网络应用程序,提供开箱即用的用户管理,包括后台界面以及可配置的支付计划和相关支付界面。
@@ -35,7 +35,7 @@ English | | | |---------------------------------------------------------------------------|---------------------------------------------------------------------------| -| | | +| | | | | | ## 开始 @@ -50,6 +50,18 @@ English | [V2](https://github.com/AprilNEA/ChatGPT-Admin-Web/tree/v2) | 弃用 | 存在设计缺陷 | | [V1](https://github.com/AprilNEA/ChatGPT-Admin-Web/tree/main) | 不再更新 | Redis数据库,可拓展性较差 | +[更新日志](https://manual.sku.moe/project/chatgpt-admin-web/update-log) + +## 技术栈 + +| Part | Tech | +|:---------|:-------------------| +| Frontend | Next.js | +| Backend | Nest.js | +| Database | PostgreSQL & Redis | + +更多技术细节:[开发手册](https://manual.sku.moe/project/chatgpt-admin-web/development)。 + ## 项目动态 ![Alt](https://repobeats.axiom.co/api/embed/67fc3464887e0956a6225b4c5c6579c2699d8363.svg "Repobeats analytics image") @@ -62,7 +74,6 @@ English - ## 捐赠 感谢您的激励,能让该项目持续发展。 diff --git a/README_EN.md b/README_EN.md index 0e7f3775..b1ef1cbe 100644 --- a/README_EN.md +++ b/README_EN.md @@ -15,7 +15,7 @@ GitHub closed issues
- +
CAW(ChatGPT-Admin-Web) is a self-hosted web application that provides out-of-the-box user management including a back-end interface as well as configurable payment plans and related payment interfaces.
@@ -51,6 +51,19 @@ For configuration and installation, see [documentation](https://manual.sku.moe/p | [V2](https://github.com/AprilNEA/ChatGPT-Admin-Web/tree/v2) | :x: Deprecated | Flawed design | | [V1](https://github.com/AprilNEA/ChatGPT-Admin-Web/tree/main) | :o: No longer updated | Redis database, less scalable | +[Update Log](https://manual.sku.moe/project/chatgpt-admin-web/update-log) + + +## 技术栈 + +| Part | Tech | +|:---------|:-------------------| +| Frontend | Next.js | +| Backend | Nest.js | +| Database | PostgreSQL & Redis | + +You can go to [Developer Manual](https://manual.sku.moe/project/chatgpt-admin-web/development) for more technical details. + ## Project Status ![Alt](https://repobeats.axiom.co/api/embed/67fc3464887e0956a6225b4c5c6579c2699d8363.svg "Repobeats analytics image") diff --git a/package.json b/package.json index a3085c8e..8cc96b22 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ ], "scripts": { "build": "turbo run build", + "build:frontend": "turbo run build --filter frontend", "dev": "turbo run dev --parallel", "start": "turbo run start", "lint": "turbo run lint", + "test": "vitest run", "prettier": "prettier --write .", "db:init": "prisma db push && prisma db seed", "db:generate": "prisma generate" @@ -26,10 +28,14 @@ "@trivago/prettier-plugin-sort-imports": "latest", "@types/node": "latest", "prettier": "latest", + "prisma": "5.6.0", "ts-node": "latest", "turbo": "latest", "typescript": "latest", - "prisma": "5.6.0" + "vite": "^5.0.7", + "vite-tsconfig-paths": "^4.2.2", + "vitest": "^1.0.2", + "zod": "latest" }, "prisma": { "schema": "prisma/schema.prisma", diff --git a/packages/backend/package.json b/packages/backend/package.json index b06589de..99de9c29 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -10,12 +10,7 @@ "dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", - "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "test": "jest", - "test:watch": "jest --watch", - "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config test/jest-e2e.json" + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix" }, "dependencies": { "@liaoliaots/nestjs-redis": "^9.0.5", @@ -38,7 +33,8 @@ "prisma-extension-pagination": "^0.5.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", - "spark-md5": "^3.0.2" + "spark-md5": "^3.0.2", + "zod": "*" }, "devDependencies": { "@nestjs/cli": "^10.2.1", diff --git a/packages/backend/src/common/config/config.service.ts b/packages/backend/src/common/config/config.service.ts index 3fd0f4cb..d40ac619 100644 --- a/packages/backend/src/common/config/config.service.ts +++ b/packages/backend/src/common/config/config.service.ts @@ -101,4 +101,26 @@ export class ConfigService { fs.writeFileSync(this.configFilePath, jsonData, 'utf8'); this.loadConfig(); } + + checkEmailEnable() { + return ( + this.get('email') && + this.get('email').use && + this.get('email').use !== 'disable' + ); + } + + checkSMSEnable() { + return ( + this.get('sms') && + this.get('sms').use && + this.get('sms').use !== 'disable' + ); + } + + checkNotifierEnable(all = false) { + return all + ? this.checkEmailEnable() && this.checkSMSEnable() + : this.checkEmailEnable() || this.checkSMSEnable(); + } } diff --git a/packages/backend/src/common/exceptions/biz.exception.ts b/packages/backend/src/common/exceptions/biz.exception.ts index b47b2105..bba89036 100644 --- a/packages/backend/src/common/exceptions/biz.exception.ts +++ b/packages/backend/src/common/exceptions/biz.exception.ts @@ -92,11 +92,15 @@ export const ErrorCode = Object.freeze< }); export class BizException extends HttpException { + code: ErrorCodeEnum; + constructor(code: ErrorCodeEnum) { const [message, chMessage, statusCode] = ErrorCode[code]; super( HttpException.createBody({ success: false, code, message, chMessage }), statusCode, ); + + this.code = code; } } diff --git a/packages/backend/src/common/pipes/zod.ts b/packages/backend/src/common/pipes/zod.ts new file mode 100644 index 00000000..7e0a74c6 --- /dev/null +++ b/packages/backend/src/common/pipes/zod.ts @@ -0,0 +1,19 @@ +import { ZodObject } from 'zod'; + +import { ArgumentMetadata, PipeTransform } from '@nestjs/common'; + +import { BizException } from '@/common/exceptions/biz.exception'; + +import { ErrorCodeEnum } from 'shared'; + +export class ZodValidationPipe implements PipeTransform { + constructor(private schema: ZodObject) {} + + transform(value: unknown, metadata: ArgumentMetadata) { + try { + return this.schema.parse(value); + } catch (error) { + throw new BizException(ErrorCodeEnum.ValidationError); + } + } +} diff --git a/packages/backend/src/modules/auth/auth.controller.ts b/packages/backend/src/modules/auth/auth.controller.ts index ddd2797e..e7230679 100644 --- a/packages/backend/src/modules/auth/auth.controller.ts +++ b/packages/backend/src/modules/auth/auth.controller.ts @@ -1,30 +1,14 @@ -import { - Body, - Controller, - Get, - Post, - Put, - Query, - UsePipes, -} from '@nestjs/common'; +import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common'; import { Role } from '@prisma/client'; +import { ConfigService } from '@/common/config'; +import { BizException } from '@/common/exceptions/biz.exception'; import { Payload, Public } from '@/common/guards/auth.guard'; -import { JoiValidationPipe } from '@/common/pipes/joi'; +import { ZodValidationPipe } from '@/common/pipes/zod'; import { WechatService } from '@/modules/auth/wechat.service'; -import { forgetPasswordDto, identityDto, validateCodeDto } from 'shared'; +import { AuthDTO, ErrorCodeEnum } from 'shared'; -import { - ForgetPasswordSchema, - InitAdminSchema, - InitPasswordSchema, - PasswordLoginSchema, - PasswordSchema, - RequireCodeSchema, - ValidateCodeSchema, - bindIdentitySchema, -} from './auth.dto'; import { AuthService } from './auth.service'; @Controller('auth') @@ -32,27 +16,50 @@ export class AuthController { constructor( private readonly authService: AuthService, private wechatService: WechatService, + private configService: ConfigService, ) {} @Public() - @UsePipes(new JoiValidationPipe(InitAdminSchema)) @Post('admin/setup') - async initAdmin(@Body() data: { identity: string; password: string }) { - await this.authService.initAdmin(data.identity, data.password); + async initAdmin( + @Body(new ZodValidationPipe(AuthDTO.InitAdminSchema)) + body: AuthDTO.InitAdminDto, + ) { + await this.authService.initAdmin(body.identity, body.password); return { success: true, }; } - /* 方法一:密码登录 */ + /* 方法一:密码登录 + * WARN: 当邮箱和短信通知均为开启时,用户可直接通过该接口注册 + * WARN: When both email and SMS notification are enabled, + * users can register directly through this interface. + */ @Public() - @UsePipes(new JoiValidationPipe(PasswordLoginSchema)) @Post('password') - async password(@Body() data: { identity: string; password: string }) { - return { - success: true, - ...(await this.authService.loginPassword(data)), - }; + async password( + @Body(new ZodValidationPipe(AuthDTO.PasswordLoginSchema)) + body: AuthDTO.PasswordLoginDto, + ) { + try { + return { + success: true, + ...(await this.authService.loginPassword(body)), + }; + } catch (e) { + if ( + e instanceof BizException && + e.code === ErrorCodeEnum.UserNotExist && + this.configService.checkNotifierEnable(false) === false // It won't hold as long as one is enabled + ) { + return { + success: true, + ...(await this.authService.registerPassword(body)), + }; + } + throw e; + } } /* 方法二:验证码登录/注册 */ @@ -60,19 +67,22 @@ export class AuthController { @Public() @Get('validateCode') async newValidateCode( - @Query(new JoiValidationPipe(RequireCodeSchema)) query: identityDto, + @Query(new ZodValidationPipe(AuthDTO.RequireCodeSchema)) + body: AuthDTO.RequireCodeDto, ) { - return await this.authService.newValidateCode(query.identity); + return await this.authService.newValidateCode(body.identity); } /** 2.通过验证码登录/注册,自动识别邮箱或手机号 */ @Public() - @UsePipes(new JoiValidationPipe(ValidateCodeSchema)) @Post('validateCode') - async loginByCode(@Body() data: validateCodeDto) { + async loginByCode( + @Body(new ZodValidationPipe(AuthDTO.ValidateCodeSchema)) + data: AuthDTO.ValidateCodeDto, + ) { return { success: true, - ...(await this.authService.WithValidateCode(data.identity, data.code)), + ...(await this.authService.withValidateCode(data.identity, data.code)), }; } @@ -100,8 +110,10 @@ export class AuthController { /* 忘记密码 */ @Public() @Post('forgetPassword') - @UsePipes(new JoiValidationPipe(ForgetPasswordSchema)) - async forgetPassword(@Body() data: forgetPasswordDto) { + async forgetPassword( + @Body(new ZodValidationPipe(AuthDTO.ForgetPasswordSchema)) + data: AuthDTO.ForgetPasswordDto, + ) { await this.authService.forgetPassword( data.identity, data.code, @@ -116,9 +128,13 @@ export class AuthController { @Put('changePassword') async changePassword( @Payload('id') userId: number, - @Body('password', new JoiValidationPipe(PasswordSchema)) password: string, + @Body(new ZodValidationPipe(AuthDTO.ChangePasswordSchema)) + data: AuthDTO.ChangePasswordDto, ) { - await this.authService.changePassword(userId, password); + await this.authService.changePassword({ + userId, + ...data, + }); return { success: true, }; @@ -127,8 +143,8 @@ export class AuthController { @Put('initUsername') async initName( @Payload('id') userId: number, - @Body(new JoiValidationPipe(InitPasswordSchema)) - body: { username: string }, + @Body(new ZodValidationPipe(AuthDTO.InitUsernameSchema)) + body: AuthDTO.InitUsernameDto, ) { await this.authService.initUsername(userId, body.username); return { @@ -140,8 +156,8 @@ export class AuthController { @Put('initPassword') async initPassword( @Payload('id') userId: number, - @Body(new JoiValidationPipe(InitPasswordSchema)) - body: { password: string }, + @Body(new ZodValidationPipe(AuthDTO.InitPasswordSchema)) + body: AuthDTO.InitPasswordDto, ) { await this.authService.initPassword(userId, body.password); return { @@ -151,10 +167,10 @@ export class AuthController { /* 绑定账户(邮箱或者密码) */ @Put('bindIdentity') - @UsePipes(new JoiValidationPipe(bindIdentitySchema)) async initIdentity( @Payload('id') userId: number, - @Body() data: { identity: string }, + @Body(new ZodValidationPipe(AuthDTO.BindIdentitySchema)) + data: AuthDTO.BindIdentityDto, ) { await this.authService.bindIdentity(userId, data.identity, true); return { diff --git a/packages/backend/src/modules/auth/auth.dto.ts b/packages/backend/src/modules/auth/auth.dto.ts deleted file mode 100644 index ce9e5d5a..00000000 --- a/packages/backend/src/modules/auth/auth.dto.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as Joi from 'joi'; - -const email = Joi.string().email(); -const phone = Joi.string().pattern(/^[0-9]{11}$/); - -export const IdentitySchema = Joi.alternatives().try(email, phone).required(); -export const PasswordSchema = Joi.string().min(8).max(20).required(); - -export const RequireCodeSchema = Joi.object({ - identity: IdentitySchema, -}); - -export const ValidateCodeSchema = Joi.object({ - identity: IdentitySchema, - code: Joi.string() - .pattern(/^[0-9]{6}$/) - .required(), -}); - -export const InitPasswordSchema = Joi.object({ - password: PasswordSchema.required(), -}); - -export const ForgetPasswordSchema = Joi.object({ - identity: Joi.alternatives().try(email, phone).required(), - code: Joi.string() - .pattern(/^[0-9]{6}$/) - .required(), - newPassword: PasswordSchema.required(), -}); - -export const PasswordLoginSchema = Joi.object({ - identity: IdentitySchema, - password: PasswordSchema.required(), -}); - -export const bindIdentitySchema = Joi.object({ - identity: IdentitySchema, -}); - -export const InitAdminSchema = Joi.object({ - identity: IdentitySchema, - password: PasswordSchema.required(), -}); diff --git a/packages/backend/src/modules/auth/auth.service.ts b/packages/backend/src/modules/auth/auth.service.ts index d9d9691b..742febaa 100644 --- a/packages/backend/src/modules/auth/auth.service.ts +++ b/packages/backend/src/modules/auth/auth.service.ts @@ -69,7 +69,7 @@ export class AuthService { let status: IAccountStatus = 'ok'; if (!user.email && !user.phone) { status = 'bind'; - } else if (!user.password) { + } else if (!user.newPassword) { status = 'password'; } return { @@ -88,6 +88,38 @@ export class AuthService { } } + async #register({ + identity, + password, + }: { + identity: string; + password?: string; + }) { + const { email, phone } = getPhoneOrEmail(identity); + + const existUser = await this.prisma.client.user.findMany({ + where: { + OR: [{ email }, { phone }], + }, + }); + + let user; + if (existUser.length != 1) { + // 注册用户 + user = await this.prisma.client.user.create({ + data: { + email: email, + phone: phone, + role: Role.User, + password: password ? hashSync(password, SALT_ROUNDS) : undefined, + }, + }); + } else { + user = existUser[0]; + } + return this.#signWithCheck(user); + } + /* 添加验证码 */ async newValidateCode(identity: string) { const { email, phone } = getPhoneOrEmail(identity); @@ -127,32 +159,18 @@ export class AuthService { } } + /* Only used when email and sms service both disabled */ + registerPassword(data: ByPassword) { + return this.#register(data); + } + /* 通过验证码登录/注册 */ - async WithValidateCode(identity: string, code: string) { + async withValidateCode(identity: string, code: string) { const { email, phone } = getPhoneOrEmail(identity); await this.#verifyCode(identity, code); - const existUser = await this.prisma.client.user.findMany({ - where: { - OR: [{ email }, { phone }], - }, - }); - - let user; - if (existUser.length != 1) { - // 注册用户 - user = await this.prisma.client.user.create({ - data: { - email: email, - phone: phone, - role: Role.User, - }, - }); - } else { - user = existUser[0]; - } - return this.#signWithCheck(user); + return this.#register({ identity }); } /* 通过密码登录 */ @@ -165,11 +183,11 @@ export class AuthService { }, }); if (user.length != 1) { - throw Error('User does not exist'); + throw new BizException(ErrorCodeEnum.UserNotExist); } const isPasswordCorrect = await compare(password, user[0].password); if (!isPasswordCorrect) { - throw Error('Password is incorrect'); + throw new BizException(ErrorCodeEnum.PasswordError); } return this.#signWithCheck(user[0]); } @@ -215,14 +233,35 @@ export class AuthService { } } - /* 修改密码 */ - async changePassword(userId: number, password: string) { + /* Utils to change user password + * old password will be checked when old password is passed + */ + async changePassword({ + userId, + newPassword, + oldPassword, + }: { + userId: number; + newPassword: string; + oldPassword?: string; + }) { + if (oldPassword) { + const user = await this.prisma.client.user.findUniqueOrThrow({ + where: { + id: userId, + }, + }); + const isPasswordCorrect = await compare(oldPassword, user.password); + if (!isPasswordCorrect) { + throw new BizException(ErrorCodeEnum.PasswordError); + } + } await this.prisma.client.user.update({ where: { id: userId, }, data: { - password: hashSync(password, SALT_ROUNDS), + password: hashSync(newPassword, SALT_ROUNDS), }, }); } @@ -245,7 +284,7 @@ export class AuthService { } else { user = existUser[0]; } - await this.changePassword(user.id, password); + await this.changePassword({ userId: user.id, newPassword: password }); return this.#signWithCheck(user); } diff --git a/packages/backend/src/modules/auth/wechat.service.ts b/packages/backend/src/modules/auth/wechat.service.ts index 7d342837..6f23a9f3 100644 --- a/packages/backend/src/modules/auth/wechat.service.ts +++ b/packages/backend/src/modules/auth/wechat.service.ts @@ -7,7 +7,7 @@ import { ConfigService } from '@/common/config'; import { JwtService } from '@/libs/jwt/jwt.service'; import { ExtendedPrismaClient } from '@/processors/database/prisma.extension'; -import { ConfigType } from 'shared'; +import { ConfigType } from 'shared/'; @Injectable() export class WechatService { diff --git a/packages/backend/src/modules/chat/chat.service.ts b/packages/backend/src/modules/chat/chat.service.ts index e944531f..4d60141f 100644 --- a/packages/backend/src/modules/chat/chat.service.ts +++ b/packages/backend/src/modules/chat/chat.service.ts @@ -278,12 +278,10 @@ ${message} where: { id: modelId }, }); - const histories: OpenAI.ChatCompletionMessage[] = messages.map( - ({ role, content }) => ({ - role: role.toLowerCase() as OpenAI.ChatCompletionRole, - content, - }), - ); + const histories = messages.map(({ role, content }) => ({ + role: role.toLowerCase() as OpenAI.ChatCompletionRole, + content, + })) as OpenAI.ChatCompletionMessage[]; const stream = await this.#streamChat({ model: model, diff --git a/packages/backend/src/modules/order/order.controller.ts b/packages/backend/src/modules/order/order.controller.ts index e10ca4e9..c299fb48 100644 --- a/packages/backend/src/modules/order/order.controller.ts +++ b/packages/backend/src/modules/order/order.controller.ts @@ -15,10 +15,11 @@ import { Role } from '@prisma/client'; import { Payload, Roles } from '@/common/guards/auth.guard'; import { Public } from '@/common/guards/auth.guard'; +import { ZodValidationPipe } from '@/common/pipes/zod'; import { JWTPayload } from '@/libs/jwt/jwt.service'; import { PaymentService } from '@/libs/payment/payment.service'; -import { newOrderDto } from 'shared'; +import { OrderDTO } from 'shared'; import { OrderService } from './order.service'; @@ -31,8 +32,12 @@ export class OrderController { /* 新建订单 */ @Post('new') - async newOrder(@Payload('id') userId: number, @Body() data: newOrderDto) { - const order = await this.orderService.createOrder(userId, data.productId); + async newOrder( + @Payload('id') userId: number, + @Body(new ZodValidationPipe(OrderDTO.NewOrderSchema)) + body: OrderDTO.NewOrderDto, + ) { + const order = await this.orderService.createOrder(userId, body.productId); // TODO 防止短时间内重复产生订单 const result = await this.paymentService.xhStartPay({ orderId: order.id, diff --git a/packages/frontend/next.config.js b/packages/frontend/next.config.js index f2fb4c03..b3e0a139 100644 --- a/packages/frontend/next.config.js +++ b/packages/frontend/next.config.js @@ -11,6 +11,7 @@ const nextConfig = { env: { NEXT_PUBLIC_TITLE: config?.title ?? 'ChatGPT Admin Web', }, + transpilePackages: ['shared'], webpack(config) { config.module.rules.push({ test: /\.svg$/, diff --git a/packages/frontend/src/app/(admin-end)/dashboard/layout.tsx b/packages/frontend/src/app/(admin-end)/dashboard/layout.tsx index b5de856a..4f5298a0 100644 --- a/packages/frontend/src/app/(admin-end)/dashboard/layout.tsx +++ b/packages/frontend/src/app/(admin-end)/dashboard/layout.tsx @@ -1,4 +1,7 @@ +'use client'; + import Link from 'next/link'; +import { usePathname } from 'next/navigation'; import { Button } from '@radix-ui/themes'; @@ -40,13 +43,21 @@ export default function DashboardLayout({ }: { children: React.ReactNode; }) { + const pathname = usePathname(); return (
{navs.map((nav) => ( - diff --git a/packages/frontend/src/app/(admin-end)/dashboard/setting/page.tsx b/packages/frontend/src/app/(admin-end)/dashboard/setting/page.tsx index 23c27cce..5f48e884 100644 --- a/packages/frontend/src/app/(admin-end)/dashboard/setting/page.tsx +++ b/packages/frontend/src/app/(admin-end)/dashboard/setting/page.tsx @@ -3,7 +3,7 @@ import useSWR from 'swr'; import { Loading } from '@/components/loading'; -import { OptionListRoot, OptionNode } from '@/components/radix-ui-lib'; +import { OptionNode } from '@/components/radix-ui-lib'; import { useStore } from '@/store'; import useInstallStore from '@/store/install'; @@ -21,10 +21,10 @@ export default function AdminSettingPage() { if (isLoading || !data) return ; return ( - +
{data.schema.map((item: any) => ( ))} - +
); } diff --git a/packages/frontend/src/app/(admin-end)/install/page.tsx b/packages/frontend/src/app/(admin-end)/install/page.tsx index 55dff49d..0a130a23 100644 --- a/packages/frontend/src/app/(admin-end)/install/page.tsx +++ b/packages/frontend/src/app/(admin-end)/install/page.tsx @@ -3,33 +3,55 @@ import { useRouter } from 'next/navigation'; import React, { useState } from 'react'; -import { Box, Button, Text, TextField } from '@radix-ui/themes'; +import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; +import { + Box, + Button, + Callout, + IconButton, + Popover, + Text, + TextField, + Tooltip, +} from '@radix-ui/themes'; import usePreventFormSubmit from '@/hooks/use-prevent-form'; +import { InfoIcon } from '@/icons'; import { useStore } from '@/store'; +import { ErrorCodeEnum } from 'shared'; + export default function SetupPage() { const router = useRouter(); - const { fetcher } = useStore(); + const { jsonFetcher } = useStore(); const [identity, setIdentity] = useState(''); const [password, setPassword] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); const [isSubmitting, handleInitAdmin] = usePreventFormSubmit(); async function initAdmin() { - await fetcher('/auth/admin/setup', { + await jsonFetcher('/auth/admin/setup', { method: 'POST', body: JSON.stringify({ identity, password }), - }) - .then((res) => res.json()) - .then((res) => { - if (res.success) { - router.push('/auth'); + }).then((data) => { + if (!data.success) { + switch (data.code) { + case ErrorCodeEnum.AdminExists: + return setErrorMessage('管理员已存在'); + case ErrorCodeEnum.AuthFail: + return setErrorMessage('用户密码错误'); + default: + setErrorMessage(data.message); + break; } - }); + } else { + router.push('/auth'); + } + }); } return ( -
+
- 初始化管理员 +
+ 初始化管理员 + + + + + +
+ {errorMessage && ( + + + + + {errorMessage} + + )} setIdentity(e.target.value)} - placeholder="Admin Email" + placeholder="Admin Indentity" /> {props.children}
; -} - /* 开关选项 */ function SwitchItem(props: { schema: ISettingSchema; diff --git a/packages/frontend/src/icons/index.ts b/packages/frontend/src/icons/index.ts new file mode 100644 index 00000000..39e460d3 --- /dev/null +++ b/packages/frontend/src/icons/index.ts @@ -0,0 +1 @@ +export { default as InfoIcon } from './info.svg'; diff --git a/packages/frontend/src/icons/info.svg b/packages/frontend/src/icons/info.svg new file mode 100644 index 00000000..971ebb86 --- /dev/null +++ b/packages/frontend/src/icons/info.svg @@ -0,0 +1,14 @@ + + + diff --git a/packages/frontend/src/store/shared.ts b/packages/frontend/src/store/shared.ts index 2ac09bb6..892af12e 100644 --- a/packages/frontend/src/store/shared.ts +++ b/packages/frontend/src/store/shared.ts @@ -65,6 +65,11 @@ export const createSharedStore: StateCreator = ( return res; }); }, + jsonFetcher(url, init) { + return get() + .fetcher(url, init) + .then((res) => res.json()); + }, // Config config: { ...DEFAULT_CONFIG, diff --git a/packages/frontend/src/store/types.ts b/packages/frontend/src/store/types.ts index 847af18d..e650ce2c 100644 --- a/packages/frontend/src/store/types.ts +++ b/packages/frontend/src/store/types.ts @@ -1,6 +1,11 @@ import { SubmitKey, Theme } from '@/store/shared'; -import { ChatMessage, ChatMessageRole, ChatSession } from 'shared'; +import { + BaseResponse, + ChatMessage, + ChatMessageRole, + ChatSession, +} from 'shared'; export type LocalConfig = { theme: Theme; @@ -36,6 +41,10 @@ export interface SharedSlice { // utils clearData: () => void; fetcher: (url: string, options?: RequestInit) => Promise; + jsonFetcher: ( + url: string, + options?: RequestInit, + ) => Promise>; } export interface ChatSlice { diff --git a/packages/frontend/src/styles/dashboard.css b/packages/frontend/src/styles/dashboard.css index 91299f41..dcd2888f 100644 --- a/packages/frontend/src/styles/dashboard.css +++ b/packages/frontend/src/styles/dashboard.css @@ -23,7 +23,7 @@ section { "content content"; height: 100%; width: 100%; - grid-template-rows: 80px 1fr; + grid-template-rows: 48px 1fr; grid-template-columns: 250px 1fr; } diff --git a/packages/frontend/src/styles/module/radix-ui-lib.module.scss b/packages/frontend/src/styles/module/radix-ui-lib.module.scss index d9bcbc38..b3e51258 100644 --- a/packages/frontend/src/styles/module/radix-ui-lib.module.scss +++ b/packages/frontend/src/styles/module/radix-ui-lib.module.scss @@ -3,14 +3,7 @@ //@import '@radix-ui/colors/gray-dark.css'; //@import '@radix-ui/colors/teal-dark.css'; -.option-list-root { - background-color: var(--gray-2); - display: flex; - flex-direction: column; - max-width: 100%; - box-shadow: 0 2px 10px var(--gray-11); - padding: 20px; -} + .option-list-item { width: 100%; diff --git a/packages/shared/package.json b/packages/shared/package.json index 59187c2e..fe67cbc6 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,12 +1,19 @@ { "name": "shared", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "scripts": { - "dev": "tsc --watch", - "build": "tsc --build" + "dev": "tsc --build tsconfig.json --watch", + "build": "tsc --build tsconfig.build.json", + "test": "vitest run", + "test:dev": "vitest watch" }, "dependencies": { "@prisma/client": "workspace:@nest-http/prisma@*" + }, + "devDependencies": { + "typescript": "*", + "vitest": "*", + "zod": "*" } } diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts deleted file mode 100644 index 5ccfb828..00000000 --- a/packages/shared/src/auth.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* 账号可能存在的状态 - * bind: 需要绑定账户,通常为 OAuth - * password: 需要设置密码 - * block: 被禁用 - * ok: 正常 */ -export type IAccountStatus = 'bind' | 'password' | 'block' | 'ok'; - -/* 认证 DTO */ -export interface identityDto { - identity: string; -} - -/* 验证验证码 DTO */ -export interface validateCodeDto { - identity: string; - code: string; -} - -/* 忘记密码 DTO */ -export interface forgetPasswordDto { - identity: string; - code: string; - newPassword: string; -} diff --git a/packages/shared/src/config.ts b/packages/shared/src/config.ts index 849d3232..db3c272d 100644 --- a/packages/shared/src/config.ts +++ b/packages/shared/src/config.ts @@ -31,7 +31,7 @@ export interface ConfigType { keys: string; }; sms: { - use?: 'aliyun' | 'tencent' | 'uni'; + use?: 'disable' | 'aliyun' | 'tencent' | 'uni'; uni?: { signature: string; templateId: string; @@ -40,7 +40,7 @@ export interface ConfigType { }; }; email: { - use?: 'smtp' | 'resend' | 'mailgun' | 'elastic'; + use?: 'disable' | 'smtp' | 'resend' | 'mailgun' | 'elastic'; domain: string; sender?: string; smtp: {}; diff --git a/packages/shared/src/dto/index.ts b/packages/shared/src/dto/index.ts new file mode 100644 index 00000000..ad02c5bf --- /dev/null +++ b/packages/shared/src/dto/index.ts @@ -0,0 +1,71 @@ +import z from 'zod'; + +export namespace AuthDTO { + const email = z.string().email(); + const phone = z.string().regex(/^[0-9]{11}$/); + export const codeSchema = z.string().regex(/^[0-9]{6}$/); + + export const identitySchema = z.union([email, phone]); + + export const passwordSchema = z.string().min(8).max(20); + + export const RequireCodeSchema = z.object({ + identity: identitySchema, + }); + export type RequireCodeDto = z.infer; + + export const ValidateCodeSchema = z.object({ + identity: identitySchema, + code: codeSchema, + }); + export type ValidateCodeDto = z.infer; + + export const ForgetPasswordSchema = z.object({ + identity: identitySchema, + code: codeSchema, + newPassword: passwordSchema, + }); + export type ForgetPasswordDto = z.infer; + + export const ChangePasswordSchema = z.object({ + oldPassword: passwordSchema, + newPassword: passwordSchema, + }); + export type ChangePasswordDto = z.infer; + + export const PasswordLoginSchema = z.object({ + identity: identitySchema, + password: passwordSchema, + }); + export type PasswordLoginDto = z.infer; + + export const BindIdentitySchema = z.object({ + identity: identitySchema, + }); + export type BindIdentityDto = z.infer; + + export const InitAdminSchema = z.object({ + identity: identitySchema, + password: passwordSchema, + }); + export type InitAdminDto = z.infer; + + export const InitUsernameSchema = z.object({ + username: z.string().min(2).max(20), + }); + export type InitUsernameDto = z.infer; + + export const InitPasswordSchema = z.object({ + password: passwordSchema, + }); + export type InitPasswordDto = z.infer; +} + +export namespace ChatDTO {} + +export namespace OrderDTO { + export const NewOrderSchema = z.object({ + productId: z.number(), + }); + export type NewOrderDto = z.infer; +} diff --git a/packages/shared/src/error-code.ts b/packages/shared/src/error-code.ts deleted file mode 100644 index 17813ae3..00000000 --- a/packages/shared/src/error-code.ts +++ /dev/null @@ -1,44 +0,0 @@ -export enum ErrorCodeEnum { - /* 200 */ - OutOfQuota = 20001, - - /* 400 */ - // BadRequest = 40000, - AdminExists = 40001, - ConfigExists, - - /* 403 */ - AuthFail = 40300, - PasswordError, - CodeValidationError, - UserNotExist, - NameDuplicated, - EmailDuplicated, - PhoneDuplicated, - BindNameExist, - BindEmailExist, - BindPhoneExist, - BindPasswordExist, - - /* 404 */ - NotFound = 40400, - SessionNotFound, - - /* 429 */ - TooManyRequests = 42900, - - /* 406 */ - ValidationError = 40600, - - /* 500 */ - ServerError = 50000, - DatabaseError, - RedisError, - EmailError, - EmailNotSetup, - SmsError, - SmsNotSetup, - WechatError, - - UnknownError = 99999, -} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 09daee35..f273f402 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,4 +1,7 @@ -export * from './auth'; +/* Data Transfer Object */ +export { AuthDTO, OrderDTO } from './dto'; +/* Types */ +export * from './types'; export * from './config'; export * from './chat'; export * from './order'; @@ -6,5 +9,61 @@ export * from './product'; export * from './types/database'; export * from './user'; export * from './install'; -export { Role } from '@prisma/client'; -export { ErrorCodeEnum } from './error-code'; + +export type BaseResponse = BaseResponseSuccess | BaseResponseFailure; + +interface BaseResponseSuccess { + success: true; + data: T; +} + +interface BaseResponseFailure { + success: false; + code: ErrorCodeEnum; + message: string; +} + +export enum ErrorCodeEnum { + /* 200 */ + OutOfQuota = 20001, + + /* 400 */ + // BadRequest = 40000, + AdminExists = 40001, + ConfigExists, + + /* 403 */ + AuthFail = 40300, + PasswordError, + CodeValidationError, + UserNotExist, + NameDuplicated, + EmailDuplicated, + PhoneDuplicated, + BindNameExist, + BindEmailExist, + BindPhoneExist, + BindPasswordExist, + + /* 404 */ + NotFound = 40400, + SessionNotFound, + + /* 429 */ + TooManyRequests = 42900, + + /* 406 */ + ValidationError = 40600, + + /* 500 */ + ServerError = 50000, + DatabaseError, + RedisError, + EmailError, + EmailNotSetup, + SmsError, + SmsNotSetup, + WechatError, + + UnknownError = 99999, +} diff --git a/packages/shared/src/types/auth.ts b/packages/shared/src/types/auth.ts new file mode 100644 index 00000000..07d68a7e --- /dev/null +++ b/packages/shared/src/types/auth.ts @@ -0,0 +1,6 @@ +/* 账号可能存在的状态 + * bind: 需要绑定账户,通常为 OAuth + * password: 需要设置密码 + * block: 被禁用 + * ok: 正常 */ +export type IAccountStatus = 'bind' | 'password' | 'block' | 'ok'; diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts new file mode 100644 index 00000000..ad5b7af6 --- /dev/null +++ b/packages/shared/src/types/index.ts @@ -0,0 +1,3 @@ +export * from './auth'; + +export type { Role } from '@prisma/client'; diff --git a/packages/shared/tsconfig.build.json b/packages/shared/tsconfig.build.json new file mode 100644 index 00000000..1c370501 --- /dev/null +++ b/packages/shared/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "sourceMap": false + }, + "extends": "./tsconfig.json", + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 770dcd39..e4ada9ef 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -22,7 +22,7 @@ "resolveJsonModule": true, "baseUrl": "./src", "paths": { - "@": [ + "@/*": [ "./*" ] } @@ -31,6 +31,8 @@ "src/**/*" ], "exclude": [ - "node_modules" + "node_modules", + "dist", + "test" ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c852c386..bae2c59d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,18 @@ importers: typescript: specifier: latest version: 5.2.2 + vite: + specifier: ^5.0.7 + version: 5.0.7(@types/node@20.8.10) + vite-tsconfig-paths: + specifier: ^4.2.2 + version: 4.2.2(typescript@5.2.2)(vite@5.0.7) + vitest: + specifier: ^1.0.2 + version: 1.0.2(@types/node@20.8.10) + zod: + specifier: latest + version: 3.22.4 packages/backend: dependencies: @@ -100,6 +112,9 @@ importers: spark-md5: specifier: ^3.0.2 version: 3.0.2 + zod: + specifier: '*' + version: 3.22.4 devDependencies: '@nestjs/cli': specifier: ^10.2.1 @@ -279,6 +294,16 @@ importers: '@prisma/client': specifier: workspace:@nest-http/prisma@* version: link:../../prisma + devDependencies: + typescript: + specifier: '*' + version: 5.3.3 + vitest: + specifier: '*' + version: 1.0.2(@types/node@20.8.10) + zod: + specifier: '*' + version: 3.22.4 prisma: dependencies: @@ -1679,6 +1704,204 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.9 + /@esbuild/android-arm64@0.19.8: + resolution: {integrity: sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.8: + resolution: {integrity: sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.8: + resolution: {integrity: sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.8: + resolution: {integrity: sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.8: + resolution: {integrity: sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.8: + resolution: {integrity: sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.8: + resolution: {integrity: sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.8: + resolution: {integrity: sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.8: + resolution: {integrity: sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.8: + resolution: {integrity: sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.8: + resolution: {integrity: sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.8: + resolution: {integrity: sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.8: + resolution: {integrity: sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.8: + resolution: {integrity: sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.8: + resolution: {integrity: sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.8: + resolution: {integrity: sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.8: + resolution: {integrity: sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.8: + resolution: {integrity: sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.8: + resolution: {integrity: sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.8: + resolution: {integrity: sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.8: + resolution: {integrity: sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.8: + resolution: {integrity: sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.42.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2047,6 +2270,13 @@ packages: '@sinclair/typebox': 0.27.8 dev: true + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + /@jest/source-map@29.6.0: resolution: {integrity: sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3603,6 +3833,110 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@rollup/rollup-android-arm-eabi@4.7.0: + resolution: {integrity: sha512-rGku10pL1StFlFvXX5pEv88KdGW6DHUghsxyP/aRYb9eH+74jTGJ3U0S/rtlsQ4yYq1Hcc7AMkoJOb1xu29Fxw==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.7.0: + resolution: {integrity: sha512-/EBw0cuJ/KVHiU2qyVYUhogXz7W2vXxBzeE9xtVIMC+RyitlY2vvaoysMUqASpkUtoNIHlnKTu/l7mXOPgnKOA==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.7.0: + resolution: {integrity: sha512-4VXG1bgvClJdbEYYjQ85RkOtwN8sqI3uCxH0HC5w9fKdqzRzgG39K7GAehATGS8jghA7zNoS5CjSKkDEqWmNZg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.7.0: + resolution: {integrity: sha512-/ImhO+T/RWJ96hUbxiCn2yWI0/MeQZV/aeukQQfhxiSXuZJfyqtdHPUPrc84jxCfXTxbJLmg4q+GBETeb61aNw==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.7.0: + resolution: {integrity: sha512-zhye8POvTyUXlKbfPBVqoHy3t43gIgffY+7qBFqFxNqVtltQLtWeHNAbrMnXiLIfYmxcoL/feuLDote2tx+Qbg==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.7.0: + resolution: {integrity: sha512-RAdr3OJnUum6Vs83cQmKjxdTg31zJnLLTkjhcFt0auxM6jw00GD6IPFF42uasYPr/wGC6TRm7FsQiJyk0qIEfg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.7.0: + resolution: {integrity: sha512-nhWwYsiJwZGq7SyR3afS3EekEOsEAlrNMpPC4ZDKn5ooYSEjDLe9W/xGvoIV8/F/+HNIY6jY8lIdXjjxfxopXw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.7.0: + resolution: {integrity: sha512-rlfy5RnQG1aop1BL/gjdH42M2geMUyVQqd52GJVirqYc787A/XVvl3kQ5NG/43KXgOgE9HXgCaEH05kzQ+hLoA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.7.0: + resolution: {integrity: sha512-cCkoGlGWfBobdDtiiypxf79q6k3/iRVGu1HVLbD92gWV5WZbmuWJCgRM4x2N6i7ljGn1cGytPn9ZAfS8UwF6vg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.7.0: + resolution: {integrity: sha512-R2oBf2p/Arc1m+tWmiWbpHBjEcJnHVnv6bsypu4tcKdrYTpDfl1UT9qTyfkIL1iiii5D4WHxUHCg5X0pzqmxFg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.7.0: + resolution: {integrity: sha512-CPtgaQL1aaPc80m8SCVEoxFGHxKYIt3zQYC3AccL/SqqiWXblo3pgToHuBwR8eCP2Toa+X1WmTR/QKFMykws7g==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.7.0: + resolution: {integrity: sha512-pmioUlttNh9GXF5x2CzNa7Z8kmRTyhEzzAC+2WOOapjewMbl+3tGuAnxbwc5JyG8Jsz2+hf/QD/n5VjimOZ63g==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.7.0: + resolution: {integrity: sha512-SeZzC2QhhdBQUm3U0c8+c/P6UlRyBcLL2Xp5KX7z46WXZxzR8RJSIWL9wSUeBTgxog5LTPJuPj0WOT9lvrtP7Q==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@rushstack/eslint-patch@1.3.3: resolution: {integrity: sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==} dev: false @@ -4279,6 +4613,44 @@ packages: '@typescript-eslint/types': 5.59.11 eslint-visitor-keys: 3.4.3 + /@vitest/expect@1.0.2: + resolution: {integrity: sha512-mAIo/8uddSWkjQMLFcjqZP3WmkwvvN0OtlyZIu33jFnwme3vZds8m8EDMxtj+Uzni2DwtPfHNjJcTM8zTV1f4A==} + dependencies: + '@vitest/spy': 1.0.2 + '@vitest/utils': 1.0.2 + chai: 4.3.10 + dev: true + + /@vitest/runner@1.0.2: + resolution: {integrity: sha512-ZcHJXPT2kg/9Hc4fNkCbItlsgZSs3m4vQbxB8LCSdzpbG85bExCmSvu6K9lWpMNdoKfAr1Jn0BwS9SWUcGnbTQ==} + dependencies: + '@vitest/utils': 1.0.2 + p-limit: 5.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/snapshot@1.0.2: + resolution: {integrity: sha512-9ClDz2/aV5TfWA4reV7XR9p+hE0e7bifhwxlURugj3Fw0YXeTFzHmKCNEHd6wOIFMfthbGGwhlq7TOJ2jDO4/g==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.1 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@1.0.2: + resolution: {integrity: sha512-YlnHmDntp+zNV3QoTVFI5EVHV0AXpiThd7+xnDEbWnD6fw0TH/J4/+3GFPClLimR39h6nA5m0W4Bjm5Edg4A/A==} + dependencies: + tinyspy: 2.2.0 + dev: true + + /@vitest/utils@1.0.2: + resolution: {integrity: sha512-GPQkGHAnFAP/+seSbB9pCsj339yRrMgILoI5H2sPevTLCYgBq0VRjF8QSllmnQyvf0EontF6KUIt2t5s2SmqoQ==} + dependencies: + diff-sequences: 29.6.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + /@webassemblyjs/ast@1.11.6: resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==} dependencies: @@ -4427,6 +4799,11 @@ packages: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} + /acorn-walk@8.3.1: + resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@8.10.0: resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} engines: {node: '>=0.4.0'} @@ -4682,6 +5059,10 @@ packages: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} dev: true + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + /ast-types-flow@0.0.7: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: false @@ -4950,6 +5331,11 @@ packages: streamsearch: 1.1.0 dev: false + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -4984,6 +5370,19 @@ packages: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} dev: false + /chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -5024,6 +5423,12 @@ packages: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} dev: false + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -5036,7 +5441,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} @@ -5426,6 +5831,13 @@ packages: optional: true dev: true + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -5493,6 +5905,11 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -5712,6 +6129,36 @@ packages: is-symbol: 1.0.4 dev: false + /esbuild@0.19.8: + resolution: {integrity: sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.19.8 + '@esbuild/android-arm64': 0.19.8 + '@esbuild/android-x64': 0.19.8 + '@esbuild/darwin-arm64': 0.19.8 + '@esbuild/darwin-x64': 0.19.8 + '@esbuild/freebsd-arm64': 0.19.8 + '@esbuild/freebsd-x64': 0.19.8 + '@esbuild/linux-arm': 0.19.8 + '@esbuild/linux-arm64': 0.19.8 + '@esbuild/linux-ia32': 0.19.8 + '@esbuild/linux-loong64': 0.19.8 + '@esbuild/linux-mips64el': 0.19.8 + '@esbuild/linux-ppc64': 0.19.8 + '@esbuild/linux-riscv64': 0.19.8 + '@esbuild/linux-s390x': 0.19.8 + '@esbuild/linux-x64': 0.19.8 + '@esbuild/netbsd-x64': 0.19.8 + '@esbuild/openbsd-x64': 0.19.8 + '@esbuild/sunos-x64': 0.19.8 + '@esbuild/win32-arm64': 0.19.8 + '@esbuild/win32-ia32': 0.19.8 + '@esbuild/win32-x64': 0.19.8 + dev: true + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -6163,6 +6610,21 @@ packages: strip-final-newline: 2.0.0 dev: true + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + /exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -6447,8 +6909,8 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true @@ -6495,6 +6957,10 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: true + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: @@ -6525,6 +6991,11 @@ packages: engines: {node: '>=10'} dev: true + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} @@ -6635,6 +7106,10 @@ packages: merge2: 1.4.1 slash: 3.0.0 + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true + /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: @@ -6788,6 +7263,11 @@ packages: engines: {node: '>=10.17.0'} dev: true + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + /humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} dependencies: @@ -7077,6 +7557,11 @@ packages: engines: {node: '>=8'} dev: true + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -7387,7 +7872,7 @@ packages: micromatch: 4.0.5 walker: 1.0.8 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /jest-leak-detector@29.6.2: @@ -7808,6 +8293,14 @@ packages: engines: {node: '>=6.11.5'} dev: true + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.4.2 + pkg-types: 1.0.3 + dev: true + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -7861,6 +8354,12 @@ packages: js-tokens: 4.0.0 dev: false + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: @@ -7901,6 +8400,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -8389,6 +8895,11 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -8451,6 +8962,15 @@ packages: hasBin: true dev: false + /mlly@1.4.2: + resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} + dependencies: + acorn: 8.10.0 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.3.2 + dev: true + /mnemonist@0.39.5: resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==} dependencies: @@ -8485,6 +9005,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -8621,6 +9147,13 @@ packages: path-key: 3.1.1 dev: true + /npm-run-path@5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + /npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} dependencies: @@ -8725,6 +9258,13 @@ packages: dependencies: mimic-fn: 2.1.0 + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + /openai@4.14.0: resolution: {integrity: sha512-pWQYkFWdeudR1yLyS/rFfIv/MTXqFEtlN4EKVm0F6KKKGjhuthznPOCC4hxfAFmjlgbpJJXhQQ/oBeB9bxFmUw==} hasBin: true @@ -8793,6 +9333,13 @@ packages: dependencies: yocto-queue: 0.1.0 + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -8859,6 +9406,11 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -8881,6 +9433,14 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -8931,6 +9491,14 @@ packages: find-up: 4.1.0 dev: true + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.4.2 + pathe: 1.1.1 + dev: true + /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -9000,6 +9568,15 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss@8.4.32: + resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -9032,6 +9609,15 @@ packages: react-is: 18.2.0 dev: true + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /prisma-extension-pagination@0.5.0(@prisma/client@prisma): resolution: {integrity: sha512-M2vqOkaQR8U++zPbc+ti1nVArZbpRgOr/BdaUDkBk7XV7uNikjJeDvQzmUEadzkTAON3H+SWtTrafHr0P0AMzA==} peerDependencies: @@ -9622,6 +10208,27 @@ packages: glob: 9.3.5 dev: true + /rollup@4.7.0: + resolution: {integrity: sha512-7Kw0dUP4BWH78zaZCqF1rPyQ8D5DSU6URG45v1dqS/faNsx9WXyess00uTOZxKr7oR/4TOjO1CPudT8L1UsEgw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.7.0 + '@rollup/rollup-android-arm64': 4.7.0 + '@rollup/rollup-darwin-arm64': 4.7.0 + '@rollup/rollup-darwin-x64': 4.7.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.7.0 + '@rollup/rollup-linux-arm64-gnu': 4.7.0 + '@rollup/rollup-linux-arm64-musl': 4.7.0 + '@rollup/rollup-linux-riscv64-gnu': 4.7.0 + '@rollup/rollup-linux-x64-gnu': 4.7.0 + '@rollup/rollup-linux-x64-musl': 4.7.0 + '@rollup/rollup-win32-arm64-msvc': 4.7.0 + '@rollup/rollup-win32-ia32-msvc': 4.7.0 + '@rollup/rollup-win32-x64-msvc': 4.7.0 + fsevents: 2.3.3 + dev: true + /run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -9768,6 +10375,10 @@ packages: get-intrinsic: 1.2.1 object-inspect: 1.12.3 + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -9864,10 +10475,18 @@ packages: escape-string-regexp: 2.0.0 dev: true + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + /standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} dev: false + /std-env@3.6.0: + resolution: {integrity: sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==} + dev: true + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -9968,10 +10587,21 @@ packages: engines: {node: '>=6'} dev: true + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.10.0 + dev: true + /style-to-object@0.4.2: resolution: {integrity: sha512-1JGpfPB3lo42ZX8cuPrheZbfQ6kqPPnPHlKMyeRYtfKD+0jG+QsXgXN57O/dvJlzlB2elI6dGmrPnl5VPQFPaA==} dependencies: @@ -10218,6 +10848,20 @@ packages: resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} dev: false + /tinybench@2.5.1: + resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + dev: true + + /tinypool@0.8.1: + resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.0: + resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + engines: {node: '>=14.0.0'} + dev: true + /tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -10409,6 +11053,19 @@ packages: resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} dev: false + /tsconfck@2.1.2(typescript@5.2.2): + resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} + engines: {node: ^14.13.1 || ^16 || >=18} + hasBin: true + peerDependencies: + typescript: ^4.3.5 || ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.2.2 + dev: true + /tsconfig-paths-webpack-plugin@4.1.0: resolution: {integrity: sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==} engines: {node: '>=10.13.0'} @@ -10584,6 +11241,16 @@ packages: engines: {node: '>=14.17'} hasBin: true + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /ufo@1.3.2: + resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} + dev: true + /uid@2.0.2: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} @@ -10828,6 +11495,137 @@ packages: d3-timer: 3.0.1 dev: false + /vite-node@1.0.2(@types/node@20.8.10): + resolution: {integrity: sha512-h7BbMJf46fLvFW/9Ygo3snkIBEHFh6fHpB4lge98H5quYrDhPFeI3S0LREz328uqPWSnii2yeJXktQ+Pmqk5BQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 5.0.7(@types/node@20.8.10) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite-tsconfig-paths@4.2.2(typescript@5.2.2)(vite@5.0.7): + resolution: {integrity: sha512-dq0FjyxHHDnp0uS3P12WEOX2W7NeuLzX9AWP38D7Zw2CTbFErapwQVlCiT5DMJcVWKQ1MMdTe92PZl/rBQ7qcw==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + dependencies: + debug: 4.3.4 + globrex: 0.1.2 + tsconfck: 2.1.2(typescript@5.2.2) + vite: 5.0.7(@types/node@20.8.10) + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /vite@5.0.7(@types/node@20.8.10): + resolution: {integrity: sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.8.10 + esbuild: 0.19.8 + postcss: 8.4.32 + rollup: 4.7.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitest@1.0.2(@types/node@20.8.10): + resolution: {integrity: sha512-F3NVwwpXfRSDnJmyv+ALPwSRVt0zDkRRE18pwUHSUPXAlWQ47rY1dc99ziMW5bBHyqwK2ERjMisLNoef64qk9w==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.8.10 + '@vitest/expect': 1.0.2 + '@vitest/runner': 1.0.2 + '@vitest/snapshot': 1.0.2 + '@vitest/spy': 1.0.2 + '@vitest/utils': 1.0.2 + acorn-walk: 8.3.1 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.6.0 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.8.1 + vite: 5.0.7(@types/node@20.8.10) + vite-node: 1.0.2(@types/node@20.8.10) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -10947,6 +11745,15 @@ packages: dependencies: isexe: 2.0.0 + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: @@ -11039,6 +11846,14 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + /zustand@4.3.6(react@18.2.0): resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==} engines: {node: '>=12.7.0'} diff --git a/prisma/index.d.ts b/prisma/index.d.ts new file mode 100644 index 00000000..4f1cce44 --- /dev/null +++ b/prisma/index.d.ts @@ -0,0 +1 @@ +export * from './client'; diff --git a/prisma/index.mjs b/prisma/index.mjs new file mode 100644 index 00000000..83dae763 --- /dev/null +++ b/prisma/index.mjs @@ -0,0 +1 @@ +export * from './client' diff --git a/test/dto/index.spec.ts b/test/dto/index.spec.ts new file mode 100644 index 00000000..67c6dc54 --- /dev/null +++ b/test/dto/index.spec.ts @@ -0,0 +1,21 @@ +import { describe, expect, test } from 'vitest'; + +import { AuthDTO } from 'shared'; + +describe('[DTO] Auth', () => { + test('normal email is safe for identity', () => { + expect(AuthDTO.identitySchema.safeParse('example@gmail.com')).toBeSafe(); + }); + test('string is not safe for identity', () => { + expect(AuthDTO.identitySchema.safeParse('example.com')).not.toBeSafe(); + }); + test('chinese phone is safe for identity', () => { + expect(AuthDTO.identitySchema.safeParse('13800138000')).toBeSafe(); + }); + test('too short is safe for password', () => { + expect(AuthDTO.passwordSchema.safeParse('123456')).not.toBeSafe(); + }); + test('6-digit is safe for validate code', () => { + expect(AuthDTO.codeSchema.safeParse('123456')).toBeSafe(); + }); +}); diff --git a/test/schema.ts b/test/schema.ts new file mode 100644 index 00000000..a994b3bf --- /dev/null +++ b/test/schema.ts @@ -0,0 +1,17 @@ +import { ZodError } from 'zod'; + +export function toBeSafe( + received: { success: true; data: T; error?: ZodError }, + expected?: T, +) { + // @ts-ignore + const { isNot } = this; + return { + // 请勿根据 isNot 参数更改你的 "pass" 值,Vitest 为你做了这件事情 + pass: + expected !== undefined + ? received.success && received.data === expected + : received.success, + message: () => `${received.data} is${isNot ? ' not' : ''} safe`, + }; +} diff --git a/test/setup-file.ts b/test/setup-file.ts new file mode 100644 index 00000000..3fc122d6 --- /dev/null +++ b/test/setup-file.ts @@ -0,0 +1,21 @@ +import { expect } from 'vitest'; + +import { toBeSafe } from '@test/schema'; + +expect.extend({ toBeSafe }); + +// import { redisHelper } from './helper/redis-mock.helper' +// import { prisma } from './lib/prisma' +// import resetDb from './lib/reset-db' +// +// beforeAll(() => {}) +// +// beforeEach(async () => { +// await resetDb() +// }) +// +// afterAll(async () => { +// await resetDb() +// await prisma.$disconnect() +// await (await redisHelper).close() +// }) diff --git a/test/setup.ts b/test/setup.ts new file mode 100644 index 00000000..cc40a464 --- /dev/null +++ b/test/setup.ts @@ -0,0 +1 @@ +module.exports = () => {}; diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 00000000..623dd425 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,63 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "declaration": true, + "strict": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "target": "es2022", + "sourceMap": true, + "outDir": "./dist", + "noEmit": true, + "allowJs": true, + "noImplicitAny": false, + "incremental": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "types": [ + "node", + "vitest/globals", + "./vitest.d.ts" + ], + "baseUrl": ".", + "paths": { + "frontend": [ + "../packages/frontend/src" + ], + "frontend/*": [ + "../packages/frontend/src/*" + ], + "backend": [ + "../packages/backend/src" + ], + "backend/*": [ + "../packages/backend/src/*" + ], + "shared": [ + "../packages/shared/src" + ], + "shared/*": [ + "../packages/shared/src/*" + ], + "@test": [ + "." + ], + "@test/*": [ + "./*" + ], + "@prisma/client": [ + "../prisma/client" + ] + } + }, + "include": [ + "./**/*.ts", + "./vitest.d.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/test/vitest.d.ts b/test/vitest.d.ts new file mode 100644 index 00000000..074e46a4 --- /dev/null +++ b/test/vitest.d.ts @@ -0,0 +1,9 @@ +interface CustomMatchers { + toBeFoo(): R; +} + +declare module 'vitest' { + interface Assertion extends CustomMatchers {} + + interface AsymmetricMatchersContaining extends CustomMatchers {} +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..3d6b9b07 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,53 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "declaration": true, + "strictNullChecks": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "target": "es2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": ".", + "noImplicitAny": false, + "incremental": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "paths": { + "frontend": [ + "./packages/frontend/src" + ], + "frontend/*": [ + "./packages/frontend/src/*" + ], + "backend": [ + "./packages/backend/src" + ], + "backend/*": [ + "./packages/backend/src/*" + ], + "shared": [ + "./packages/shared/src" + ], + "shared/*": [ + "./packages/shared/src/*" + ], + "@test": [ + "." + ], + "@test/*": [ + "./*" + ], + "@prisma/client": [ + "./prisma/client" + ] + } + }, + "exclude": [ + "dist", + "tmp" + ] +} diff --git a/turbo.json b/turbo.json index e4260cbe..bf6f8754 100644 --- a/turbo.json +++ b/turbo.json @@ -10,6 +10,15 @@ "^db:generate" ] }, + "frontend#build": { + "inputs": ["config.json"], + "dependsOn": [ + "shared#build" + ], + "outputs": [ + "packages/backend/.next/**" + ] + }, "backend#build": { "dependsOn": [ "db:generate", @@ -31,10 +40,8 @@ "!.next/cache/**" ] }, - "test": { - "dependsOn": [ - "^test" - ] + "//#test": { + "dependsOn": [] }, "start": { "dependsOn": [ diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..6d9d1a01 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,24 @@ +import { resolve } from 'path'; +import tsconfigPath from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + root: './test', + test: { + include: ['**/*.spec.ts', '**/*.e2e-spec.ts'], + + globals: true, + setupFiles: [resolve(__dirname, './test/setup-file.ts')], + environment: 'node', + includeSource: [resolve(__dirname, './test')], + }, + + plugins: [ + tsconfigPath({ + projects: [ + resolve(__dirname, './test/tsconfig.json'), + resolve(__dirname, './tsconfig.json'), + ], + }), + ], +});