@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.
- package/CHANGELOG.md +141 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/config/IOCIdentifier.ts +2 -2
- package/dist/templates/next-app/config/Identifier/common/common.ts +14 -0
- package/dist/templates/next-app/config/Identifier/pages/index.ts +1 -0
- package/dist/templates/next-app/config/Identifier/pages/page.about.ts +20 -0
- package/dist/templates/next-app/config/common.ts +1 -1
- package/dist/templates/next-app/config/cookies.ts +23 -0
- package/dist/templates/next-app/config/i18n/AboutI18n.ts +14 -0
- package/dist/templates/next-app/config/i18n/i18nConfig.ts +3 -1
- package/dist/templates/next-app/config/i18n/index.ts +1 -0
- package/dist/templates/next-app/config/i18n/loginI18n.ts +8 -0
- package/dist/templates/next-app/config/theme.ts +4 -0
- package/dist/templates/next-app/eslint.config.mjs +4 -1
- package/dist/templates/next-app/next.config.ts +1 -0
- package/dist/templates/next-app/package.json +13 -4
- package/dist/templates/next-app/public/locales/en.json +5 -0
- package/dist/templates/next-app/public/locales/zh.json +5 -0
- package/dist/templates/next-app/src/app/[locale]/admin/AdminI18nProvider.tsx +37 -0
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +30 -6
- package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +47 -10
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +22 -10
- package/dist/templates/next-app/src/app/[locale]/page.tsx +23 -8
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +21 -9
- package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +7 -28
- package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +7 -34
- package/dist/templates/next-app/src/app/api/admin/locales/route.ts +12 -34
- package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +7 -26
- package/dist/templates/next-app/src/app/api/admin/users/route.ts +14 -33
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +13 -25
- package/dist/templates/next-app/src/app/api/user/login/route.ts +6 -46
- package/dist/templates/next-app/src/app/api/user/logout/route.ts +5 -24
- package/dist/templates/next-app/src/app/api/user/register/route.ts +6 -45
- package/dist/templates/next-app/src/app/manifest.ts +16 -0
- package/dist/templates/next-app/src/app/robots.txt +2 -0
- package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +12 -2
- package/dist/templates/next-app/src/base/cases/RouterService.ts +5 -5
- package/dist/templates/next-app/src/base/port/AppApiInterface.ts +22 -0
- package/dist/templates/next-app/src/base/port/IOCInterface.ts +9 -0
- package/dist/templates/next-app/src/base/types/{PageProps.ts → AppPageRouter.ts} +4 -1
- package/dist/templates/next-app/src/base/types/PagesRouter.ts +9 -0
- package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +19 -5
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +2 -2
- package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +0 -1
- package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +29 -8
- package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +4 -2
- package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +25 -7
- package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +1 -1
- package/dist/templates/next-app/src/i18n/loadMessages.ts +103 -0
- package/dist/templates/next-app/src/i18n/request.ts +3 -22
- package/dist/templates/next-app/src/pages/[locale]/about.tsx +61 -0
- package/dist/templates/next-app/src/pages/_app.tsx +50 -0
- package/dist/templates/next-app/src/pages/_document.tsx +13 -0
- package/dist/templates/next-app/src/{middleware.ts → proxy.ts} +2 -1
- package/dist/templates/next-app/src/server/AppPageRouteParams.ts +94 -0
- package/dist/templates/next-app/src/server/NextApiServer.ts +53 -0
- package/dist/templates/next-app/src/server/PagesRouteParams.ts +136 -0
- package/dist/templates/next-app/src/server/{sqlBridges/SupabaseBridge.ts → SupabaseBridge.ts} +2 -0
- package/dist/templates/next-app/src/server/controllers/AdminLocalesController.ts +74 -0
- package/dist/templates/next-app/src/server/controllers/AdminUserController.ts +39 -0
- package/dist/templates/next-app/src/server/controllers/LocalesController.ts +33 -0
- package/dist/templates/next-app/src/server/controllers/UserController.ts +77 -0
- package/dist/templates/next-app/src/server/port/AIControllerInterface.ts +8 -0
- package/dist/templates/next-app/src/server/port/AdminLocalesControllerInterface.ts +21 -0
- package/dist/templates/next-app/src/server/port/AdminUserControllerInterface.ts +11 -0
- package/dist/templates/next-app/src/server/port/LocalesControllerInterface.ts +10 -0
- package/dist/templates/next-app/src/server/port/{ParamsHandlerInterface.ts → RouteParamsnHandlerInterface.ts} +9 -2
- package/dist/templates/next-app/src/server/port/ServerInterface.ts +2 -2
- package/dist/templates/next-app/src/server/port/UserControllerInerface.ts +8 -0
- package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +1 -1
- package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +2 -2
- package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +8 -2
- package/dist/templates/next-app/src/{base → server}/services/AdminLocalesService.ts +2 -2
- package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +25 -10
- package/dist/templates/next-app/src/server/services/ApiUserService.ts +5 -2
- package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +4 -2
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +1 -1
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +10 -10
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/_default.css +0 -44
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/dark.css +0 -44
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/pink.css +0 -44
- package/dist/templates/next-app/src/styles/css/index.css +1 -1
- package/dist/templates/next-app/src/styles/css/scrollbar.css +34 -0
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +34 -11
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +69 -39
- package/dist/templates/next-app/src/uikit/components/ClientRootProvider.tsx +64 -0
- package/dist/templates/next-app/src/uikit/components/ClinetRenderProvider.tsx +42 -0
- package/dist/templates/next-app/src/uikit/components/IOCProvider.tsx +34 -0
- package/dist/templates/next-app/src/uikit/components-app/AppBridge.tsx +17 -0
- package/dist/templates/next-app/src/uikit/components-app/AppRoutePage.tsx +112 -0
- package/dist/templates/next-app/src/uikit/{components → components-app}/LanguageSwitcher.tsx +15 -19
- package/dist/templates/next-app/src/uikit/{components → components-app}/ThemeSwitcher.tsx +53 -52
- package/dist/templates/next-app/src/uikit/components-pages/LanguageSwitcher.tsx +98 -0
- package/dist/templates/next-app/src/uikit/components-pages/PagesRoutePage.tsx +93 -0
- package/dist/templates/next-app/src/uikit/context/IOCContext.ts +16 -4
- package/dist/templates/next-app/src/uikit/hook/useStrictEffect.ts +32 -0
- package/dist/templates/next-app/tsconfig.json +3 -2
- package/package.json +1 -1
- package/dist/templates/next-app/src/server/PageParams.ts +0 -66
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +0 -80
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +0 -65
- package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +0 -58
- package/dist/templates/next-app/src/uikit/components/NextIntlProvider.tsx +0 -21
- /package/dist/templates/next-app/{src/app/[locale] → public}/favicon.ico +0 -0
- /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);
|
|
@@ -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
|
-
|
|
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
|
+
}
|
package/dist/templates/next-app/src/server/{sqlBridges/SupabaseBridge.ts → SupabaseBridge.ts}
RENAMED
|
@@ -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
|
+
}
|