@qlover/create-app 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/next-app/config/IOCIdentifier.ts +2 -2
  5. package/dist/templates/next-app/config/Identifier/common/common.ts +14 -0
  6. package/dist/templates/next-app/config/Identifier/pages/index.ts +1 -0
  7. package/dist/templates/next-app/config/Identifier/pages/page.about.ts +20 -0
  8. package/dist/templates/next-app/config/common.ts +1 -1
  9. package/dist/templates/next-app/config/cookies.ts +23 -0
  10. package/dist/templates/next-app/config/i18n/AboutI18n.ts +14 -0
  11. package/dist/templates/next-app/config/i18n/i18nConfig.ts +3 -1
  12. package/dist/templates/next-app/config/i18n/index.ts +1 -0
  13. package/dist/templates/next-app/config/i18n/loginI18n.ts +8 -0
  14. package/dist/templates/next-app/config/theme.ts +4 -0
  15. package/dist/templates/next-app/eslint.config.mjs +4 -1
  16. package/dist/templates/next-app/next.config.ts +1 -0
  17. package/dist/templates/next-app/package.json +13 -4
  18. package/dist/templates/next-app/public/locales/en.json +5 -0
  19. package/dist/templates/next-app/public/locales/zh.json +5 -0
  20. package/dist/templates/next-app/src/app/[locale]/admin/AdminI18nProvider.tsx +37 -0
  21. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +30 -6
  22. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +1 -1
  23. package/dist/templates/next-app/src/app/[locale]/layout.tsx +47 -10
  24. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +1 -1
  25. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +22 -10
  26. package/dist/templates/next-app/src/app/[locale]/page.tsx +23 -8
  27. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +21 -9
  28. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +7 -28
  29. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +7 -34
  30. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +12 -34
  31. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +7 -26
  32. package/dist/templates/next-app/src/app/api/admin/users/route.ts +14 -33
  33. package/dist/templates/next-app/src/app/api/locales/json/route.ts +13 -25
  34. package/dist/templates/next-app/src/app/api/user/login/route.ts +6 -46
  35. package/dist/templates/next-app/src/app/api/user/logout/route.ts +5 -24
  36. package/dist/templates/next-app/src/app/api/user/register/route.ts +6 -45
  37. package/dist/templates/next-app/src/app/manifest.ts +16 -0
  38. package/dist/templates/next-app/src/app/robots.txt +2 -0
  39. package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +12 -2
  40. package/dist/templates/next-app/src/base/cases/RouterService.ts +5 -5
  41. package/dist/templates/next-app/src/base/port/AppApiInterface.ts +22 -0
  42. package/dist/templates/next-app/src/base/port/IOCInterface.ts +9 -0
  43. package/dist/templates/next-app/src/base/types/{PageProps.ts → AppPageRouter.ts} +4 -1
  44. package/dist/templates/next-app/src/base/types/PagesRouter.ts +9 -0
  45. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +19 -5
  46. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +2 -2
  47. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +0 -1
  48. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +29 -8
  49. package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +4 -2
  50. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +25 -7
  51. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +1 -1
  52. package/dist/templates/next-app/src/i18n/loadMessages.ts +103 -0
  53. package/dist/templates/next-app/src/i18n/request.ts +3 -22
  54. package/dist/templates/next-app/src/pages/[locale]/about.tsx +61 -0
  55. package/dist/templates/next-app/src/pages/_app.tsx +50 -0
  56. package/dist/templates/next-app/src/pages/_document.tsx +13 -0
  57. package/dist/templates/next-app/src/{middleware.ts → proxy.ts} +2 -1
  58. package/dist/templates/next-app/src/server/AppPageRouteParams.ts +94 -0
  59. package/dist/templates/next-app/src/server/NextApiServer.ts +53 -0
  60. package/dist/templates/next-app/src/server/PagesRouteParams.ts +136 -0
  61. package/dist/templates/next-app/src/server/{sqlBridges/SupabaseBridge.ts → SupabaseBridge.ts} +2 -0
  62. package/dist/templates/next-app/src/server/controllers/AdminLocalesController.ts +74 -0
  63. package/dist/templates/next-app/src/server/controllers/AdminUserController.ts +39 -0
  64. package/dist/templates/next-app/src/server/controllers/LocalesController.ts +33 -0
  65. package/dist/templates/next-app/src/server/controllers/UserController.ts +77 -0
  66. package/dist/templates/next-app/src/server/port/AIControllerInterface.ts +8 -0
  67. package/dist/templates/next-app/src/server/port/AdminLocalesControllerInterface.ts +21 -0
  68. package/dist/templates/next-app/src/server/port/AdminUserControllerInterface.ts +11 -0
  69. package/dist/templates/next-app/src/server/port/LocalesControllerInterface.ts +10 -0
  70. package/dist/templates/next-app/src/server/port/{ParamsHandlerInterface.ts → RouteParamsnHandlerInterface.ts} +9 -2
  71. package/dist/templates/next-app/src/server/port/ServerInterface.ts +2 -2
  72. package/dist/templates/next-app/src/server/port/UserControllerInerface.ts +8 -0
  73. package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +1 -1
  74. package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +2 -2
  75. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +8 -2
  76. package/dist/templates/next-app/src/{base → server}/services/AdminLocalesService.ts +2 -2
  77. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +25 -10
  78. package/dist/templates/next-app/src/server/services/ApiUserService.ts +5 -2
  79. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +4 -2
  80. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +1 -1
  81. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +10 -10
  82. package/dist/templates/next-app/src/styles/css/antd-themes/_common/_default.css +0 -44
  83. package/dist/templates/next-app/src/styles/css/antd-themes/_common/dark.css +0 -44
  84. package/dist/templates/next-app/src/styles/css/antd-themes/_common/pink.css +0 -44
  85. package/dist/templates/next-app/src/styles/css/index.css +1 -1
  86. package/dist/templates/next-app/src/styles/css/scrollbar.css +34 -0
  87. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +34 -11
  88. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +69 -39
  89. package/dist/templates/next-app/src/uikit/components/ClientRootProvider.tsx +64 -0
  90. package/dist/templates/next-app/src/uikit/components/ClinetRenderProvider.tsx +42 -0
  91. package/dist/templates/next-app/src/uikit/components/IOCProvider.tsx +34 -0
  92. package/dist/templates/next-app/src/uikit/components-app/AppBridge.tsx +17 -0
  93. package/dist/templates/next-app/src/uikit/components-app/AppRoutePage.tsx +112 -0
  94. package/dist/templates/next-app/src/uikit/{components → components-app}/LanguageSwitcher.tsx +15 -19
  95. package/dist/templates/next-app/src/uikit/{components → components-app}/ThemeSwitcher.tsx +53 -52
  96. package/dist/templates/next-app/src/uikit/components-pages/LanguageSwitcher.tsx +98 -0
  97. package/dist/templates/next-app/src/uikit/components-pages/PagesRoutePage.tsx +93 -0
  98. package/dist/templates/next-app/src/uikit/context/IOCContext.ts +16 -4
  99. package/dist/templates/next-app/src/uikit/hook/useStrictEffect.ts +32 -0
  100. package/dist/templates/next-app/tsconfig.json +3 -2
  101. package/package.json +1 -1
  102. package/dist/templates/next-app/src/server/PageParams.ts +0 -66
  103. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +0 -80
  104. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +0 -65
  105. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +0 -58
  106. package/dist/templates/next-app/src/uikit/components/NextIntlProvider.tsx +0 -21
  107. /package/dist/templates/next-app/{src/app/[locale] → public}/favicon.ico +0 -0
  108. /package/dist/templates/next-app/src/uikit/{components → components-app}/LogoutButton.tsx +0 -0
@@ -0,0 +1,61 @@
1
+ import { useMemo } from 'react';
2
+ import { PagesRouteParams } from '@/server/PagesRouteParams';
3
+ import type { PagesRouteParamsType } from '@/server/PagesRouteParams';
4
+ import { ClientSeo } from '@/uikit/components/ClientSeo';
5
+ import { PagesRoutePage } from '@/uikit/components-pages/PagesRoutePage';
6
+ import { useI18nInterface } from '@/uikit/hook/useI18nInterface';
7
+ import { aboutI18n, i18nConfig } from '@config/i18n';
8
+ import { COMMON_ADMIN_TITLE } from '@config/Identifier';
9
+ import type { GetStaticPropsContext } from 'next';
10
+
11
+ interface AboutProps {
12
+ messages: Record<string, string>;
13
+ }
14
+
15
+ const namespace = 'page_about';
16
+
17
+ export default function About({}: AboutProps) {
18
+ const i18nInterface = useMemo(() => {
19
+ return {
20
+ ...aboutI18n,
21
+ adminTitle: COMMON_ADMIN_TITLE
22
+ };
23
+ }, []);
24
+ const seoMetadata = useI18nInterface(i18nInterface);
25
+
26
+ return (
27
+ <PagesRoutePage
28
+ data-testid="AboutPage"
29
+ tt={{ title: seoMetadata.title, adminTitle: seoMetadata.adminTitle }}
30
+ showAdminButton={false}
31
+ >
32
+ <ClientSeo i18nInterface={seoMetadata} />
33
+ <div data-testid="About" className="bg-primary h-screen">
34
+ {seoMetadata.title}
35
+ </div>
36
+ </PagesRoutePage>
37
+ );
38
+ }
39
+
40
+ export async function getStaticProps({
41
+ params
42
+ }: GetStaticPropsContext<PagesRouteParamsType>) {
43
+ const pageParams = new PagesRouteParams(params);
44
+
45
+ const messages = await pageParams.getI18nMessages(namespace);
46
+
47
+ return {
48
+ props: {
49
+ messages
50
+ }
51
+ };
52
+ }
53
+
54
+ export async function getStaticPaths() {
55
+ return {
56
+ paths: i18nConfig.supportedLngs.map((locale) => ({
57
+ params: { locale }
58
+ })),
59
+ fallback: false
60
+ };
61
+ }
@@ -0,0 +1,50 @@
1
+ import dynamic from 'next/dynamic';
2
+ import { withRouter } from 'next/router';
3
+ import { NextIntlClientProvider } from 'next-intl';
4
+ import '@/styles/css/index.css';
5
+ import type { PagesRouterProps } from '@/base/types/PagesRouter';
6
+ import { BootstrapsProvider } from '@/uikit/components/BootstrapsProvider';
7
+ import { IOCProvider } from '@/uikit/components/IOCProvider';
8
+ import { i18nConfig } from '@config/i18n';
9
+ import { themeConfig } from '@config/theme';
10
+
11
+ /**
12
+ * 动态导入 ClientRootProvider,禁用 SSR,确保只在客户端渲染
13
+ *
14
+ * 为什么需要禁用 SSR?
15
+ * 1. ClientRootProvider 内部使用了 AntdRegistry(来自 @ant-design/nextjs-registry),
16
+ * 该组件主要针对 App Router 设计,在 Pages Router 的 SSR 环境中可能存在兼容性问题
17
+ * 2. ClientRootProvider 使用了 useIOC() hook,该 hook 在服务端渲染时可能无法正常工作
18
+ * 3. ThemeProvider 和 AntdThemeProvider 可能依赖浏览器 API(如 localStorage、window 等),
19
+ * 在服务端渲染时会导致错误或页面一直处于加载状态
20
+ *
21
+ * 通过在 Pages Router 中使用 dynamic import 并设置 ssr: false,
22
+ * 可以确保 ClientRootProvider 只在客户端渲染,避免 SSR 相关的问题
23
+ */
24
+ const ClientRootProvider = dynamic(
25
+ () =>
26
+ import('@/uikit/components/ClientRootProvider').then(
27
+ (mod) => mod.ClientRootProvider
28
+ ),
29
+ {
30
+ ssr: false
31
+ }
32
+ );
33
+
34
+ function App({ Component, pageProps, router }: PagesRouterProps) {
35
+ const locale = (router.query.locale as string) || i18nConfig.fallbackLng;
36
+
37
+ return (
38
+ <IOCProvider data-testid="IOCProvider">
39
+ <NextIntlClientProvider locale={locale} messages={pageProps.messages}>
40
+ <BootstrapsProvider>
41
+ <ClientRootProvider themeConfig={themeConfig}>
42
+ <Component {...pageProps} />
43
+ </ClientRootProvider>
44
+ </BootstrapsProvider>
45
+ </NextIntlClientProvider>
46
+ </IOCProvider>
47
+ );
48
+ }
49
+
50
+ export default withRouter(App);
@@ -0,0 +1,13 @@
1
+ import { Html, Head, Main, NextScript } from 'next/document';
2
+
3
+ export default function Document() {
4
+ return (
5
+ <Html data-testid="PagesRootLayout">
6
+ <Head />
7
+ <body>
8
+ <Main />
9
+ <NextScript />
10
+ </body>
11
+ </Html>
12
+ );
13
+ }
@@ -20,6 +20,7 @@ export const config = {
20
20
  // - Next.js internals (_next/*)
21
21
  // - Static files (*.svg, *.png, *.jpg, *.jpeg, *.gif, *.ico)
22
22
  // - Other static assets and special files
23
- '/((?!api|_next|.*\\.(?:svg|png|jpg|jpeg|gif|ico)|favicon.ico|sitemap.xml|sitemap-0.xml).*)'
23
+ // - Manifest file (manifest.webmanifest)
24
+ '/((?!api|_next|.*\\.(?:svg|png|jpg|jpeg|gif|ico)|favicon.ico|sitemap.xml|sitemap-0.xml|manifest.webmanifest).*)'
24
25
  ]
25
26
  };
@@ -0,0 +1,94 @@
1
+ import { cookies } from 'next/headers';
2
+ import { notFound } from 'next/navigation';
3
+ import { getMessages, getTranslations } from 'next-intl/server';
4
+ import { TranslateI18nInterface } from '@/base/cases/TranslateI18nInterface';
5
+ import { filterMessagesByNamespace } from '@/i18n/loadMessages';
6
+ import { i18nConfig } from '@config/i18n';
7
+ import type { LocaleType, PageI18nInterface } from '@config/i18n';
8
+ import { themeConfig } from '@config/theme';
9
+ import type { RouteParamsnHandlerInterface } from './port/RouteParamsnHandlerInterface';
10
+
11
+ export interface PageWithParams {
12
+ params?: Promise<PageParamsType>;
13
+ }
14
+
15
+ export interface PageParamsType {
16
+ locale?: string;
17
+ }
18
+
19
+ /**
20
+ * 用于 src/app/page 路由的参数管理工具
21
+ */
22
+ export class AppPageRouteParams<
23
+ T extends PageParamsType
24
+ > implements RouteParamsnHandlerInterface {
25
+ private locale: string | null;
26
+
27
+ constructor(protected readonly params: T) {
28
+ this.locale = this.params.locale || i18nConfig.fallbackLng;
29
+ }
30
+
31
+ public getLocale(defaultLocale?: string): string {
32
+ if (this.locale) {
33
+ return this.locale;
34
+ }
35
+
36
+ this.locale = this.params.locale || defaultLocale || i18nConfig.fallbackLng;
37
+
38
+ return this.locale;
39
+ }
40
+
41
+ public getI18nWithNotFound(): string {
42
+ const locale = this.getLocale();
43
+
44
+ if (!i18nConfig.supportedLngs.includes(locale as LocaleType)) {
45
+ notFound();
46
+ }
47
+
48
+ return locale;
49
+ }
50
+
51
+ /**
52
+ * 获取 i18n 消息
53
+ * 使用 next-intl 的 getMessages 加载消息
54
+ *
55
+ * @param namespace - 可选的命名空间(单个字符串或字符串数组),会与默认命名空间 ['common', 'api'] 合并
56
+ * @returns Promise<Record<string, string>> 返回翻译消息对象
57
+ */
58
+ public async getI18nMessages(
59
+ namespace?: string | string[]
60
+ ): Promise<Record<string, string>> {
61
+ const locale = this.getLocale();
62
+ const messages = await getMessages({ locale });
63
+ // 将默认命名空间和用户提供的命名空间合并
64
+ const defaultNamespaces = [...i18nConfig.defaultNamespaces];
65
+ const userNamespaces = namespace
66
+ ? Array.isArray(namespace)
67
+ ? namespace
68
+ : [namespace]
69
+ : [];
70
+ // 合并并去重
71
+ const namespaces = [...new Set([...defaultNamespaces, ...userNamespaces])];
72
+ return filterMessagesByNamespace(messages, namespaces);
73
+ }
74
+
75
+ public async getI18nInterface<T extends PageI18nInterface>(
76
+ i18nInterface: T,
77
+ _namespace?: string
78
+ ): Promise<T> {
79
+ const t = await getTranslations({
80
+ locale: this.getLocale()
81
+ // namespace: namespace
82
+ });
83
+
84
+ return TranslateI18nInterface.translate<T>(i18nInterface, t);
85
+ }
86
+
87
+ public async getTheme(): Promise<string> {
88
+ const cookieStore = await cookies();
89
+ return (
90
+ cookieStore.get(themeConfig.storageKey)?.value ||
91
+ themeConfig.supportedThemes[0]
92
+ );
93
+ }
94
+ }
@@ -0,0 +1,53 @@
1
+ import { ExecutorError } from '@qlover/fe-corekit';
2
+ import { NextResponse } from 'next/server';
3
+ import {
4
+ isAppApiErrorInterface,
5
+ isAppApiSuccessInterface,
6
+ type AppApiResult
7
+ } from '@/base/port/AppApiInterface';
8
+ import type { BootstrapServerContextValue } from '@/core/bootstraps/BootstrapServer';
9
+ import { BootstrapServer } from '@/core/bootstraps/BootstrapServer';
10
+ import { AppErrorApi } from './AppErrorApi';
11
+ import { AppSuccessApi } from './AppSuccessApi';
12
+ import type { PromiseTask } from '@qlover/fe-corekit';
13
+
14
+ export class NextApiServer extends BootstrapServer {
15
+ protected isAppApiResult(result: unknown): result is AppApiResult {
16
+ return isAppApiSuccessInterface(result) || isAppApiErrorInterface(result);
17
+ }
18
+
19
+ async run<Result>(
20
+ task?: PromiseTask<Result | AppApiResult, BootstrapServerContextValue>
21
+ ): Promise<AppApiResult> {
22
+ const result = await this.execNoError(task);
23
+
24
+ // Is result is AppApiResult, return it directly
25
+ if (this.isAppApiResult(result)) {
26
+ return result;
27
+ }
28
+
29
+ // If result is ExecutorError, return AppErrorApi
30
+ if (result instanceof ExecutorError) {
31
+ return new AppErrorApi(result.id, result.message);
32
+ }
33
+
34
+ // If result is Error, return AppErrorApi
35
+ if (result instanceof Error) {
36
+ return new AppErrorApi('SERVER_ERROR', result.message);
37
+ }
38
+
39
+ return new AppSuccessApi(result);
40
+ }
41
+
42
+ async runWithJson<Result>(
43
+ task?: PromiseTask<Result | AppApiResult, BootstrapServerContextValue>
44
+ ): Promise<NextResponse> {
45
+ const result = await this.run(task);
46
+
47
+ if (!result.success) {
48
+ return NextResponse.json(result, { status: 400 });
49
+ }
50
+
51
+ return NextResponse.json(result);
52
+ }
53
+ }
@@ -0,0 +1,136 @@
1
+ import { TranslateI18nInterface } from '@/base/cases/TranslateI18nInterface';
2
+ import { loadMessages } from '@/i18n/loadMessages';
3
+ import { i18nConfig } from '@config/i18n';
4
+ import type { LocaleType, PageI18nInterface } from '@config/i18n';
5
+ import { themeConfig } from '@config/theme';
6
+ import type { RouteParamsnHandlerInterface } from './port/RouteParamsnHandlerInterface';
7
+ import type { useTranslations } from 'next-intl';
8
+ import type { ParsedUrlQuery } from 'querystring';
9
+
10
+ export interface PagesRouteParamsType extends ParsedUrlQuery {
11
+ locale?: string | string[];
12
+ }
13
+
14
+ /**
15
+ * 用于 src/pages/** 路由的参数管理工具
16
+ *
17
+ * ⚠️ 注意:此类仅在服务器端使用(getStaticProps、getServerSideProps 中)
18
+ * 此类直接从 JSON 文件加载翻译消息,不依赖 next-intl/server,因此可以在 pages 目录中安全使用
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * export async function getStaticProps({ params }: GetStaticPropsContext<PagesRouteParamsType>) {
23
+ * const pageParams = new PagesRouteParams(params);
24
+ * const messages = await pageParams.getI18nMessages();
25
+ * return { props: { messages } };
26
+ * }
27
+ * ```
28
+ */
29
+ export class PagesRouteParams implements RouteParamsnHandlerInterface {
30
+ private locale: string | null;
31
+
32
+ constructor(protected readonly params: PagesRouteParamsType = {}) {
33
+ const localeParam = Array.isArray(this.params.locale)
34
+ ? this.params.locale[0]
35
+ : this.params.locale;
36
+ this.locale = localeParam || i18nConfig.fallbackLng;
37
+ }
38
+
39
+ public getLocale(defaultLocale?: string): string {
40
+ if (this.locale) {
41
+ return this.locale;
42
+ }
43
+
44
+ const localeParam = Array.isArray(this.params.locale)
45
+ ? this.params.locale[0]
46
+ : this.params.locale;
47
+ this.locale = localeParam || defaultLocale || i18nConfig.fallbackLng;
48
+
49
+ return this.locale;
50
+ }
51
+
52
+ /**
53
+ * 获取 locale,如果 locale 不支持则抛出错误
54
+ * 注意:在 pages 目录中,应该在 getStaticProps/getServerSideProps 中返回 { notFound: true } 来处理不支持的 locale
55
+ *
56
+ * @returns 支持的 locale 字符串
57
+ * @throws 如果 locale 不支持
58
+ */
59
+ public getI18nWithNotFound(): string {
60
+ const locale = this.getLocale();
61
+
62
+ if (!i18nConfig.supportedLngs.includes(locale as LocaleType)) {
63
+ // 在 pages 目录中,应该返回 { notFound: true } 而不是调用 notFound()
64
+ // 这里抛出错误,由调用方在 getStaticProps/getServerSideProps 中处理
65
+ throw new Error(`Unsupported locale: ${locale}`);
66
+ }
67
+
68
+ return locale;
69
+ }
70
+
71
+ /**
72
+ * 获取 i18n 消息
73
+ * 使用公共方法加载消息,支持从 API 加载或动态导入 JSON 文件
74
+ * 不依赖 next-intl/server,适用于 getStaticProps/getServerSideProps
75
+ *
76
+ * @param namespace - 可选的命名空间(单个字符串或字符串数组),会与默认命名空间 ['common', 'api'] 合并
77
+ * @returns Promise<Record<string, string>> 返回翻译消息对象
78
+ */
79
+ public async getI18nMessages(
80
+ namespace?: string | string[]
81
+ ): Promise<Record<string, string>> {
82
+ const locale = this.getLocale();
83
+ // 将默认命名空间和用户提供的命名空间合并
84
+ const defaultNamespaces = [...i18nConfig.defaultNamespaces];
85
+ const userNamespaces = namespace
86
+ ? Array.isArray(namespace)
87
+ ? namespace
88
+ : [namespace]
89
+ : [];
90
+ // 合并并去重
91
+ const namespaces = [...new Set([...defaultNamespaces, ...userNamespaces])];
92
+ return loadMessages(locale, namespaces);
93
+ }
94
+
95
+ /**
96
+ * 获取翻译后的 i18n 接口
97
+ * 创建一个简单的翻译函数,基于加载的 messages
98
+ */
99
+ public async getI18nInterface<T extends PageI18nInterface>(
100
+ i18nInterface: T,
101
+ namespace?: string
102
+ ): Promise<T> {
103
+ const messages = await this.getI18nMessages(namespace);
104
+
105
+ // 创建一个简单的翻译函数
106
+ const t = (
107
+ key: string,
108
+ params?: Record<string, string | number | Date>
109
+ ) => {
110
+ const fullKey = namespace ? `${namespace}:${key}` : key;
111
+
112
+ let message = messages[fullKey] || messages[key] || key;
113
+
114
+ // 简单的参数替换
115
+ if (params) {
116
+ Object.entries(params).forEach(([paramKey, paramValue]) => {
117
+ message = message.replace(
118
+ new RegExp(`\\{${paramKey}\\}`, 'g'),
119
+ String(paramValue)
120
+ );
121
+ });
122
+ }
123
+
124
+ return message;
125
+ };
126
+
127
+ return TranslateI18nInterface.translate<T>(
128
+ i18nInterface,
129
+ t as ReturnType<typeof useTranslations>
130
+ );
131
+ }
132
+
133
+ public getTheme(): string {
134
+ return themeConfig.defaultTheme;
135
+ }
136
+ }
@@ -59,6 +59,8 @@ export class SupabaseBridge implements DBBridgeInterface {
59
59
  result: PostgrestSingleResponse<unknown>
60
60
  ): Promise<SupabaseBridgeResponse<unknown>> {
61
61
  if (result.error) {
62
+ this.logger.error('', result);
63
+
62
64
  if (this.hasPausedProject(result)) {
63
65
  throw new Error(
64
66
  'Project is paused, Please Restore project: https://supabase.com/dashboard'
@@ -0,0 +1,74 @@
1
+ import { inject, injectable } from 'inversify';
2
+ import { AdminLocalesService } from '@/server/services/AdminLocalesService';
3
+ import type { LocalesSchema } from '@migrations/schema/LocalesSchema';
4
+ import {
5
+ ApiLocaleService,
6
+ type ImportLocalesData
7
+ } from '../services/ApiLocaleService';
8
+ import {
9
+ LocalesImportValidator,
10
+ LocalesValidator
11
+ } from '../validators/LocalesValidator';
12
+ import { PaginationValidator } from '../validators/PaginationValidator';
13
+ import type { AdminLocalesControllerInterface } from '../port/AdminLocalesControllerInterface';
14
+ import type { BridgeOrderBy } from '../port/DBBridgeInterface';
15
+ import type { UpsertResult } from '../port/LocalesRepositoryInterface';
16
+ import type { PaginationInterface } from '../port/PaginationInterface';
17
+ import type { ValidatorInterface } from '../port/ValidatorInterface';
18
+ import type { PaginationParams } from '../validators/PaginationValidator';
19
+
20
+ @injectable()
21
+ export class AdminLocalesController implements AdminLocalesControllerInterface {
22
+ constructor(
23
+ @inject(AdminLocalesService)
24
+ protected adminLocalesService: AdminLocalesService,
25
+ @inject(PaginationValidator)
26
+ protected paginationValidator: ValidatorInterface<PaginationParams>,
27
+ @inject(ApiLocaleService)
28
+ protected apiLocaleService: ApiLocaleService,
29
+ @inject(LocalesValidator)
30
+ protected localesValidator: ValidatorInterface<Partial<LocalesSchema>>,
31
+ @inject(LocalesImportValidator)
32
+ protected localesImportValidator: ValidatorInterface<ImportLocalesData>
33
+ ) {}
34
+
35
+ async getLocales(query: {
36
+ page: number;
37
+ pageSize: number;
38
+ orders?: BridgeOrderBy;
39
+ }): Promise<PaginationInterface<LocalesSchema>> {
40
+ const paginationParams = await this.paginationValidator.getThrow(query);
41
+
42
+ const result = await this.apiLocaleService.getLocales({
43
+ page: paginationParams.page,
44
+ pageSize: paginationParams.pageSize,
45
+ orderBy: paginationParams?.orders
46
+ });
47
+
48
+ return result;
49
+ }
50
+
51
+ async createLocale(
52
+ body: Omit<LocalesSchema, 'id' | 'created_at' | 'updated_at'>
53
+ ): Promise<{ success: boolean }> {
54
+ const localesParams = await this.localesValidator.getThrow(body);
55
+
56
+ await this.apiLocaleService.create(localesParams);
57
+
58
+ return {
59
+ success: true
60
+ };
61
+ }
62
+ async updateLocale(body: Partial<LocalesSchema>): Promise<void> {
63
+ await this.apiLocaleService.update(body);
64
+ }
65
+ async importLocales(formData: unknown): Promise<UpsertResult> {
66
+ const localesParams = await this.localesImportValidator.getThrow({
67
+ values: formData
68
+ });
69
+
70
+ const result = await this.apiLocaleService.importLocales(localesParams);
71
+
72
+ return result;
73
+ }
74
+ }
@@ -0,0 +1,39 @@
1
+ import { inject, injectable } from 'inversify';
2
+
3
+ import type { UserSchema } from '@migrations/schema/UserSchema';
4
+ import { UserRepository } from '../repositorys/UserRepository';
5
+ import { ApiUserService } from '../services/ApiUserService';
6
+ import { PaginationValidator } from '../validators/PaginationValidator';
7
+ import type { AdminUserControllerInterface } from '../port/AdminUserControllerInterface';
8
+ import type { BridgeOrderBy } from '../port/DBBridgeInterface';
9
+ import type { PaginationInterface } from '../port/PaginationInterface';
10
+ import type { UserRepositoryInterface } from '../port/UserRepositoryInterface';
11
+ import type { ValidatorInterface } from '../port/ValidatorInterface';
12
+ import type { PaginationParams } from '../validators/PaginationValidator';
13
+
14
+ @injectable()
15
+ export class AdminUserController implements AdminUserControllerInterface {
16
+ constructor(
17
+ @inject(UserRepository)
18
+ protected userRepository: UserRepositoryInterface,
19
+ @inject(PaginationValidator)
20
+ protected paginationValidator: ValidatorInterface<PaginationParams>,
21
+ @inject(ApiUserService)
22
+ protected apiUserService: ApiUserService
23
+ ) {}
24
+
25
+ async getUsers(query: {
26
+ page: number;
27
+ pageSize: number;
28
+ orderBy?: BridgeOrderBy;
29
+ }): Promise<PaginationInterface<UserSchema>> {
30
+ const paginationParams = await this.paginationValidator.getThrow(query);
31
+
32
+ const result = await this.apiUserService.getUsers({
33
+ page: paginationParams.page,
34
+ pageSize: paginationParams.pageSize
35
+ });
36
+
37
+ return result;
38
+ }
39
+ }
@@ -0,0 +1,33 @@
1
+ import { inject, injectable } from 'inversify';
2
+ import { i18nConfig } from '@config/i18n';
3
+ import type { LocaleType } from '@config/i18n';
4
+ import { ApiLocaleService } from '../services/ApiLocaleService';
5
+ import type {
6
+ LocalesControllerInterface,
7
+ LocalesControllerJsonQuery
8
+ } from '../port/LocalesControllerInterface';
9
+
10
+ @injectable()
11
+ export class LocalesController implements LocalesControllerInterface {
12
+ constructor(
13
+ @inject(ApiLocaleService)
14
+ protected apiLocaleService: ApiLocaleService
15
+ ) {}
16
+
17
+ async json(
18
+ query: LocalesControllerJsonQuery
19
+ ): Promise<Record<string, string>> {
20
+ const locale = query.locale as LocaleType;
21
+
22
+ if (!locale || !i18nConfig.supportedLngs.includes(locale)) {
23
+ return {};
24
+ }
25
+
26
+ const result = await this.apiLocaleService.getLocalesJson(
27
+ locale,
28
+ query.orderBy
29
+ );
30
+
31
+ return result;
32
+ }
33
+ }
@@ -0,0 +1,77 @@
1
+ import { ExecutorError } from '@qlover/fe-corekit';
2
+ import { inject, injectable } from 'inversify';
3
+ import { StringEncryptor } from '@/base/cases/StringEncryptor';
4
+ import type { UserSchema } from '@migrations/schema/UserSchema';
5
+ import { ServerAuth } from '../ServerAuth';
6
+ import { UserService } from '../services/UserService';
7
+ import {
8
+ LoginValidator,
9
+ type LoginValidatorData
10
+ } from '../validators/LoginValidator';
11
+ import type { ServerAuthInterface } from '../port/ServerAuthInterface';
12
+ import type { UserControllerInerface } from '../port/UserControllerInerface';
13
+ import type { UserServiceInterface } from '../port/UserServiceInterface';
14
+ import type { ValidatorInterface } from '../port/ValidatorInterface';
15
+ import type { Encryptor } from '@qlover/fe-corekit';
16
+
17
+ @injectable()
18
+ export class UserController implements UserControllerInerface {
19
+ constructor(
20
+ @inject(ServerAuth) protected serverAuth: ServerAuthInterface,
21
+ @inject(StringEncryptor)
22
+ protected stringEncryptor: Encryptor<string, string>,
23
+ @inject(LoginValidator)
24
+ protected loginValidator: ValidatorInterface<LoginValidatorData>,
25
+ @inject(UserService) protected userService: UserServiceInterface
26
+ ) {}
27
+
28
+ async login(requestBody: LoginValidatorData): Promise<UserSchema> {
29
+ try {
30
+ if (requestBody.password) {
31
+ requestBody.password = this.stringEncryptor.decrypt(
32
+ requestBody.password
33
+ );
34
+ }
35
+ } catch {
36
+ throw new ExecutorError(
37
+ 'encrypt_password_failed',
38
+ 'Encrypt password failed'
39
+ );
40
+ }
41
+ const body = await this.loginValidator.getThrow(requestBody);
42
+
43
+ const user = await this.userService.login(body);
44
+
45
+ await this.serverAuth.setAuth(user.credential_token);
46
+
47
+ return user;
48
+ }
49
+
50
+ async register(requestBody: LoginValidatorData): Promise<UserSchema> {
51
+ try {
52
+ if (requestBody.password) {
53
+ requestBody.password = this.stringEncryptor.decrypt(
54
+ requestBody.password
55
+ );
56
+ }
57
+ } catch {
58
+ throw new ExecutorError(
59
+ 'encrypt_password_failed',
60
+ 'Encrypt password failed'
61
+ );
62
+ }
63
+
64
+ const body = await this.loginValidator.getThrow(requestBody);
65
+
66
+ const user = await this.userService.register({
67
+ email: body.email,
68
+ password: body.password
69
+ });
70
+
71
+ return user;
72
+ }
73
+
74
+ async logout(): Promise<void> {
75
+ return await this.userService.logout();
76
+ }
77
+ }
@@ -0,0 +1,8 @@
1
+ import type { AppApiResult } from '@/base/port/AppApiInterface';
2
+ import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
3
+
4
+ export interface AIControllerInterface {
5
+ completions(body: {
6
+ messages: ChatCompletionMessageParam[];
7
+ }): Promise<AppApiResult<unknown>>;
8
+ }