diff --git a/packages/backend/src/libs/jwt/jwt.service.ts b/packages/backend/src/libs/jwt/jwt.service.ts index 1564ebeb..601cc612 100644 --- a/packages/backend/src/libs/jwt/jwt.service.ts +++ b/packages/backend/src/libs/jwt/jwt.service.ts @@ -19,8 +19,16 @@ export type JWTPayload = JWTPayloadDefault & { export class JwtService { constructor(private configService: ConfigService) {} - async getKey(type: 'public' | 'private') { + async getKey(type: 'public' | 'private', refresh = false) { switch (this.configService.get('jwt').algorithm) { + case 'HS256': + if (refresh) { + const refreshSecret = this.configService.get('jwt')?.refreshSecret; + if (refreshSecret) { + return new TextEncoder().encode(refreshSecret); + } + } + return new TextEncoder().encode(this.configService.get('jwt').secret); case 'ES256': if (type === 'private') { return await importJWK( @@ -32,15 +40,20 @@ export class JwtService { JSON.parse(this.configService.get('jwt').publicKey), 'ES256', ); - case 'HS256': - return new TextEncoder().encode(this.configService.get('jwt').secret); } } - async sign(payload: JWTPayload): Promise { + async sign( + payload: JWTPayload, + duration = 7 * 24 * 60 * 60, + refresh = false, + ): Promise { const iat = Math.floor(Date.now() / 1000); // Not before: Now - const exp = iat + 7 * 24 * 60 * 60; // One week - return await new SignJWT({ ...payload }) + const exp = iat + duration; // One week + return await new SignJWT({ + ...payload, + ...(refresh ? { sub: 'refresh' } : {}), + }) .setProtectedHeader({ alg: this.configService.get('jwt').algorithm, typ: 'JWT', @@ -48,7 +61,7 @@ export class JwtService { .setExpirationTime(exp) .setIssuedAt(iat) .setNotBefore(iat) - .sign(await this.getKey('private')); + .sign(await this.getKey('private', refresh)); } async verify(token: string): Promise { diff --git a/packages/backend/src/modules/auth/auth.controller.ts b/packages/backend/src/modules/auth/auth.controller.ts index e7230679..700326f8 100644 --- a/packages/backend/src/modules/auth/auth.controller.ts +++ b/packages/backend/src/modules/auth/auth.controller.ts @@ -5,6 +5,7 @@ import { ConfigService } from '@/common/config'; import { BizException } from '@/common/exceptions/biz.exception'; import { Payload, Public } from '@/common/guards/auth.guard'; import { ZodValidationPipe } from '@/common/pipes/zod'; +import { JWTPayload } from '@/libs/jwt/jwt.service'; import { WechatService } from '@/modules/auth/wechat.service'; import { AuthDTO, ErrorCodeEnum } from 'shared'; @@ -185,4 +186,12 @@ export class AuthController { data: roles, }; } + + @Get('refresh') + async refresh(@Payload() payload: JWTPayload) { + return { + success: true, + ...(await this.authService.refresh(payload.id, payload.role)), + }; + } } diff --git a/packages/backend/src/modules/auth/auth.service.ts b/packages/backend/src/modules/auth/auth.service.ts index 48c84d04..8d645192 100644 --- a/packages/backend/src/modules/auth/auth.service.ts +++ b/packages/backend/src/modules/auth/auth.service.ts @@ -62,10 +62,7 @@ export class AuthService { * 1. 检查是否绑定账户 * 2. 检查是否设置密码 */ - async #signWithCheck(user: any): Promise<{ - token: string; - status: IAccountStatus; - }> { + async #signWithCheck(user: any) { let status: IAccountStatus = 'ok'; if (!user.email && !user.phone) { status = 'bind'; @@ -73,7 +70,11 @@ export class AuthService { status = 'password'; } return { - token: await this.jwt.sign({ id: user.id, role: user.role }), + sessionToken: await this.jwt.sign({ id: user.id, role: user.role }), + refreshToken: await this.jwt.sign( + { id: user.id, role: user.role }, + 30 * 24 * 60 * 60, + ), status, }; } @@ -342,4 +343,8 @@ export class AuthService { }, }); } + + async refresh(userId: number, userRole: string) { + return this.#signWithCheck({ id: userId, role: userRole }); + } } diff --git a/packages/backend/src/modules/chat/chat.controller.ts b/packages/backend/src/modules/chat/chat.controller.ts index 52076e55..27440400 100644 --- a/packages/backend/src/modules/chat/chat.controller.ts +++ b/packages/backend/src/modules/chat/chat.controller.ts @@ -164,7 +164,7 @@ export class ChatController { modelId: data.modelId, input: data.content, messages: chatSession.messages, - // key, + topic: chatSession.topic, }); } } diff --git a/packages/backend/src/modules/chat/chat.service.ts b/packages/backend/src/modules/chat/chat.service.ts index 4d60141f..6401bd66 100644 --- a/packages/backend/src/modules/chat/chat.service.ts +++ b/packages/backend/src/modules/chat/chat.service.ts @@ -234,7 +234,7 @@ export class ChatService { async summarizeTopic(message: string, sessionId: string) { const result = (await this.#chat({ - input: `Give me the topic title about the following text by use as few words as possible. + input: `summarize the text in 4 words. Text: """ ${message} """`, @@ -242,8 +242,7 @@ ${message} histories: [ { role: 'system', - content: - 'You are an assistant who uses a few words to summarize conversations', + content: 'You are an assistant who uses 4 words to summarize text', }, ], stream: false, @@ -263,6 +262,7 @@ ${message} input, modelId, messages, // key, + topic, }: { userId: number; sessionId: string; @@ -273,6 +273,7 @@ ${message} messages: ChatMessage[]; /* Request API Key */ // key: string; + topic?: string; }) { const { name: model } = await this.prisma.client.model.findUniqueOrThrow({ where: { id: modelId }, @@ -338,6 +339,24 @@ ${message} }), ]); subscriber.complete(); + /* 首次对话自动总结对话, + * First conversation automatically summarizes the conversation + */ + if (!topic) { + await this.summarizeTopic( + [ + ...histories, + { role: 'user', content: input }, + { + role: 'assistant', + content: generated, + }, + ] + .map((m) => `${m.role}: ${m.content}`) + .join('\n'), + sessionId, + ); + } } })(); }); diff --git a/packages/frontend/.eslintrc.json b/packages/frontend/.eslintrc.json index bbde8de8..3ddc8932 100644 --- a/packages/frontend/.eslintrc.json +++ b/packages/frontend/.eslintrc.json @@ -7,8 +7,7 @@ }, "rules": { "@next/next/no-html-link-for-pages": [ - "error", - "packages/frontend/src/app" + "off" ] } } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index c6d44e78..007bce5e 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -18,7 +18,6 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/themes": "^2.0.0", "@svgr/webpack": "^8.1.0", - "@tremor/react": "^3.11.1", "@types/ramda": "^0.29.7", "clsx": "^2.0.0", "emoji-picker-react": "^4.4.7", diff --git a/packages/frontend/src/app/(admin-end)/dashboard/page.tsx b/packages/frontend/src/app/(admin-end)/dashboard/page.tsx index 3bab702e..4365b859 100644 --- a/packages/frontend/src/app/(admin-end)/dashboard/page.tsx +++ b/packages/frontend/src/app/(admin-end)/dashboard/page.tsx @@ -3,7 +3,6 @@ import useSWR from 'swr'; import { Box, Grid, Text } from '@radix-ui/themes'; -import { Card, LineChart, Title } from '@tremor/react'; import { Loading } from '@/components/loading'; import { useStore } from '@/store'; @@ -32,14 +31,14 @@ export default function DashboardIndex() { return ( {analytics.map((item) => ( - + {item.label} {data[item.key]} - + ))} ); diff --git a/packages/frontend/src/app/(admin-end)/layout.tsx b/packages/frontend/src/app/(admin-end)/layout.tsx index eb626a82..21bbb001 100644 --- a/packages/frontend/src/app/(admin-end)/layout.tsx +++ b/packages/frontend/src/app/(admin-end)/layout.tsx @@ -4,7 +4,8 @@ import '@radix-ui/themes/styles.css'; import '@/styles/dashboard.css'; export const metadata = { - title: 'Dashboard | ChatGPT Admin ', + title: 'Dashboard | ChatGPT Admin Web', + description: 'Manage Dashboard for ChatGPT Admin Web', }; export default function AdminEndLayout({ diff --git a/packages/frontend/src/app/auth/page.tsx b/packages/frontend/src/app/auth/page.tsx index c0afe173..3326c716 100644 --- a/packages/frontend/src/app/auth/page.tsx +++ b/packages/frontend/src/app/auth/page.tsx @@ -29,7 +29,7 @@ const weChatOauthRedirectUrl = /* 验证码登录/注册 */ function ValidateCodeLogin() { const router = useRouter(); - const { fetcher, setSessionToken } = useStore(); + const { fetcher, setAuthToken } = useStore(); const [identity, setIdentity] = useState(''); const [ifCodeSent, setIfCodeSent] = useState(false); const [validateCode, setValidateCode] = useState(''); @@ -65,7 +65,7 @@ function ValidateCodeLogin() { .then((res) => res.json()) .then((res) => { if (res.success) { - setSessionToken(res.token); + setAuthToken(res.sessionToken, res.refreshToken); return router.push('/'); } else { showToast(res.message); @@ -151,7 +151,7 @@ function ValidateCodeLogin() { /* 密码登录 */ const PasswordLogin: React.FC = () => { const router = useRouter(); - const { fetcher, setSessionToken } = useStore(); + const { fetcher, setAuthToken } = useStore(); const [identity, setIdentity] = useState(''); const [password, setPassword] = useState(''); const [isSubmitting, handleSubmit] = usePreventFormSubmit(); @@ -165,7 +165,7 @@ const PasswordLogin: React.FC = () => { .then((res) => res.json()) .then((res) => { if (res.success) { - setSessionToken(res.token); + setAuthToken(res.sessionToken, res.refreshToken); router.push('/'); } else { router.refresh(); diff --git a/packages/frontend/src/app/provider/auth-middleware.ts b/packages/frontend/src/app/provider/auth-middleware.ts new file mode 100644 index 00000000..e383ff81 --- /dev/null +++ b/packages/frontend/src/app/provider/auth-middleware.ts @@ -0,0 +1,25 @@ +import { useRouter } from 'next/navigation'; +import { Middleware, SWRHook } from 'swr'; + +import { useStore } from '@/store'; + +import { ErrorCodeEnum } from 'shared'; + +const authMiddleware: Middleware = + (useSWRNext: SWRHook) => (key, fetcher, config) => { + // Handle the next middleware, or the `useSWR` hook if this is the last one. + const swr = useSWRNext(key, fetcher, config); + + // After hook runs... + // @ts-ignore + if (swr.data?.success === false) { + // @ts-ignore + if (swr.data?.code === ErrorCodeEnum.AuthFail) { + useStore().clearAuthToken(); + useRouter().push('/auth'); + throw new Error('Auth failed'); + } + } + return swr; + }; +export default authMiddleware; diff --git a/packages/frontend/src/app/provider/client-provider.tsx b/packages/frontend/src/app/provider/client-provider.tsx index a0a68a51..6919e465 100644 --- a/packages/frontend/src/app/provider/client-provider.tsx +++ b/packages/frontend/src/app/provider/client-provider.tsx @@ -4,6 +4,7 @@ import { usePathname, useRouter } from 'next/navigation'; import React, { useCallback, useEffect, useState } from 'react'; import { SWRConfig } from 'swr'; +import authMiddleware from '@/app/provider/auth-middleware'; import { Loading } from '@/components/loading'; import { useStore } from '@/store'; @@ -14,7 +15,8 @@ export function AuthProvider({ children: React.ReactNode; admin?: boolean; }) { - const { sessionToken, setSessionToken } = useStore(); + const { sessionToken, refreshToken, setAuthToken, clearAuthToken } = + useStore(); const pathname = usePathname(); const router = useRouter(); @@ -25,12 +27,36 @@ export function AuthProvider({ if (!sessionToken) return false; try { + const payload = JSON.parse(atob(sessionToken.split('.')[1])); if (admin) { - const payload = JSON.parse(atob(sessionToken.split('.')[1])); if (payload.role !== 'Admin') { return false; } } + return payload.exp >= Date.now() / 1000; + } catch (e) { + return false; + } + } + + function refreshSession() { + if (!refreshToken) return false; + + try { + const payload = JSON.parse(atob(refreshToken.split('.')[1])); + if (payload.exp <= Date.now() / 1000) { + return false; + } + fetch('/api/auth/refresh', { + method: 'GET', + headers: { + Authorization: `Bearer ${refreshToken}`, + }, + }) + .then((res) => res.json()) + .then((data) => { + setAuthToken(data.sessionToken, data.refreshSession); + }); return true; } catch (e) { return false; @@ -40,21 +66,30 @@ export function AuthProvider({ const validate = useCallback(() => { const isValid = validateSession(); if (!isValid) { - if (admin) { - return router.push('/'); - } - return router.push('/auth'); - } else { - setIsValidating(false); - if (pathname.startsWith('/auth')) { - return router.push('/'); + if (!refreshSession()) { + clearAuthToken(); + if (admin) { + return router.push('/'); + } + return router.push('/auth'); } } - }, [pathname, validateSession, router, admin]); + setIsValidating(false); + if (pathname.startsWith('/auth')) { + return router.push('/'); + } + }, [ + router, + pathname, + clearAuthToken, + validateSession, + refreshSession, + admin, + ]); useEffect(() => { validate(); - }, [pathname, validate]); + }, [pathname, sessionToken, refreshToken, validate]); if (isValidating) { return ; @@ -65,6 +100,8 @@ export function AuthProvider({ export function SWRProvider({ children }: { children: React.ReactNode }) { return ( - new Map() }}>{children} + new Map() }}> + {children} + ); } diff --git a/packages/frontend/src/components/radix-ui-lib.tsx b/packages/frontend/src/components/radix-ui-lib.tsx index 85fde7ab..b106d628 100644 --- a/packages/frontend/src/components/radix-ui-lib.tsx +++ b/packages/frontend/src/components/radix-ui-lib.tsx @@ -10,7 +10,6 @@ import useInstallStore from '@/store/install'; import styles from '@/styles/module/radix-ui-lib.module.scss'; import { - ISettingResult, ISettingSchema, MultiInputSettingSchema, SelectSettingSchema, diff --git a/packages/frontend/src/components/sidebar/index.tsx b/packages/frontend/src/components/sidebar/index.tsx index 0bd65539..07576ba5 100644 --- a/packages/frontend/src/components/sidebar/index.tsx +++ b/packages/frontend/src/components/sidebar/index.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import Link from 'next/link'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import useSWR from 'swr'; @@ -107,15 +107,17 @@ function bindPassword() { } export function Sidebar({ children }: { children: React.ReactNode }) { + const router = useRouter(); const { - setSessionToken, + fetcher, + clearAuthToken, showSideBar, setShowSideBar, - fetcher, latestAnnouncementId, setLatestAnnouncementId, config, } = useStore(); + // componentState const [newbtnExpanded, setNewbtnExpanded] = useState(false); const [morebtnExpanded, setMorebtnExpanded] = useState(false); @@ -133,6 +135,7 @@ export function Sidebar({ children }: { children: React.ReactNode }) { } }), ); + const { data: userData, isLoading: isUserDataLoading } = useSWR( '/user/info', (url) => @@ -160,6 +163,11 @@ export function Sidebar({ children }: { children: React.ReactNode }) { }, ); + function logout() { + clearAuthToken(); + router.push('/auth'); + } + const loading = !useHasHydrated(); useSwitchTheme(); @@ -329,13 +337,7 @@ export function Sidebar({ children }: { children: React.ReactNode }) {
{ - setSessionToken(undefined); - setShowSideBar(false); - setNewbtnExpanded(false); - setMorebtnExpanded(false); - Router.reload(); - }} + onClick={logout} >
diff --git a/packages/frontend/src/store/install.ts b/packages/frontend/src/store/install.ts index 41a6bf92..b7657c58 100644 --- a/packages/frontend/src/store/install.ts +++ b/packages/frontend/src/store/install.ts @@ -1,12 +1,13 @@ import * as ramda from 'ramda'; import { create } from 'zustand'; -import { ISettingResult, ISettingResultValue } from 'shared'; +import { ISettingResultValue } from 'shared'; type pathLens = (string | number)[]; interface InstallStoreState { - settings: ISettingResult; + settings: any; + // settings: ISettingResult; _getFullPath: (key: pathLens) => pathLens; getSettingItem: (path: pathLens) => ISettingResultValue | undefined; updateSettingItem: (path: pathLens, value: ISettingResultValue) => void; diff --git a/packages/frontend/src/store/shared.ts b/packages/frontend/src/store/shared.ts index 892af12e..736936f5 100644 --- a/packages/frontend/src/store/shared.ts +++ b/packages/frontend/src/store/shared.ts @@ -27,8 +27,16 @@ export const createSharedStore: StateCreator = ( get, ) => ({ // Auth - setSessionToken(token: string | undefined) { - set({ sessionToken: token }); + setAuthToken(sessionToken, refreshToken) { + set({ sessionToken, refreshToken }); + }, + clearAuthToken() { + if (get().sessionToken) { + set({ sessionToken: undefined }); + } + if (get().refreshToken) { + set({ refreshToken: undefined }); + } }, // Model diff --git a/packages/frontend/src/store/types.ts b/packages/frontend/src/store/types.ts index e650ce2c..6850b687 100644 --- a/packages/frontend/src/store/types.ts +++ b/packages/frontend/src/store/types.ts @@ -30,7 +30,9 @@ export interface SharedSlice { // auth sessionToken?: string; - setSessionToken: (token: string | undefined) => void; + refreshToken?: string; + setAuthToken: (sessionToken: string, refreshToken: string) => void; + clearAuthToken: () => void; // layout latestAnnouncementId?: number; diff --git a/packages/frontend/src/styles/module/home.module.scss b/packages/frontend/src/styles/module/home.module.scss index ca164186..c61d2caf 100644 --- a/packages/frontend/src/styles/module/home.module.scss +++ b/packages/frontend/src/styles/module/home.module.scss @@ -286,7 +286,8 @@ } .chat-item-main { - flex: 1; + width: calc(100% - 50px); + overflow: hidden; } .chat-item-title { @@ -304,8 +305,12 @@ right: -20px; transition: all ease 0.3s; opacity: 0; + background: linear-gradient(to right, transparent, 5% , var(--white)); + padding-left: 10px; svg { + opacity: 0; + transition: all ease 0.3s; height: 20px; width: 20px; fill: var(--black); @@ -313,12 +318,18 @@ } .chat-item:hover>.chat-item-main>.chat-item-delete { - opacity: 0.5; + opacity: 1; right: 7.5px; + + svg { + opacity: 0.5; + } } .chat-item:hover>.chat-item-main>.chat-item-delete:hover { - opacity: 1; + svg { + opacity: 1; + } } .chat-item-info { diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 75be211f..4440b588 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -1,130 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: [ - './src/**/*.{js,ts,jsx,tsx,mdx}', - './node_modules/@tremor/**/*.{js,ts,jsx,tsx}', - ], - theme: { - transparent: 'transparent', - current: 'currentColor', - extend: { - colors: { - // light mode - tremor: { - brand: { - faint: '#eff6ff', // blue-50 - muted: '#bfdbfe', // blue-200 - subtle: '#60a5fa', // blue-400 - DEFAULT: '#3b82f6', // blue-500 - emphasis: '#1d4ed8', // blue-700 - inverted: '#ffffff', // white - }, - background: { - muted: '#f9fafb', // gray-50 - subtle: '#f3f4f6', // gray-100 - DEFAULT: '#ffffff', // white - emphasis: '#374151', // gray-700 - }, - border: { - DEFAULT: '#e5e7eb', // gray-200 - }, - ring: { - DEFAULT: '#e5e7eb', // gray-200 - }, - content: { - subtle: '#9ca3af', // gray-400 - DEFAULT: '#6b7280', // gray-500 - emphasis: '#374151', // gray-700 - strong: '#111827', // gray-900 - inverted: '#ffffff', // white - }, - }, - // dark mode - 'dark-tremor': { - brand: { - faint: '#0B1229', // custom - muted: '#172554', // blue-950 - subtle: '#1e40af', // blue-800 - DEFAULT: '#3b82f6', // blue-500 - emphasis: '#60a5fa', // blue-400 - inverted: '#030712', // gray-950 - }, - background: { - muted: '#131A2B', // custom - subtle: '#1f2937', // gray-800 - DEFAULT: '#111827', // gray-900 - emphasis: '#d1d5db', // gray-300 - }, - border: { - DEFAULT: '#1f2937', // gray-800 - }, - ring: { - DEFAULT: '#1f2937', // gray-800 - }, - content: { - subtle: '#4b5563', // gray-600 - DEFAULT: '#6b7280', // gray-500 - emphasis: '#e5e7eb', // gray-200 - strong: '#f9fafb', // gray-50 - inverted: '#000000', // black - }, - }, - }, - boxShadow: { - // light - 'tremor-input': '0 1px 2px 0 rgb(0 0 0 / 0.05)', - 'tremor-card': - '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', - 'tremor-dropdown': - '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', - // dark - 'dark-tremor-input': '0 1px 2px 0 rgb(0 0 0 / 0.05)', - 'dark-tremor-card': - '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', - 'dark-tremor-dropdown': - '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', - }, - borderRadius: { - 'tremor-small': '0.375rem', - 'tremor-default': '0.5rem', - 'tremor-full': '9999px', - }, - fontSize: { - 'tremor-label': ['0.75rem'], - 'tremor-default': ['0.875rem', { lineHeight: '1.25rem' }], - 'tremor-title': ['1.125rem', { lineHeight: '1.75rem' }], - 'tremor-metric': ['1.875rem', { lineHeight: '2.25rem' }], - }, - }, - }, - safelist: [ - { - pattern: - /^(bg-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, - variants: ['hover', 'ui-selected'], - }, - { - pattern: - /^(text-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, - variants: ['hover', 'ui-selected'], - }, - { - pattern: - /^(border-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, - variants: ['hover', 'ui-selected'], - }, - { - pattern: - /^(ring-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, - }, - { - pattern: - /^(stroke-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, - }, - { - pattern: - /^(fill-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, - }, - ], - plugins: [require('@headlessui/tailwindcss')], + content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], + theme: {}, + plugins: [], }; diff --git a/packages/shared/src/config.ts b/packages/shared/src/config.ts index db3c272d..e739491a 100644 --- a/packages/shared/src/config.ts +++ b/packages/shared/src/config.ts @@ -1,74 +1,245 @@ -export interface ConfigType { - mode: 'nginx' | 'docker' | 'debug'; - title: string; - frontend: { - port: number; - url: string; - }; - backend: { - port: number; - url: string; - }; - /* Deprecated in 3.1 */ - port?: { - frontend: number; - backend: number; - }; - jwt: { - algorithm: 'HS256' | 'ES256'; - secret?: string; - publicKey?: string; - privateKey?: string; - }; - redis: { - url: string; - }; - postgres: { - url: string; - }; - openai: { - baseUrl: string; - keys: string; - }; - sms: { - use?: 'disable' | 'aliyun' | 'tencent' | 'uni'; - uni?: { - signature: string; - templateId: string; - apiKey: string; - apiSecret?: string; - }; - }; - email: { - use?: 'disable' | 'smtp' | 'resend' | 'mailgun' | 'elastic'; - domain: string; - sender?: string; - smtp: {}; - resend: { - apiKey: string; - }; - mailgun: { - apiKey: string; - domain: string; - }; - elastic: { - apiKey: string; - }; - }; - wechat: { - oauth: { - appId: string; - appSecret: string; - }; - }; - payment: { - use: 'xunhu'; - xunhu?: { - wapName: string; - appId: string; - appSecret: string; - notifyUrl: string; - returnUrl: string; - }; - }; +import z, { ZodFirstPartyTypeKind, ZodString, ZodType, ZodTypeAny } from 'zod'; + +const useRefine = (schema: z.ZodObject) => + schema.refine( + (data: any) => + data.use !== 'disable' + ? data[data.use as Exclude] !== undefined + : true, + { + message: "When 'use' is not 'disable', the corresponding key must exist", + path: ['use'], + }, + ); + +const CompatibilityConfigSchema = z + .object({ + port: z.object({ + frontend: z.coerce.number().optional(), + backend: z.coerce.number().optional(), + }), + openai: z.object({ + baseUrl: z.string().url().default('https://api.openai.com'), + keys: z.array(z.string()).default(['']), + }), + }) + .partial(); + +const smsEnum = z.enum(['disable', 'aliyun', 'tencent', 'uni']); +type smsEnum = z.infer; +const smsSchema = useRefine( + z + .object({ + use: smsEnum.default('disable'), + uni: z.object({ + signature: z.string(), + templateId: z.string(), + apiKey: z.string(), + apiSecret: z.string().optional(), + }), + }) + .partial(), +); + +const emailEnum = z.enum(['disable', 'smtp', 'resend', 'mailgun', 'elastic']); +type emailEnum = z.infer; +const emailSchema = useRefine( + z + .object({ + use: emailEnum.default('disable'), + domain: z.string(), + sender: z.string(), + smtp: z.object({}), // 根据实际的 smtp 配置细化 + resend: z.object({ + apiKey: z.string(), + }), + mailgun: z.object({ + apiKey: z.string(), + domain: z.string(), + }), + elastic: z.object({ + apiKey: z.string(), + }), + }) + .partial(), +); + +const wechatSchema = z.object({ + oauth: z.object({ + appId: z.string(), + appSecret: z.string(), + }), +}); + +const paymentEnum = z.enum(['disable', 'xunhu']); +type paymentEnum = z.infer; + +const paymentSchema = useRefine( + z + .object({ + use: paymentEnum.default('disable'), + xunhu: z.object({ + wapName: z.string(), + appId: z.string(), + appSecret: z.string(), + notifyUrl: z.string().optional(), + returnUrl: z.string().optional(), + }), + }) + .partial(), +); + +const ThirdPartyServiceConfigSchema = z + .object({ + sms: smsSchema, + email: emailSchema, + wechat: wechatSchema, + payment: paymentSchema, + }) + .partial(); + +export const ConfigSchema = z + .object({ + mode: z.enum(['nginx', 'docker', 'debug']).optional().default('nginx'), + title: z.string().optional().default('ChatGPT Admin Web'), + frontend: z + .object({ + url: z.string().url().default('http://localhost:3000'), + port: z.coerce.number().default(3000), + }) + .optional(), + backend: z + .object({ + url: z.string().url().default('http://localhost:4000'), + port: z.coerce.number().default(3001), + }) + .optional(), + jwt: z + .object({ + algorithm: z.enum(['HS256', 'ES256']).default('HS256'), + secret: z.string().default('secret'), + refreshSecret: z.string().default('refreshSecret'), + privateKey: z.string(), + publicKey: z.string(), + }) + .partial() + .optional(), + redis: z.object({ + enable: z.boolean().default(true), + url: z.string().url().default('redis://localhost:6379'), + }), + postgres: z.object({ + url: z + .string() + .url() + .default('postgres://postgres:postgres@localhost:5432/postgres'), + }), + }) + .merge(CompatibilityConfigSchema) + .merge(ThirdPartyServiceConfigSchema); + +export type ConfigType = z.infer; + +export interface BaseSettingOptions { + label?: string; + value?: string | boolean | number; + isOptional?: boolean; + description?: string; +} + +export interface TypeSettingSchema extends BaseSettingOptions { + key: string; + type: 'switch' | 'input' | 'list'; + items?: never; +} + +export interface MultiInputSettingSchema extends BaseSettingOptions { + key: string; + keys: string[]; + type: 'multi-input'; + items?: never; +} + +export interface SelectSettingSchema extends BaseSettingOptions { + key: string; + type: 'select'; + items?: never; + selectOptions: string[]; +} + +export interface ItemsSettingSchema extends BaseSettingOptions { + key: string; + type?: never; + items: ISettingSchema[]; +} + +/* 渲染表单的结构 */ +export type ISettingSchema = + | TypeSettingSchema + | ItemsSettingSchema + | SelectSettingSchema + | MultiInputSettingSchema; + +export type ISettingResultValue = + | string + | boolean + | number + | string[] + | number[] + | Record[]; + +function convertZodSchemaToCustomSchema( + key: string, + zodSchema: z.ZodTypeAny, +): ISettingSchema { + const typeName = zodSchema._def.typeName; + switch (typeName) { + case ZodFirstPartyTypeKind.ZodString: + case ZodFirstPartyTypeKind.ZodNumber: + return { + key, + type: 'input' as const, + isOptional: zodSchema.isOptional(), + // default: zodSchema._def.defaultValue, + description: zodSchema._def.description, + }; + case ZodFirstPartyTypeKind.ZodObject: + const customSchema = []; + for (const key in zodSchema._def.shape) { + customSchema.push( + convertZodSchemaToCustomSchema(key, zodSchema._def.shape[key]), + ); + } + return { + key, + items: customSchema, + }; + // case ZodFirstPartyTypeKind.ZodArray: + // return { + // key, + // type: 'multi-input' as const, + // isOptional: zodSchema.isOptional(), + // // default: zodSchema._def.defaultValue, + // description: zodSchema._def.description, + // }; + case ZodFirstPartyTypeKind.ZodBoolean: + return { + key, + type: 'switch' as const, + isOptional: zodSchema.isOptional(), + // default: zodSchema._def.defaultValue, + description: zodSchema._def.description, + }; + case ZodFirstPartyTypeKind.ZodEnum: + return { + key, + type: 'select' as const, + selectOptions: zodSchema._def.values, + isOptional: zodSchema.isOptional(), + // default: zodSchema._def.defaultValue, + description: zodSchema._def.description, + }; + default: + throw 'Unknown type'; + } } diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index f273f402..c42391de 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -8,7 +8,6 @@ export * from './order'; export * from './product'; export * from './types/database'; export * from './user'; -export * from './install'; export type BaseResponse = BaseResponseSuccess | BaseResponseFailure; diff --git a/packages/shared/src/install.ts b/packages/shared/src/install.ts deleted file mode 100644 index c9316592..00000000 --- a/packages/shared/src/install.ts +++ /dev/null @@ -1,53 +0,0 @@ -export interface BaseSettingOptions { - label: string; - description?: string; - isOptional?: boolean; - value?: string | boolean | number; -} - -export interface TypeSettingSchema extends BaseSettingOptions { - key: string; - type: 'switch' | 'input' | 'list'; - items?: never; -} - -export interface MultiInputSettingSchema extends BaseSettingOptions { - key: string; - keys: string[]; - type: 'multi-input'; - items?: never; -} - -export interface SelectSettingSchema extends BaseSettingOptions { - key: string; - type: 'select'; - items?: never; - selectOptions: string[]; -} - -export interface ItemsSettingSchema extends BaseSettingOptions { - key: string; - type?: never; - items: ISettingSchema[]; -} - -/* 渲染表单的结构 */ -export type ISettingSchema = - | TypeSettingSchema - | ItemsSettingSchema - | SelectSettingSchema - | MultiInputSettingSchema; - -export type ISettingResultValue = - | string - | boolean - | number - | string[] - | number[] - | Record[] - | ISettingResult; - -/* 回传表单的数据 */ -export interface ISettingResult { - [key: string]: ISettingResultValue; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62f60552..3a947acc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,9 +203,6 @@ importers: '@svgr/webpack': specifier: ^8.1.0 version: 8.1.0(typescript@5.2.2) - '@tremor/react': - specifier: ^3.11.1 - version: 3.11.1(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.3.5) '@types/ramda': specifier: ^0.29.7 version: 0.29.7 @@ -333,6 +330,7 @@ packages: /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + dev: true /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} @@ -1703,6 +1701,7 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/trace-mapping': 0.3.9 + dev: true /@esbuild/android-arm64@0.19.8: resolution: {integrity: sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==} @@ -2001,17 +2000,6 @@ packages: '@floating-ui/utils': 0.1.6 dev: false - /@floating-ui/react-dom@1.3.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - '@floating-ui/dom': 1.5.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@floating-ui/react-dom@2.0.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==} peerDependencies: @@ -2023,19 +2011,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@floating-ui/react@0.19.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - '@floating-ui/react-dom': 1.3.0(react-dom@18.2.0)(react@18.2.0) - aria-hidden: 1.2.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - tabbable: 6.2.0 - dev: false - /@floating-ui/utils@0.1.6: resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} dev: false @@ -2050,27 +2025,6 @@ packages: '@hapi/hoek': 9.3.0 dev: false - /@headlessui/react@1.7.17(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==} - engines: {node: '>=10'} - peerDependencies: - react: ^16 || ^17 || ^18 - react-dom: ^16 || ^17 || ^18 - dependencies: - client-only: 0.0.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@headlessui/tailwindcss@0.1.3(tailwindcss@3.3.5): - resolution: {integrity: sha512-3aMdDyYZx9A15euRehpppSyQnb2gIw2s/Uccn2ELIoLQ9oDy0+9oRygNWNjXCD5Dt+w1pxo7C+XoiYvGcqA4Kg==} - engines: {node: '>=10'} - peerDependencies: - tailwindcss: ^3.0 - dependencies: - tailwindcss: 3.3.5(ts-node@10.9.1) - dev: false - /@humanwhocodes/config-array@0.11.10: resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} engines: {node: '>=10.10.0'} @@ -2378,6 +2332,7 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + dev: true /@liaoliaots/nestjs-redis@9.0.5(@nestjs/common@10.2.8)(@nestjs/core@10.2.8)(ioredis@5.3.2): resolution: {integrity: sha512-nPcGLj0zW4mEsYtQYfWx3o7PmrMjuzFk6+t/g2IRopAeWWUZZ/5nIJ4KTKiz/3DJEUkbX8PZqB+dOhklGF0SVA==} @@ -4145,27 +4100,6 @@ packages: tslib: 2.6.2 dev: false - /@tremor/react@3.11.1(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.3.5): - resolution: {integrity: sha512-oiBm8vFe0+05RFIHlriSmfZX7BMwgAIFGdvz5kAEbN6G/cGOh2oPkTGG+NPbbk8eyo68f13IT6KfTiMVSEhRSA==} - peerDependencies: - react: ^18.0.0 - react-dom: '>=16.6.0' - dependencies: - '@floating-ui/react': 0.19.2(react-dom@18.2.0)(react@18.2.0) - '@headlessui/react': 1.7.17(react-dom@18.2.0)(react@18.2.0) - '@headlessui/tailwindcss': 0.1.3(tailwindcss@3.3.5) - date-fns: 2.30.0 - react: 18.2.0 - react-day-picker: 8.9.1(date-fns@2.30.0)(react@18.2.0) - react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - recharts: 2.10.1(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) - tailwind-merge: 1.14.0 - transitivePeerDependencies: - - prop-types - - tailwindcss - dev: false - /@trivago/prettier-plugin-sort-imports@4.2.1(prettier@3.0.3): resolution: {integrity: sha512-iuy2MPVURGdxILTchHr15VAioItuYBejKfcTmQFlxIuqA7jeaT6ngr5aUIG6S6U096d6a6lJCgaOwlRrPLlOPg==} peerDependencies: @@ -4193,15 +4127,19 @@ packages: /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true /@tsconfig/node12@1.0.11: resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true /@tsconfig/node14@1.0.3: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true /@tsconfig/node16@1.0.4: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true /@types/babel__core@7.20.1: resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} @@ -4236,48 +4174,6 @@ packages: resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==} dev: true - /@types/d3-array@3.2.1: - resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} - dev: false - - /@types/d3-color@3.1.3: - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - dev: false - - /@types/d3-ease@3.0.2: - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - dev: false - - /@types/d3-interpolate@3.0.4: - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - dependencies: - '@types/d3-color': 3.1.3 - dev: false - - /@types/d3-path@3.0.2: - resolution: {integrity: sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==} - dev: false - - /@types/d3-scale@4.0.8: - resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} - dependencies: - '@types/d3-time': 3.0.3 - dev: false - - /@types/d3-shape@3.1.5: - resolution: {integrity: sha512-dfEWpZJ1Pdg8meLlICX1M3WBIpxnaH2eQV2eY43Y5ysRJOTAV9f3/R++lgJKFstfrEOE2zdJ0sv5qwr2Bkic6Q==} - dependencies: - '@types/d3-path': 3.0.2 - dev: false - - /@types/d3-time@3.0.3: - resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} - dev: false - - /@types/d3-timer@3.0.2: - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - dev: false - /@types/debug@4.1.9: resolution: {integrity: sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==} dependencies: @@ -4388,6 +4284,7 @@ packages: resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==} dependencies: undici-types: 5.26.5 + dev: true /@types/node@20.8.3: resolution: {integrity: sha512-jxiZQFpb+NlH5kjW49vXxvxTjeeqlbsnTAdBTKpzEdPs9itay7MscYXz3Fo9VYFEsfQ6LJFitHad3faerLAjCw==} @@ -4798,6 +4695,7 @@ packages: /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} + dev: true /acorn-walk@8.3.1: resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} @@ -4924,6 +4822,7 @@ packages: /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -4950,9 +4849,11 @@ packages: /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -5349,6 +5250,7 @@ packages: /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + dev: true /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} @@ -5570,6 +5472,7 @@ packages: /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + dev: true /commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} @@ -5649,6 +5552,7 @@ packages: /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -5697,6 +5601,7 @@ packages: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true + dev: true /csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} @@ -5708,88 +5613,10 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - /d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} - dependencies: - internmap: 2.0.3 - dev: false - - /d3-color@3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} - engines: {node: '>=12'} - dev: false - - /d3-ease@3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} - engines: {node: '>=12'} - dev: false - - /d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} - engines: {node: '>=12'} - dev: false - - /d3-interpolate@3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} - engines: {node: '>=12'} - dependencies: - d3-color: 3.1.0 - dev: false - - /d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - dev: false - - /d3-scale@4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} - engines: {node: '>=12'} - dependencies: - d3-array: 3.2.4 - d3-format: 3.1.0 - d3-interpolate: 3.0.1 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - dev: false - - /d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - dependencies: - d3-path: 3.1.0 - dev: false - - /d3-time-format@4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} - engines: {node: '>=12'} - dependencies: - d3-time: 3.1.0 - dev: false - - /d3-time@3.1.0: - resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} - engines: {node: '>=12'} - dependencies: - d3-array: 3.2.4 - dev: false - - /d3-timer@3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} - engines: {node: '>=12'} - dev: false - /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: false - /date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - dependencies: - '@babel/runtime': 7.22.10 - dev: false - /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -5812,10 +5639,6 @@ packages: dependencies: ms: 2.1.2 - /decimal.js-light@2.5.1: - resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - dev: false - /decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: @@ -5899,6 +5722,7 @@ packages: /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: true /diff-sequences@29.4.3: resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} @@ -5913,6 +5737,7 @@ packages: /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + dev: true /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} @@ -5934,6 +5759,7 @@ packages: /dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: true /doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} @@ -5948,19 +5774,6 @@ packages: dependencies: esutils: 2.0.3 - /dom-helpers@3.4.0: - resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==} - dependencies: - '@babel/runtime': 7.22.10 - dev: false - - /dom-helpers@5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dependencies: - '@babel/runtime': 7.22.10 - csstype: 3.1.2 - dev: false - /dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} dependencies: @@ -6572,10 +6385,6 @@ packages: engines: {node: '>=6'} dev: false - /eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: false - /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -6670,11 +6479,6 @@ packages: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: true - /fast-equals@5.0.1: - resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} - engines: {node: '>=6.0.0'} - dev: false - /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -7046,6 +6850,7 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + dev: true /glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} @@ -7376,11 +7181,6 @@ packages: side-channel: 1.0.4 dev: false - /internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} - dev: false - /interpret@1.4.0: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} @@ -8127,6 +7927,7 @@ packages: /jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + dev: true /joi@17.9.2: resolution: {integrity: sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==} @@ -8280,10 +8081,12 @@ packages: /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} + dev: true /lilconfig@3.0.0: resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} engines: {node: '>=14'} + dev: true /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -8423,6 +8226,7 @@ packages: /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true /makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -8999,6 +8803,7 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + dev: true /nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} @@ -9176,6 +8981,7 @@ packages: /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + dev: true /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} @@ -9451,6 +9257,7 @@ packages: /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + dev: true /pino-abstract-transport@1.1.0: resolution: {integrity: sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==} @@ -9483,6 +9290,7 @@ packages: /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + dev: true /pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} @@ -9514,6 +9322,7 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.4 + dev: true /postcss-js@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -9523,6 +9332,7 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.31 + dev: true /postcss-load-config@4.0.2(postcss@8.4.31)(ts-node@10.9.1): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} @@ -9540,6 +9350,7 @@ packages: postcss: 8.4.31 ts-node: 10.9.1(@types/node@20.8.10)(typescript@5.2.2) yaml: 2.3.4 + dev: true /postcss-nested@6.0.1(postcss@8.4.31): resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} @@ -9549,6 +9360,7 @@ packages: dependencies: postcss: 8.4.31 postcss-selector-parser: 6.0.13 + dev: true /postcss-selector-parser@6.0.13: resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} @@ -9556,9 +9368,11 @@ packages: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 + dev: true /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: true /postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} @@ -9719,16 +9533,6 @@ packages: safe-buffer: 5.2.1 dev: true - /react-day-picker@8.9.1(date-fns@2.30.0)(react@18.2.0): - resolution: {integrity: sha512-W0SPApKIsYq+XCtfGeMYDoU0KbsG3wfkYtlw8l+vZp6KoBXGOlhzBUp4tNx1XiwiOZwhfdGOlj7NGSCKGSlg5Q==} - peerDependencies: - date-fns: ^2.28.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - date-fns: 2.30.0 - react: 18.2.0 - dev: false - /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -9755,10 +9559,6 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - /react-lifecycles-compat@3.0.4: - resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} - dev: false - /react-markdown@8.0.5(@types/react@18.0.28)(react@18.2.0): resolution: {integrity: sha512-jGJolWWmOWAvzf+xMdB9zwStViODyyFQhNB/bwCerbBKmrTmgmA599CGiOlP58OId1IMoIRsA8UdI1Lod4zb5A==} peerDependencies: @@ -9821,20 +9621,6 @@ packages: use-sidecar: 1.1.2(@types/react@18.0.28)(react@18.2.0) dev: false - /react-smooth@2.0.5(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA==} - peerDependencies: - prop-types: ^15.6.0 - react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - dependencies: - fast-equals: 5.0.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-transition-group: 2.9.0(react-dom@18.2.0)(react@18.2.0) - dev: false - /react-style-singleton@2.2.1(@types/react@18.0.28)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -9852,34 +9638,6 @@ packages: tslib: 2.6.2 dev: false - /react-transition-group@2.9.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==} - peerDependencies: - react: '>=15.0.0' - react-dom: '>=15.0.0' - dependencies: - dom-helpers: 3.4.0 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-lifecycles-compat: 3.0.4 - dev: false - - /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - dependencies: - '@babel/runtime': 7.22.10 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -9891,6 +9649,7 @@ packages: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: pify: 2.3.0 + dev: true /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} @@ -9922,33 +9681,6 @@ packages: engines: {node: '>= 12.13.0'} dev: false - /recharts-scale@0.4.5: - resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} - dependencies: - decimal.js-light: 2.5.1 - dev: false - - /recharts@2.10.1(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-9bi0jIzxOTfEda+oYqgimKuYfApmBr0zKnAX8r4Iw56k3Saz/IQyBD4zohZL0eyzfz0oGFRH7alpJBgH1eC57g==} - engines: {node: '>=14'} - peerDependencies: - prop-types: ^15.6.0 - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 - dependencies: - clsx: 2.0.0 - eventemitter3: 4.0.7 - lodash: 4.17.21 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 16.13.1 - react-smooth: 2.0.5(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) - recharts-scale: 0.4.5 - tiny-invariant: 1.3.1 - victory-vendor: 36.6.12 - dev: false - /rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -10638,6 +10370,7 @@ packages: mz: 2.7.0 pirates: 4.0.6 ts-interface-checker: 0.1.13 + dev: true /superagent@8.0.9: resolution: {integrity: sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==} @@ -10723,14 +10456,6 @@ packages: engines: {node: '>=0.10'} dev: true - /tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - dev: false - - /tailwind-merge@1.14.0: - resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} - dev: false - /tailwindcss@3.3.5(ts-node@10.9.1): resolution: {integrity: sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==} engines: {node: '>=14.0.0'} @@ -10760,6 +10485,7 @@ packages: sucrase: 3.34.0 transitivePeerDependencies: - ts-node + dev: true /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} @@ -10829,11 +10555,13 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 + dev: true /thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 + dev: true /thread-stream@2.4.1: resolution: {integrity: sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==} @@ -10845,10 +10573,6 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true - /tiny-invariant@1.3.1: - resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} - dev: false - /tinybench@2.5.1: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} dev: true @@ -10907,6 +10631,7 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true /ts-jest@29.1.0(@babel/core@7.22.10)(jest@29.5.0)(typescript@5.2.2): resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} @@ -10987,6 +10712,7 @@ packages: typescript: 5.2.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + dev: true /ts-node@10.9.1(@types/node@20.8.3)(typescript@5.2.2): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} @@ -11444,6 +11170,7 @@ packages: /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true /v8-to-istanbul@9.1.0: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} @@ -11477,25 +11204,6 @@ packages: vfile-message: 3.1.4 dev: false - /victory-vendor@36.6.12: - resolution: {integrity: sha512-pJrTkNHln+D83vDCCSUf0ZfxBvIaVrFHmrBOsnnLAbdqfudRACAj51He2zU94/IWq9464oTADcPVkmWAfNMwgA==} - dependencies: - '@types/d3-array': 3.2.1 - '@types/d3-ease': 3.0.2 - '@types/d3-interpolate': 3.0.4 - '@types/d3-scale': 4.0.8 - '@types/d3-shape': 3.1.5 - '@types/d3-time': 3.0.3 - '@types/d3-timer': 3.0.2 - d3-array: 3.2.4 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-scale: 4.0.2 - d3-shape: 3.2.0 - d3-time: 3.1.0 - 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} @@ -11820,6 +11528,7 @@ packages: /yaml@2.3.4: resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} engines: {node: '>= 14'} + dev: true /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} @@ -11842,6 +11551,7 @@ packages: /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + dev: true /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} diff --git a/test/dto/index.spec.ts b/test/dto/index.spec.ts index 67c6dc54..ed9063e1 100644 --- a/test/dto/index.spec.ts +++ b/test/dto/index.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; -import { AuthDTO } from 'shared'; +import { AuthDTO, ConfigSchema } from 'shared'; describe('[DTO] Auth', () => { test('normal email is safe for identity', () => { @@ -19,3 +19,61 @@ describe('[DTO] Auth', () => { expect(AuthDTO.codeSchema.safeParse('123456')).toBeSafe(); }); }); + +const DEFAULT_CONFIG = { + mode: 'nginx', + title: 'ChatGPT Admin Web', + frontend: { + port: '3000', + url: 'https://localhost:3000', + }, + backend: { + port: '3001', + url: 'http://localhost:3001', + }, + postgres: { + url: 'postgres://postgres:t@localhost:5433/postgres', + }, + redis: { + url: 'redis://localhost:6379/0', + enable: false, + }, + jwt: { + algorithm: 'HS256', + secret: 'ffffff', + }, +}; + +describe('Config', () => { + test('default schema is safe', () => { + expect(ConfigSchema.safeParse(DEFAULT_CONFIG)).toBeSafe(); + }); + test('string/number port is safe', () => { + expect( + ConfigSchema.safeParse({ + ...DEFAULT_CONFIG, + frontend: { + ...DEFAULT_CONFIG.frontend, + port: '3000', + }, + backend: { + ...DEFAULT_CONFIG.backend, + port: 3001, + }, + }), + ).toBeSafe(); + }); + test('compatible with older port representations', () => { + expect( + ConfigSchema.safeParse({ + ...DEFAULT_CONFIG, + frontend: undefined, + backend: undefined, + port: { + frontend: '3000', + backend: 3001, + }, + }), + ).toBeSafe(); + }); +}); diff --git a/test/schema.ts b/test/schema.ts index a994b3bf..186babe1 100644 --- a/test/schema.ts +++ b/test/schema.ts @@ -1,17 +1 @@ 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 index 3fc122d6..46be3d5b 100644 --- a/test/setup-file.ts +++ b/test/setup-file.ts @@ -1,6 +1,31 @@ import { expect } from 'vitest'; +import { ZodError } from 'zod'; -import { toBeSafe } from '@test/schema'; +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`, + }; +} + +interface CustomMatchers { + toBeSafe(): R; +} + +declare module 'vitest' { + interface Assertion extends CustomMatchers {} + + interface AsymmetricMatchersContaining extends CustomMatchers {} +} expect.extend({ toBeSafe }); diff --git a/test/tsconfig.json b/test/tsconfig.json index 623dd425..b14a6f50 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -55,7 +55,6 @@ }, "include": [ "./**/*.ts", - "./vitest.d.ts" ], "exclude": [ "node_modules" diff --git a/test/vitest.d.ts b/test/vitest.d.ts deleted file mode 100644 index 074e46a4..00000000 --- a/test/vitest.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -interface CustomMatchers { - toBeFoo(): R; -} - -declare module 'vitest' { - interface Assertion extends CustomMatchers {} - - interface AsymmetricMatchersContaining extends CustomMatchers {} -}