@qlover/create-app 1.0.1 → 1.1.0

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 (86) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/index.cjs +9 -9
  3. package/dist/index.js +9 -9
  4. package/dist/templates/next-app/config/Identifier/api.ts +7 -0
  5. package/dist/templates/next-app/config/Identifier/pages/page.register.ts +8 -0
  6. package/dist/templates/next-app/config/common.ts +1 -1
  7. package/dist/templates/next-app/config/i18n/HomeI18n.ts +2 -0
  8. package/dist/templates/next-app/config/i18n/register18n.ts +2 -1
  9. package/dist/templates/next-app/config/route.ts +9 -0
  10. package/dist/templates/next-app/migrations/schema/UserSchema.ts +1 -1
  11. package/dist/templates/next-app/next.config.ts +5 -4
  12. package/dist/templates/next-app/package.json +7 -8
  13. package/dist/templates/next-app/public/locales/en.json +4 -1
  14. package/dist/templates/next-app/public/locales/zh.json +4 -1
  15. package/dist/templates/next-app/src/app/[locale]/auth/layout.tsx +18 -0
  16. package/dist/templates/next-app/src/app/[locale]/{login → auth/login}/LoginForm.tsx +2 -1
  17. package/dist/templates/next-app/src/app/[locale]/{login → auth/login}/page.tsx +4 -5
  18. package/dist/templates/next-app/src/app/[locale]/auth/page.tsx +8 -0
  19. package/dist/templates/next-app/src/app/[locale]/{register → auth/register}/RegisterForm.tsx +24 -3
  20. package/dist/templates/next-app/src/app/[locale]/{register → auth/register}/page.tsx +4 -5
  21. package/dist/templates/next-app/src/app/[locale]/page.tsx +18 -45
  22. package/dist/templates/next-app/src/app/api/ai/completions/route.ts +13 -13
  23. package/dist/templates/next-app/src/app/api/auth/callback/route.ts +11 -0
  24. package/dist/templates/next-app/src/app/api/callback/route.ts +49 -0
  25. package/dist/templates/next-app/src/base/cases/AppConfig.ts +2 -0
  26. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +3 -6
  27. package/dist/templates/next-app/src/base/cases/DialogHandler.ts +0 -1
  28. package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +13 -15
  29. package/dist/templates/next-app/src/base/cases/RouterService.ts +2 -7
  30. package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +0 -6
  31. package/dist/templates/next-app/src/base/cases/TranslateI18nUtil.ts +53 -0
  32. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +0 -10
  33. package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +0 -3
  34. package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +1 -1
  35. package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +0 -18
  36. package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +10 -5
  37. package/dist/templates/next-app/src/base/services/{appApi/AppApiRequester.ts → AppApiRequester.ts} +16 -11
  38. package/dist/templates/next-app/src/base/services/AppUserGateway.ts +110 -0
  39. package/dist/templates/next-app/src/base/services/I18nService.ts +1 -7
  40. package/dist/templates/next-app/src/base/services/ResourceService.ts +1 -4
  41. package/dist/templates/next-app/src/base/services/UserService.ts +28 -17
  42. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +5 -7
  43. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +4 -3
  44. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +24 -16
  45. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +2 -5
  46. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +15 -18
  47. package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +0 -5
  48. package/dist/templates/next-app/src/core/globals.ts +1 -0
  49. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +2 -8
  50. package/dist/templates/next-app/src/i18n/routing.ts +8 -3
  51. package/dist/templates/next-app/src/lib/supabase/client.ts +8 -0
  52. package/dist/templates/next-app/src/lib/supabase/conts.ts +2 -0
  53. package/dist/templates/next-app/src/lib/supabase/proxy.ts +84 -0
  54. package/dist/templates/next-app/src/lib/supabase/server.ts +38 -0
  55. package/dist/templates/next-app/src/proxy.ts +8 -1
  56. package/dist/templates/next-app/src/server/AppPageRouteParams.ts +5 -2
  57. package/dist/templates/next-app/src/server/NextApiServer.ts +2 -9
  58. package/dist/templates/next-app/src/server/PagesRouteParams.ts +3 -4
  59. package/dist/templates/next-app/src/server/ServerAuth.ts +18 -12
  60. package/dist/templates/next-app/src/server/SupabaseBridge.ts +66 -59
  61. package/dist/templates/next-app/src/server/controllers/UserController.ts +7 -2
  62. package/dist/templates/next-app/src/server/port/ServerAuthInterface.ts +4 -0
  63. package/dist/templates/next-app/src/server/port/ServerInterface.ts +2 -1
  64. package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +7 -1
  65. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +0 -3
  66. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +0 -3
  67. package/dist/templates/next-app/src/server/services/UserService.ts +71 -51
  68. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +0 -3
  69. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +0 -6
  70. package/dist/templates/next-app/src/server/validators/SignupVerifyValidator.ts +68 -0
  71. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -3
  72. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +0 -6
  73. package/dist/templates/next-app/src/uikit/components-app/AdminButton.tsx +29 -0
  74. package/dist/templates/next-app/src/uikit/components-app/AppRoutePage.tsx +21 -28
  75. package/dist/templates/next-app/src/uikit/components-app/AuthButton.tsx +20 -0
  76. package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +2 -2
  77. package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +3 -17
  78. package/dist/templates/next-app/src/uikit/utils/getHashParams.ts +8 -0
  79. package/dist/templates/next-app/src/uikit/utils/getHashVerifyEmailParams.ts +42 -0
  80. package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +2 -0
  81. package/package.json +2 -2
  82. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +0 -25
  83. package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +0 -78
  84. package/dist/templates/next-app/src/base/services/adminApi/AdminApiRequester.ts +0 -25
  85. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +0 -78
  86. package/dist/templates/next-app/src/server/port/UserControllerInerface.ts +0 -8
@@ -23,7 +23,6 @@ export class ClientIOCRegister implements IOCRegisterInterface<
23
23
  *
24
24
  * 一般用于注册全局
25
25
  *
26
- * @override
27
26
  * @param ioc - IOC container
28
27
  */
29
28
  protected registerGlobals(ioc: IOCContainerInterface): void {
@@ -48,7 +47,6 @@ export class ClientIOCRegister implements IOCRegisterInterface<
48
47
  * - 国际化服务
49
48
  * - 主题服务
50
49
  *
51
- * @override
52
50
  * @param ioc
53
51
  */
54
52
  protected registerImplement(ioc: IOCContainerInterface): void {
@@ -70,9 +68,6 @@ export class ClientIOCRegister implements IOCRegisterInterface<
70
68
  // ioc.bind(I.RequestPageBridgeInterface, ioc.get(RequestPageBridge));
71
69
  }
72
70
 
73
- /**
74
- * @override
75
- */
76
71
  protected registerCommon(_ioc: IOCContainerInterface): void {
77
72
  // const { appConfig } = this.options;
78
73
  // const logger = ioc.get(I.Logger);
@@ -16,6 +16,7 @@ export const dialogHandler = new DialogHandler();
16
16
  * Global logger
17
17
  */
18
18
  export const logger = new Logger({
19
+ name: 'next-app',
19
20
  handlers: new ConsoleHandler(new ColorFormatter(loggerStyles)),
20
21
  silent: false,
21
22
  level: 'debug'
@@ -19,7 +19,6 @@ export class ServerIOCRegister implements IOCRegisterInterface<
19
19
  *
20
20
  * 一般用于注册全局
21
21
  *
22
- * @override
23
22
  * @param ioc - IOC container
24
23
  */
25
24
  protected registerGlobals(ioc: IOCContainerInterface): void {
@@ -28,6 +27,7 @@ export class ServerIOCRegister implements IOCRegisterInterface<
28
27
  ioc.bind(
29
28
  I.Logger,
30
29
  new Logger({
30
+ name: 'next-app-server',
31
31
  handlers: new ConsoleHandler(
32
32
  new TimestampFormatter({
33
33
  localeOptions: {
@@ -41,21 +41,15 @@ export class ServerIOCRegister implements IOCRegisterInterface<
41
41
  })
42
42
  ),
43
43
  silent: false,
44
- level: appConfig.env === 'development' ? 'debug' : 'info'
44
+ level: appConfig.isProduction ? 'warn' : 'debug'
45
45
  })
46
46
  );
47
47
  }
48
48
 
49
- /**
50
- * @override
51
- */
52
49
  protected registerImplement(ioc: IOCContainerInterface): void {
53
50
  ioc.bind(I.DBBridgeInterface, ioc.get(SupabaseBridge));
54
51
  }
55
52
 
56
- /**
57
- * @override
58
- */
59
53
  protected registerCommon(_ioc: IOCContainerInterface): void {}
60
54
 
61
55
  /**
@@ -2,6 +2,7 @@ import { createNavigation } from 'next-intl/navigation';
2
2
  import { defineRouting } from 'next-intl/routing';
3
3
  import { useLocaleRoutes } from '@config/common';
4
4
  import { i18nConfig } from '@config/i18n';
5
+ import { ROUTE_LOGIN, ROUTE_REGISTER } from '@config/route';
5
6
 
6
7
  const locales = i18nConfig.supportedLngs;
7
8
 
@@ -19,9 +20,13 @@ export const routing = defineRouting({
19
20
  en: '/',
20
21
  zh: '/'
21
22
  },
22
- '/login': {
23
- en: '/login',
24
- zh: '/login'
23
+ [ROUTE_LOGIN]: {
24
+ en: '/auth/login',
25
+ zh: '/auth/login'
26
+ },
27
+ [ROUTE_REGISTER]: {
28
+ en: '/auth/register',
29
+ zh: '/auth/register'
25
30
  }
26
31
  }
27
32
  });
@@ -0,0 +1,8 @@
1
+ import { createBrowserClient } from '@supabase/ssr';
2
+
3
+ export function createClient() {
4
+ return createBrowserClient(
5
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
6
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
7
+ );
8
+ }
@@ -0,0 +1,2 @@
1
+ export const SUPABASE_URL = process.env.SUPABASE_URL;
2
+ export const SUPABASE_KEY = process.env.SUPABASE_ANON_KEY;
@@ -0,0 +1,84 @@
1
+ import { createServerClient } from '@supabase/ssr';
2
+ import { NextResponse, type NextRequest } from 'next/server';
3
+ import { ROUTE_LOGIN, ROUTE_REGISTER } from '@config/route';
4
+ import { SUPABASE_KEY, SUPABASE_URL } from './conts';
5
+
6
+ export async function updateSession(request: NextRequest) {
7
+ let supabaseResponse = NextResponse.next({
8
+ request
9
+ });
10
+
11
+ // If the env vars are not set, skip proxy check. You can remove this
12
+ // once you setup the project.
13
+ if (!SUPABASE_URL && SUPABASE_KEY) {
14
+ return supabaseResponse;
15
+ }
16
+
17
+ // With Fluid compute, don't put this client in a global environment
18
+ // variable. Always create a new one on each request.
19
+ const supabase = createServerClient(SUPABASE_URL!, SUPABASE_KEY!, {
20
+ // global: {
21
+ // fetch: (input, init) => {
22
+ // console.log('proxy supabase globals fetch', input, init);
23
+
24
+ // return fetch(input, init);
25
+ // }
26
+ // },
27
+ cookies: {
28
+ getAll() {
29
+ return request.cookies.getAll();
30
+ },
31
+ setAll(cookiesToSet) {
32
+ cookiesToSet.forEach(({ name, value }) =>
33
+ request.cookies.set(name, value)
34
+ );
35
+ supabaseResponse = NextResponse.next({
36
+ request
37
+ });
38
+ cookiesToSet.forEach(({ name, value, options }) =>
39
+ supabaseResponse.cookies.set(name, value, options)
40
+ );
41
+ }
42
+ }
43
+ });
44
+
45
+ // Do not run code between createServerClient and
46
+ // supabase.auth.getClaims(). A simple mistake could make it very hard to debug
47
+ // issues with users being randomly logged out.
48
+
49
+ // IMPORTANT: If you remove getClaims() and you use server-side rendering
50
+ // with the Supabase client, your users may be randomly logged out.
51
+ const { data } = await supabase.auth.getClaims();
52
+ const user = data?.claims;
53
+
54
+ console.log('Proxy(supabase) logged?:', !!user);
55
+
56
+ if (
57
+ request.nextUrl.pathname !== '/' &&
58
+ !user &&
59
+ !request.nextUrl.pathname.includes(ROUTE_LOGIN) &&
60
+ !request.nextUrl.pathname.includes(ROUTE_REGISTER)
61
+ ) {
62
+ console.log('proxy supabase no user to login');
63
+
64
+ // no user, potentially respond by redirecting the user to the login page
65
+ const url = request.nextUrl.clone();
66
+ url.pathname = ROUTE_LOGIN;
67
+ return NextResponse.redirect(url);
68
+ }
69
+
70
+ // IMPORTANT: You *must* return the supabaseResponse object as it is.
71
+ // If you're creating a new response object with NextResponse.next() make sure to:
72
+ // 1. Pass the request in it, like so:
73
+ // const myNewResponse = NextResponse.next({ request })
74
+ // 2. Copy over the cookies, like so:
75
+ // myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
76
+ // 3. Change the myNewResponse object to fit your needs, but avoid changing
77
+ // the cookies!
78
+ // 4. Finally:
79
+ // return myNewResponse
80
+ // If this is not done, you may be causing the browser and server to go out
81
+ // of sync and terminate the user's session prematurely!
82
+
83
+ return supabaseResponse;
84
+ }
@@ -0,0 +1,38 @@
1
+ import { createServerClient } from '@supabase/ssr';
2
+ import { cookies } from 'next/headers';
3
+ import { SUPABASE_KEY, SUPABASE_URL } from './conts';
4
+
5
+ /**
6
+ * Especially important if using Fluid compute: Don't put this client in a
7
+ * global variable. Always create a new client within each function when using
8
+ * it.
9
+ */
10
+ export async function createClient() {
11
+ const cookieStore = await cookies();
12
+
13
+ return createServerClient(SUPABASE_URL!, SUPABASE_KEY!, {
14
+ // global: {
15
+ // fetch: (input, init) => {
16
+ // console.log('supabase globals fetch', input, init);
17
+
18
+ // return fetch(input, init);
19
+ // }
20
+ // },
21
+ cookies: {
22
+ getAll() {
23
+ return cookieStore.getAll();
24
+ },
25
+ setAll(cookiesToSet) {
26
+ try {
27
+ cookiesToSet.forEach(({ name, value, options }) =>
28
+ cookieStore.set(name, value, options)
29
+ );
30
+ } catch {
31
+ // The `setAll` method was called from a Server Component.
32
+ // This can be ignored if you have proxy refreshing
33
+ // user sessions.
34
+ }
35
+ }
36
+ }
37
+ });
38
+ }
@@ -5,10 +5,17 @@ import createMiddleware from 'next-intl/middleware';
5
5
 
6
6
  // Import your routing configuration which contains all locales, defaultLocale, and pathnames
7
7
  import { routing } from './i18n/routing';
8
+ import { updateSession } from './lib/supabase/proxy';
9
+ import type { NextRequest } from 'next/server';
8
10
 
9
11
  // Export the middleware created by next-intl
10
12
  // This middleware will handle locale detection, redirects, and internationalized routing automatically
11
- export default createMiddleware(routing);
13
+ // export default createMiddleware(routing);
14
+
15
+ export default async function proxy(request: NextRequest) {
16
+ await updateSession(request);
17
+ return createMiddleware(routing)(request);
18
+ }
12
19
 
13
20
  // Next.js middleware configuration object
14
21
  export const config = {
@@ -1,7 +1,7 @@
1
1
  import { cookies } from 'next/headers';
2
2
  import { notFound } from 'next/navigation';
3
3
  import { getMessages, getTranslations } from 'next-intl/server';
4
- import { TranslateI18nInterface } from '@/base/cases/TranslateI18nInterface';
4
+ import { TranslateI18nUtil } from '@/base/cases/TranslateI18nUtil';
5
5
  import { filterMessagesByNamespace } from '@/i18n/loadMessages';
6
6
  import { i18nConfig } from '@config/i18n';
7
7
  import type { LocaleType, PageI18nInterface } from '@config/i18n';
@@ -91,7 +91,10 @@ export class AppPageRouteParams<
91
91
  // namespace: namespace
92
92
  });
93
93
 
94
- return TranslateI18nInterface.translate<T>(i18nInterface, t);
94
+ return TranslateI18nUtil.translate<T>(
95
+ i18nInterface,
96
+ TranslateI18nUtil.overrideTranslateT(t)
97
+ );
95
98
  }
96
99
 
97
100
  /**
@@ -12,16 +12,10 @@ import { AppSuccessApi } from './AppSuccessApi';
12
12
  import type { ExecutorAsyncTask } from '@qlover/fe-corekit';
13
13
 
14
14
  export class NextApiServer extends BootstrapServer {
15
- /**
16
- * @override
17
- */
18
15
  protected isAppApiResult(result: unknown): result is AppApiResult {
19
16
  return isAppApiSuccessInterface(result) || isAppApiErrorInterface(result);
20
17
  }
21
18
 
22
- /**
23
- * @override
24
- */
25
19
  public async run<Result>(
26
20
  task?: ExecutorAsyncTask<
27
21
  Result | AppApiResult,
@@ -37,20 +31,19 @@ export class NextApiServer extends BootstrapServer {
37
31
 
38
32
  // If result is ExecutorError, return AppErrorApi
39
33
  if (result instanceof ExecutorError) {
34
+ this.logger.debug('NextApiServer run error:', result);
40
35
  return new AppErrorApi(result.id, result.message);
41
36
  }
42
37
 
43
38
  // If result is Error, return AppErrorApi
44
39
  if (result instanceof Error) {
40
+ this.logger.debug('NextApiServer run error:', result);
45
41
  return new AppErrorApi('SERVER_ERROR', result.message);
46
42
  }
47
43
 
48
44
  return new AppSuccessApi(result);
49
45
  }
50
46
 
51
- /**
52
- * @override
53
- */
54
47
  public async runWithJson<Result>(
55
48
  task?: ExecutorAsyncTask<
56
49
  Result | AppApiResult,
@@ -1,4 +1,4 @@
1
- import { TranslateI18nInterface } from '@/base/cases/TranslateI18nInterface';
1
+ import { TranslateI18nUtil } from '@/base/cases/TranslateI18nUtil';
2
2
  import { loadMessages } from '@/i18n/loadMessages';
3
3
  import { i18nConfig } from '@config/i18n';
4
4
  import type { LocaleType, PageI18nInterface } from '@config/i18n';
@@ -100,9 +100,8 @@ export class PagesRouteParams implements RouteParamsnHandlerInterface {
100
100
  /**
101
101
  * 获取翻译后的 i18n 接口
102
102
  * 创建一个简单的翻译函数,基于加载的 messages
103
-
104
103
  * @override
105
- */
104
+ */
106
105
  public async getI18nInterface<T extends PageI18nInterface>(
107
106
  i18nInterface: T,
108
107
  namespace?: string
@@ -131,7 +130,7 @@ export class PagesRouteParams implements RouteParamsnHandlerInterface {
131
130
  return message;
132
131
  };
133
132
 
134
- return TranslateI18nInterface.translate<T>(
133
+ return TranslateI18nUtil.translate<T>(
135
134
  i18nInterface,
136
135
  t as ReturnType<typeof useTranslations>
137
136
  );
@@ -2,8 +2,10 @@ import { ExecutorError } from '@qlover/fe-corekit';
2
2
  import { inject, injectable } from 'inversify';
3
3
  import { cookies } from 'next/headers';
4
4
  import type { AppConfig } from '@/base/cases/AppConfig';
5
+ import { UserSchema } from '@migrations/schema/UserSchema';
5
6
  import { API_NOT_AUTHORIZED } from '@config/Identifier';
6
7
  import { I } from '@config/IOCIdentifier';
8
+ import { SupabaseBridge } from './SupabaseBridge';
7
9
  import { UserCredentialToken } from './UserCredentialToken';
8
10
  import type { ServerAuthInterface } from './port/ServerAuthInterface';
9
11
 
@@ -13,7 +15,8 @@ export class ServerAuth implements ServerAuthInterface {
13
15
  constructor(
14
16
  @inject(I.AppConfig) protected server: AppConfig,
15
17
  @inject(UserCredentialToken)
16
- protected userCredentialToken: UserCredentialToken
18
+ protected userCredentialToken: UserCredentialToken,
19
+ @inject(SupabaseBridge) protected supabase: SupabaseBridge
17
20
  ) {
18
21
  this.userTokenKey = server.userTokenKey;
19
22
  }
@@ -31,19 +34,11 @@ export class ServerAuth implements ServerAuthInterface {
31
34
  * @override
32
35
  */
33
36
  public async hasAuth(): Promise<boolean> {
34
- const token = await this.getAuth();
37
+ const supabase = await this.supabase.getSupabase();
35
38
 
36
- if (!token) {
37
- return false;
38
- }
39
-
40
- try {
41
- const user = await this.userCredentialToken.parseToken(token);
39
+ const { data } = await supabase.auth.getClaims();
42
40
 
43
- return !!user;
44
- } catch {
45
- return false;
46
- }
41
+ return !!data?.claims;
47
42
  }
48
43
 
49
44
  /**
@@ -72,4 +67,15 @@ export class ServerAuth implements ServerAuthInterface {
72
67
  throw new ExecutorError(API_NOT_AUTHORIZED, 'Not authorized');
73
68
  }
74
69
  }
70
+
71
+ /**
72
+ * @override
73
+ */
74
+ public async getUser(): Promise<UserSchema | null> {
75
+ const supabase = await this.supabase.getSupabase();
76
+
77
+ const { data } = await supabase.auth.getUser();
78
+
79
+ return data.user ? this.supabase.toUserSchema(data.user) : null;
80
+ }
75
81
  }
@@ -1,11 +1,7 @@
1
- import {
2
- createClient,
3
- type PostgrestSingleResponse,
4
- type SupabaseClient,
5
- type PostgrestResponse
6
- } from '@supabase/supabase-js';
1
+ import { ExecutorError } from '@qlover/fe-corekit';
2
+ import { AuthError } from '@supabase/supabase-js';
7
3
  import { injectable, inject } from 'inversify';
8
- import type { AppConfig } from '@/base/cases/AppConfig';
4
+ import { createClient } from '@/lib/supabase/server';
9
5
  import type {
10
6
  BridgeEvent,
11
7
  DBBridgeInterface,
@@ -13,12 +9,18 @@ import type {
13
9
  BridgeOrderBy,
14
10
  Where
15
11
  } from '@/server/port/DBBridgeInterface';
12
+ import { UserRole, UserSchema } from '@migrations/schema/UserSchema';
16
13
  import { I } from '@config/IOCIdentifier';
17
14
  import type { LoggerInterface } from '@qlover/logger';
15
+ import type { PostgrestResponseFailure } from '@supabase/postgrest-js';
18
16
  import type {
19
- PostgrestFilterBuilder,
20
- PostgrestResponseFailure
21
- } from '@supabase/postgrest-js';
17
+ PostgrestSingleResponse,
18
+ SupabaseClient,
19
+ PostgrestResponse,
20
+ User,
21
+ AuthResponse,
22
+ UserResponse
23
+ } from '@supabase/supabase-js';
22
24
 
23
25
  const whereHandlerMaps = {
24
26
  '=': 'eq',
@@ -34,36 +36,18 @@ export type SupabaseBridgeResponse<T> = DBBridgeResponse<T> &
34
36
 
35
37
  @injectable()
36
38
  export class SupabaseBridge implements DBBridgeInterface {
37
- protected supabase: SupabaseClient;
38
-
39
- constructor(
40
- @inject(I.AppConfig) appConfig: AppConfig,
41
- @inject(I.Logger) protected logger: LoggerInterface
42
- ) {
43
- this.supabase = createClient(
44
- appConfig.supabaseUrl,
45
- appConfig.supabaseAnonKey
46
- );
47
- }
39
+ constructor(@inject(I.Logger) protected logger: LoggerInterface) {}
48
40
 
49
- /**
50
- * @override
51
- */
52
- public getSupabase(): SupabaseClient {
53
- return this.supabase;
41
+ public async getSupabase(): Promise<SupabaseClient> {
42
+ return await createClient();
54
43
  }
55
44
 
56
- /**
57
- * @override
58
- */
59
45
  public async execSql(sql: string): Promise<SupabaseBridgeResponse<unknown>> {
60
- const res = await this.supabase.rpc('exec_sql', { sql });
46
+ const supabase = await this.getSupabase();
47
+ const res = await supabase.rpc('exec_sql', { sql });
61
48
  return this.catch(res);
62
49
  }
63
50
 
64
- /**
65
- * @override
66
- */
67
51
  protected async catch(
68
52
  result: PostgrestSingleResponse<unknown>
69
53
  ): Promise<SupabaseBridgeResponse<unknown>> {
@@ -82,23 +66,14 @@ export class SupabaseBridge implements DBBridgeInterface {
82
66
  return result as SupabaseBridgeResponse<unknown>;
83
67
  }
84
68
 
85
- /**
86
- * @override
87
- */
88
69
  protected hasPausedProject(error: PostgrestResponseFailure): boolean {
89
70
  return (
90
71
  error.status === 0 && error.error.message === 'TypeError: fetch failed'
91
72
  );
92
73
  }
93
74
 
94
- /**
95
- * @override
96
- */
97
- protected handleWhere(
98
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
- handler: PostgrestFilterBuilder<any, any, any, any, string, unknown, any>,
100
- wheres: Where[]
101
- ): void {
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ protected handleWhere(handler: any, wheres: Where[]): void {
102
77
  for (const where of wheres) {
103
78
  const [key, operator, value] = where;
104
79
  const opr = whereHandlerMaps[operator];
@@ -116,19 +91,41 @@ export class SupabaseBridge implements DBBridgeInterface {
116
91
  }
117
92
  }
118
93
 
119
- /**
120
- * @override
121
- */
122
- protected handleOrderBy(
123
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
- handler: PostgrestFilterBuilder<any, any, any, any, string, unknown, any>,
125
- orderBy?: BridgeOrderBy
126
- ): void {
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ protected handleOrderBy(handler: any, orderBy?: BridgeOrderBy): void {
127
96
  if (orderBy) {
128
97
  handler.order(orderBy[0], { ascending: orderBy[1] === 0 });
129
98
  }
130
99
  }
131
100
 
101
+ public toUserSchema(user: User): UserSchema {
102
+ return {
103
+ id: user.id,
104
+ email: user.email!,
105
+ created_at: user.created_at,
106
+ updated_at: user.updated_at,
107
+ role: user.role === 'admin' ? UserRole.ADMIN : UserRole.USER,
108
+ // 始终为空
109
+ password: '',
110
+ credential_token: ''
111
+ };
112
+ }
113
+
114
+ public throwIfError(
115
+ response: AuthResponse | UserResponse | { error: unknown }
116
+ ): void {
117
+ const { error } = response;
118
+ if (error) {
119
+ this.logger.info('SupabaseBridge throw error:', error);
120
+
121
+ if (error instanceof AuthError) {
122
+ throw new ExecutorError('SupabaseAuthError', error);
123
+ }
124
+
125
+ throw new Error(error as string);
126
+ }
127
+ }
128
+
132
129
  /**
133
130
  * @override
134
131
  */
@@ -137,7 +134,8 @@ export class SupabaseBridge implements DBBridgeInterface {
137
134
  if (!data) {
138
135
  throw new Error('Data is required for add operation');
139
136
  }
140
- const res = await this.supabase
137
+ const supabase = await this.getSupabase();
138
+ const res = await supabase
141
139
  .from(table)
142
140
  .insert(Array.isArray(data) ? data : [data]);
143
141
  return this.catch(res);
@@ -152,7 +150,9 @@ export class SupabaseBridge implements DBBridgeInterface {
152
150
  throw new Error('Data is required for upsert operation');
153
151
  }
154
152
  const selectFields = Array.isArray(fields) ? fields.join(',') : fields;
155
- const res = await this.supabase
153
+ const supabase = await this.getSupabase();
154
+
155
+ const res = await supabase
156
156
  .from(table)
157
157
  .upsert(Array.isArray(data) ? data : [data], {
158
158
  onConflict: 'value'
@@ -169,8 +169,9 @@ export class SupabaseBridge implements DBBridgeInterface {
169
169
  if (!data) {
170
170
  throw new Error('Data is required for update operation');
171
171
  }
172
+ const supabase = await this.getSupabase();
172
173
 
173
- const handler = this.supabase.from(table).update(data);
174
+ const handler = supabase.from(table).update(data);
174
175
 
175
176
  this.handleWhere(handler, where ?? []);
176
177
 
@@ -182,7 +183,9 @@ export class SupabaseBridge implements DBBridgeInterface {
182
183
  */
183
184
  public async delete(event: BridgeEvent): Promise<DBBridgeResponse<unknown>> {
184
185
  const { table, where } = event;
185
- const handler = this.supabase.from(table).delete();
186
+ const supabase = await this.getSupabase();
187
+
188
+ const handler = supabase.from(table).delete();
186
189
 
187
190
  this.handleWhere(handler, where ?? []);
188
191
 
@@ -197,7 +200,9 @@ export class SupabaseBridge implements DBBridgeInterface {
197
200
  ): Promise<SupabaseBridgeResponse<unknown>> {
198
201
  const { table, fields = '*', where, orderBy } = event;
199
202
  const selectFields = Array.isArray(fields) ? fields.join(',') : fields;
200
- const handler = this.supabase.from(table).select(selectFields);
203
+ const supabase = await this.getSupabase();
204
+
205
+ const handler = supabase.from(table).select(selectFields);
201
206
 
202
207
  this.handleWhere(handler, where ?? []);
203
208
 
@@ -222,8 +227,10 @@ export class SupabaseBridge implements DBBridgeInterface {
222
227
  } = event;
223
228
  const selectFields = Array.isArray(fields) ? fields.join(',') : fields;
224
229
 
230
+ const supabase = await this.getSupabase();
231
+
225
232
  // 获取总数
226
- const countHandler = this.supabase
233
+ const countHandler = supabase
227
234
  .from(table)
228
235
  .select('*', { count: 'exact', head: true });
229
236
 
@@ -231,7 +238,7 @@ export class SupabaseBridge implements DBBridgeInterface {
231
238
  const countResult = await this.catch(await countHandler);
232
239
 
233
240
  // 获取分页数据
234
- const handler = this.supabase.from(table).select(selectFields);
241
+ const handler = supabase.from(table).select(selectFields);
235
242
 
236
243
  this.handleOrderBy(handler, orderBy);
237
244
 
@@ -8,20 +8,25 @@ import {
8
8
  LoginValidator,
9
9
  type LoginValidatorData
10
10
  } from '../validators/LoginValidator';
11
+ import {
12
+ SignupVerifyParamType,
13
+ SignupVerifyValidator
14
+ } from '../validators/SignupVerifyValidator';
11
15
  import type { ServerAuthInterface } from '../port/ServerAuthInterface';
12
- import type { UserControllerInerface } from '../port/UserControllerInerface';
13
16
  import type { UserServiceInterface } from '../port/UserServiceInterface';
14
17
  import type { ValidatorInterface } from '../port/ValidatorInterface';
15
18
  import type { EncryptorInterface } from '@qlover/fe-corekit';
16
19
 
17
20
  @injectable()
18
- export class UserController implements UserControllerInerface {
21
+ export class UserController implements UserServiceInterface {
19
22
  constructor(
20
23
  @inject(ServerAuth) protected serverAuth: ServerAuthInterface,
21
24
  @inject(StringEncryptor)
22
25
  protected stringEncryptor: EncryptorInterface<string, string>,
23
26
  @inject(LoginValidator)
24
27
  protected loginValidator: ValidatorInterface<LoginValidatorData>,
28
+ @inject(SignupVerifyValidator)
29
+ protected verifyValidator: ValidatorInterface<SignupVerifyParamType>,
25
30
  @inject(UserService) protected userService: UserServiceInterface
26
31
  ) {}
27
32
 
@@ -1,3 +1,5 @@
1
+ import type { UserSchema } from '@migrations/schema/UserSchema';
2
+
1
3
  export interface ServerAuthInterface {
2
4
  setAuth(credential_token: string): Promise<void>;
3
5
 
@@ -8,4 +10,6 @@ export interface ServerAuthInterface {
8
10
  hasAuth(): Promise<boolean>;
9
11
 
10
12
  throwIfNotAuth(): Promise<void>;
13
+
14
+ getUser(): Promise<UserSchema | null>;
11
15
  }
@@ -1,4 +1,5 @@
1
- import { ExecutorAsyncTask, type ExecutorError } from '@qlover/fe-corekit';
1
+ import type { ExecutorAsyncTask } from '@qlover/fe-corekit';
2
+ import { type ExecutorError } from '@qlover/fe-corekit';
2
3
  import { type IOCIdentifierMapServer } from '@config/IOCIdentifier';
3
4
  import type {
4
5
  ServiceIdentifier,