@qlover/create-app 1.0.1 → 1.0.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 (85) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  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/package.json +1 -1
  81. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +0 -25
  82. package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +0 -78
  83. package/dist/templates/next-app/src/base/services/adminApi/AdminApiRequester.ts +0 -25
  84. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +0 -78
  85. package/dist/templates/next-app/src/server/port/UserControllerInerface.ts +0 -8
@@ -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,
@@ -1,7 +1,13 @@
1
1
  import type { UserSchema } from '@migrations/schema/UserSchema';
2
2
 
3
+ export type UserServiceRegisterParams = {
4
+ username?: string;
5
+ email: string;
6
+ password: string;
7
+ };
8
+
3
9
  export interface UserServiceInterface {
4
- register(params: { email: string; password: string }): Promise<UserSchema>;
10
+ register(params: UserServiceRegisterParams): Promise<UserSchema>;
5
11
  login(params: { email: string; password: string }): Promise<UserSchema>;
6
12
 
7
13
  logout(): Promise<void>;
@@ -27,9 +27,6 @@ export class LocalesRepository implements LocalesRepositoryInterface {
27
27
  @inject(Datetime) protected datetime: Datetime
28
28
  ) {}
29
29
 
30
- /**
31
- * @override
32
- */
33
30
  public async getAll(): Promise<LocalesSchema[]> {
34
31
  const result = await this.dbBridge.get({
35
32
  table: this.name,
@@ -25,9 +25,6 @@ export class UserRepository implements UserRepositoryInterface {
25
25
  @inject(I.DBBridgeInterface) protected dbBridge: DBBridgeInterface
26
26
  ) {}
27
27
 
28
- /**
29
- * @override
30
- */
31
28
  public getAll(): Promise<unknown> {
32
29
  return this.dbBridge.get({ table: this.name });
33
30
  }
@@ -1,13 +1,15 @@
1
+ import { ExecutorError, type EncryptorInterface } from '@qlover/fe-corekit';
2
+ import { Session, User } from '@supabase/supabase-js';
1
3
  import { inject, injectable } from 'inversify';
2
- import { isEmpty, last, omit } from 'lodash';
4
+ import { isString } from 'lodash';
5
+ import { AppConfig } from '@/base/cases/AppConfig';
3
6
  import type { UserSchema } from '@migrations/schema/UserSchema';
4
- import {
5
- API_USER_NOT_FOUND,
6
- API_USER_ALREADY_EXISTS
7
- } from '@config/Identifier/api';
7
+ import { API_USER_NOT_FOUND } from '@config/Identifier/api';
8
+ import { I } from '@config/IOCIdentifier';
8
9
  import { PasswordEncrypt } from '../PasswordEncrypt';
9
10
  import { UserRepository } from '../repositorys/UserRepository';
10
11
  import { ServerAuth } from '../ServerAuth';
12
+ import { SupabaseBridge } from '../SupabaseBridge';
11
13
  import {
12
14
  UserCredentialToken,
13
15
  type UserCredentialTokenValue
@@ -15,11 +17,20 @@ import {
15
17
  import type { CrentialTokenInterface } from '../port/CrentialTokenInterface';
16
18
  import type { ServerAuthInterface } from '../port/ServerAuthInterface';
17
19
  import type { UserRepositoryInterface } from '../port/UserRepositoryInterface';
18
- import type { UserServiceInterface } from '../port/UserServiceInterface';
19
- import type { EncryptorInterface } from '@qlover/fe-corekit';
20
+ import type {
21
+ UserServiceInterface,
22
+ UserServiceRegisterParams
23
+ } from '../port/UserServiceInterface';
24
+ import type { LoggerInterface } from '@qlover/logger';
20
25
 
21
26
  @injectable()
22
27
  export class UserService implements UserServiceInterface {
28
+ @inject(I.Logger)
29
+ protected logger!: LoggerInterface;
30
+
31
+ @inject(I.AppConfig)
32
+ protected appConfig!: AppConfig;
33
+
23
34
  constructor(
24
35
  @inject(UserRepository)
25
36
  protected userRepository: UserRepositoryInterface,
@@ -28,33 +39,39 @@ export class UserService implements UserServiceInterface {
28
39
  @inject(PasswordEncrypt)
29
40
  protected encryptor: EncryptorInterface<string, string>,
30
41
  @inject(UserCredentialToken)
31
- protected credentialToken: CrentialTokenInterface<UserCredentialTokenValue>
42
+ protected credentialToken: CrentialTokenInterface<UserCredentialTokenValue>,
43
+ @inject(SupabaseBridge) protected supabaseBridge: SupabaseBridge
32
44
  ) {}
33
45
 
34
46
  /**
35
47
  * @override
36
48
  */
37
- public async register(params: {
38
- email: string;
39
- password: string;
40
- }): Promise<UserSchema> {
41
- const user = await this.userRepository.getUserByEmail(params.email);
42
-
43
- if (!isEmpty(user)) {
44
- throw new Error(API_USER_ALREADY_EXISTS);
45
- }
46
-
47
- const result = await this.userRepository.add({
49
+ public async register(
50
+ params: UserServiceRegisterParams
51
+ ): Promise<UserSchema> {
52
+ const supabase = await this.supabaseBridge.getSupabase();
53
+
54
+ // TODO: 检查 username, 是否重复
55
+ // const user = await this.userRepository.getUserByEmail(params.email);
56
+ // if (!isEmpty(user)) {
57
+ // throw new Error(API_USER_ALREADY_EXISTS);
58
+ // }
59
+
60
+ const result = await supabase.auth.signUp({
48
61
  email: params.email,
49
- password: this.encryptor.encrypt(params.password)
62
+ password: params.password
63
+
64
+ // options: {
65
+ // emailRedirectTo: 'http://localhost:3100/callback'
66
+ // }
50
67
  });
68
+ this.supabaseBridge.throwIfError(result);
51
69
 
52
- const target = last(result);
53
- if (!target) {
70
+ if (!result.data.user) {
54
71
  throw new Error(API_USER_NOT_FOUND);
55
72
  }
56
73
 
57
- return omit(target, 'password') as UserSchema;
74
+ return this.supabaseBridge.toUserSchema(result.data.user);
58
75
  }
59
76
 
60
77
  /**
@@ -63,52 +80,55 @@ export class UserService implements UserServiceInterface {
63
80
  public async login(params: {
64
81
  email: string;
65
82
  password: string;
83
+ authCode?: string;
66
84
  }): Promise<UserSchema> {
67
- const user = await this.userRepository.getUserByEmail(params.email);
68
-
69
- if (!user) {
70
- throw new Error(API_USER_NOT_FOUND);
71
- }
72
-
73
- const encryptedPassword = this.encryptor.encrypt(params.password);
85
+ const supabase = await this.supabaseBridge.getSupabase();
74
86
 
75
- if (encryptedPassword !== user.password) {
76
- throw new Error(API_USER_NOT_FOUND);
87
+ if (params.authCode) {
88
+ const ares = await supabase.auth.exchangeCodeForSession(params.authCode);
89
+ this.supabaseBridge.throwIfError(ares);
77
90
  }
78
91
 
79
- const credentialToken = await this.credentialToken.generateToken(user);
80
-
81
- await this.userRepository.updateById(user.id, {
82
- credential_token: credentialToken
92
+ const result = await supabase.auth.signInWithPassword({
93
+ email: params.email,
94
+ password: params.password
83
95
  });
96
+ this.supabaseBridge.throwIfError(result);
84
97
 
85
- return Object.assign(omit(user, 'password') as UserSchema, {
86
- credential_token: credentialToken
87
- });
98
+ this.logger.info('supbase login succees', result.data);
99
+
100
+ return this.supabaseBridge.toUserSchema(result.data.user!);
88
101
  }
89
102
 
90
103
  /**
91
104
  * @override
92
105
  */
93
106
  public async logout(): Promise<void> {
94
- const auth = await this.userAuth.getAuth();
107
+ const supabase = await this.supabaseBridge.getSupabase();
108
+
109
+ const response = await supabase.auth.signOut();
110
+
111
+ this.supabaseBridge.throwIfError(response);
112
+ }
95
113
 
96
- if (!auth) {
97
- return;
114
+ public async exchangeSessionForCode(code: string): Promise<{
115
+ user: User;
116
+ session: Session;
117
+ }> {
118
+ if (code == null || !isString(code)) {
119
+ throw new ExecutorError('code is required');
98
120
  }
99
121
 
100
- try {
101
- const user = await this.credentialToken.parseToken(auth);
122
+ const supabase = await this.supabaseBridge.getSupabase();
123
+ const response = await supabase.auth.exchangeCodeForSession(code);
124
+ this.supabaseBridge.throwIfError(response);
102
125
 
103
- console.log('user', user);
126
+ this.logger.debug('exchangeSessionForCode', response.data);
104
127
 
105
- await this.userRepository.updateById(user.id, {
106
- credential_token: ''
107
- });
108
- } catch {
109
- return;
128
+ if (!response.data.user) {
129
+ throw new ExecutorError(API_USER_NOT_FOUND);
110
130
  }
111
131
 
112
- await this.userAuth.clear();
132
+ return response.data;
113
133
  }
114
134
  }
@@ -53,9 +53,6 @@ export class LocalesValidator implements ValidatorInterface<
53
53
  }
54
54
 
55
55
  export class LocalesImportValidator implements ValidatorInterface<ImportLocalesData> {
56
- /**
57
- * @override
58
- */
59
56
  public getHasAnyFilesLocale(
60
57
  values: FormData
61
58
  ): { language: LocaleType; value: FormDataEntryValue }[] {
@@ -27,9 +27,6 @@ const passwordSchema = z
27
27
  .regex(/^\S+$/, { message: V_PASSWORD_SPECIAL_CHARS });
28
28
 
29
29
  export class LoginValidator implements ValidatorInterface<LoginValidatorData> {
30
- /**
31
- * @override
32
- */
33
30
  public validateEmail(data: unknown): void | ValidationFaildResult {
34
31
  const emailResult = emailSchema.safeParse(data);
35
32
  if (!emailResult.success) {
@@ -37,9 +34,6 @@ export class LoginValidator implements ValidatorInterface<LoginValidatorData> {
37
34
  }
38
35
  }
39
36
 
40
- /**
41
- * @override
42
- */
43
37
  public validatePassword(data: unknown): void | ValidationFaildResult {
44
38
  const passwordResult = passwordSchema.safeParse(data);
45
39
  if (!passwordResult.success) {
@@ -0,0 +1,68 @@
1
+ import { ExecutorError } from '@qlover/fe-corekit';
2
+ import { isPlainObject, isString } from 'lodash';
3
+ import type { ExtendedExecutorError } from './ExtendedExecutorError';
4
+ import type {
5
+ ValidationFaildResult,
6
+ ValidatorInterface
7
+ } from '../port/ValidatorInterface';
8
+ import type { EmailOtpType } from '@supabase/supabase-js';
9
+
10
+ export type SignupVerifyParamType = {
11
+ access_token: string;
12
+ expires_at: string;
13
+ expires_in: string;
14
+ refresh_token: string;
15
+ token_type: string;
16
+ type: EmailOtpType;
17
+ };
18
+
19
+ export const emailVerifyParamKeys = [
20
+ 'access_token',
21
+ 'expires_at',
22
+ 'expires_in',
23
+ 'refresh_token',
24
+ 'token_type',
25
+ 'type'
26
+ ] as const;
27
+
28
+ export class SignupVerifyValidator implements ValidatorInterface<SignupVerifyParamType> {
29
+ /**
30
+ * @override
31
+ */
32
+ public validate(data: unknown): void | ValidationFaildResult {
33
+ if (!isPlainObject(data)) {
34
+ return {
35
+ path: ['form'],
36
+ message: 'Invalid Signup verify params'
37
+ };
38
+ }
39
+
40
+ for (const key of emailVerifyParamKeys) {
41
+ if (
42
+ !(
43
+ isString((data as SignupVerifyParamType)[key]) &&
44
+ (data as SignupVerifyParamType)[key]
45
+ )
46
+ ) {
47
+ return {
48
+ path: [key],
49
+ message: `Invalid Signup verify ${key} params`
50
+ };
51
+ }
52
+ }
53
+ }
54
+ /**
55
+ * @override
56
+ */
57
+ public getThrow(data: unknown): SignupVerifyParamType {
58
+ const result = this.validate(data);
59
+
60
+ if (result == null) {
61
+ return data as SignupVerifyParamType;
62
+ }
63
+
64
+ const error: ExtendedExecutorError = new ExecutorError(result.message);
65
+ error.issues = [result];
66
+ throw error;
67
+ }
68
+ }
@@ -1,14 +1,14 @@
1
1
  'use client';
2
2
  import '@ant-design/v5-patch-for-react-19';
3
3
 
4
- import { useEffect, useState } from 'react';
5
4
  import { useLocale } from 'next-intl';
5
+ import { useEffect, useState } from 'react';
6
+ import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
6
7
  import { BootstrapClient } from '@/core/bootstraps/BootstrapClient';
8
+ import { I } from '@config/IOCIdentifier';
7
9
  import { useIOC } from '../hook/useIOC';
8
10
  import { useStrictEffect } from '../hook/useStrictEffect';
9
11
  import { useWarnTranslations } from '../hook/useWarnTranslations';
10
- import { I } from '@config/IOCIdentifier';
11
- import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
12
12
 
13
13
  export function BootstrapsProvider(props: { children: React.ReactNode }) {
14
14
  const IOC = useIOC();
@@ -10,18 +10,12 @@ export class LocalesImportEvent extends StoreInterface<LocalesImportEventState>
10
10
  super(() => new LocalesImportEventState());
11
11
  }
12
12
 
13
- /**
14
- * @override
15
- */
16
13
  protected validate(file: File): void {
17
14
  if (file.type !== 'application/json') {
18
15
  throw new Error('File must be a JSON file');
19
16
  }
20
17
  }
21
18
 
22
- /**
23
- * @override
24
- */
25
19
  public async onImport(type: LocaleType, file: File): Promise<void> {
26
20
  try {
27
21
  this.validate(file);
@@ -0,0 +1,29 @@
1
+ import { TeamOutlined } from '@ant-design/icons';
2
+ import { bootstrapServer } from '@/core/bootstraps/BootstrapServer';
3
+ import { ServerAuth } from '@/server/ServerAuth';
4
+ import { LocaleLink } from '../components/LocaleLink';
5
+
6
+ export async function AdminButton(props: {
7
+ adminTitle: string;
8
+ locale?: string;
9
+ }) {
10
+ const { adminTitle, locale } = props;
11
+ const hasAuth = await bootstrapServer.getIOC(ServerAuth).hasAuth();
12
+
13
+ if (!hasAuth) {
14
+ return null;
15
+ }
16
+
17
+ return (
18
+ <LocaleLink
19
+ data-testid="AdminButton"
20
+ key="admin-button"
21
+ href="/admin"
22
+ title={adminTitle}
23
+ locale={locale}
24
+ className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
25
+ >
26
+ <TeamOutlined className="text-lg text-text" />
27
+ </LocaleLink>
28
+ );
29
+ }