@qlover/create-app 0.7.8 → 0.7.10

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 (89) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/dist/index.cjs +3 -3
  3. package/dist/index.js +3 -3
  4. package/dist/templates/next-app/.env.template +8 -4
  5. package/dist/templates/next-app/config/IOCIdentifier.ts +4 -1
  6. package/dist/templates/next-app/config/Identifier/api.ts +20 -0
  7. package/dist/templates/next-app/config/Identifier/index.ts +2 -0
  8. package/dist/templates/next-app/config/Identifier/page.home.ts +7 -0
  9. package/dist/templates/next-app/config/Identifier/page.login.ts +2 -2
  10. package/dist/templates/next-app/config/Identifier/page.register.ts +43 -22
  11. package/dist/templates/next-app/config/Identifier/validator.ts +34 -0
  12. package/dist/templates/next-app/config/i18n/HomeI18n .ts +22 -0
  13. package/dist/templates/next-app/config/i18n/index.ts +1 -0
  14. package/dist/templates/next-app/config/i18n/register18n.ts +44 -0
  15. package/dist/templates/next-app/config/theme.ts +1 -0
  16. package/dist/templates/next-app/migrations/schema/UserSchema.ts +13 -0
  17. package/dist/templates/next-app/migrations/sql/1694244000000.sql +10 -0
  18. package/dist/templates/next-app/package.json +15 -6
  19. package/dist/templates/next-app/public/locales/en.json +17 -2
  20. package/dist/templates/next-app/public/locales/zh.json +17 -2
  21. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +21 -0
  22. package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +10 -0
  23. package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -7
  24. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +28 -16
  25. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +10 -17
  26. package/dist/templates/next-app/src/app/[locale]/page.tsx +94 -102
  27. package/dist/templates/next-app/src/app/[locale]/register/RegisterForm.tsx +176 -0
  28. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +79 -0
  29. package/dist/templates/next-app/src/app/api/user/login/route.ts +50 -0
  30. package/dist/templates/next-app/src/app/api/user/logout/route.ts +27 -0
  31. package/dist/templates/next-app/src/app/api/user/register/route.ts +50 -0
  32. package/dist/templates/next-app/src/base/cases/AppConfig.ts +19 -0
  33. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +35 -0
  34. package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +70 -0
  35. package/dist/templates/next-app/src/base/cases/RouterService.ts +4 -0
  36. package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +67 -0
  37. package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +48 -0
  38. package/dist/templates/next-app/src/base/port/AppApiInterface.ts +14 -0
  39. package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +15 -0
  40. package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +18 -0
  41. package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +92 -0
  42. package/dist/templates/next-app/src/base/port/DBTableInterface.ts +3 -0
  43. package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +3 -2
  44. package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +3 -0
  45. package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +6 -0
  46. package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +3 -2
  47. package/dist/templates/next-app/src/base/services/I18nService.ts +9 -45
  48. package/dist/templates/next-app/src/base/services/UserService.ts +9 -8
  49. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +63 -0
  50. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +72 -0
  51. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +48 -0
  52. package/dist/templates/next-app/src/base/services/appApi/AppUserType.ts +51 -0
  53. package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +43 -0
  54. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +30 -5
  55. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +4 -3
  56. package/dist/templates/next-app/src/server/AppErrorApi.ts +10 -0
  57. package/dist/templates/next-app/src/server/AppSuccessApi.ts +7 -0
  58. package/dist/templates/next-app/src/server/PasswordEncrypt.ts +12 -0
  59. package/dist/templates/next-app/src/server/ServerAuth.ts +50 -0
  60. package/dist/templates/next-app/src/server/SupabaseBridge.ts +124 -0
  61. package/dist/templates/next-app/src/server/UserCredentialToken.ts +49 -0
  62. package/dist/templates/next-app/src/server/port/CrentialTokenInterface.ts +5 -0
  63. package/dist/templates/next-app/src/server/port/ServerInterface.ts +22 -0
  64. package/dist/templates/next-app/src/server/port/UserAuthInterface.ts +9 -0
  65. package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +15 -0
  66. package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +8 -0
  67. package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +23 -0
  68. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +63 -0
  69. package/dist/templates/next-app/src/server/services/UserService.ts +105 -0
  70. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +79 -0
  71. package/dist/templates/next-app/src/styles/css/antd-themes/_default.css +12 -0
  72. package/dist/templates/next-app/src/styles/css/antd-themes/dark.css +26 -0
  73. package/dist/templates/next-app/src/styles/css/antd-themes/pink.css +16 -0
  74. package/dist/templates/next-app/src/styles/css/page.css +4 -3
  75. package/dist/templates/next-app/src/styles/css/themes/_default.css +1 -0
  76. package/dist/templates/next-app/src/styles/css/themes/dark.css +1 -0
  77. package/dist/templates/next-app/src/styles/css/themes/pink.css +1 -0
  78. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +6 -11
  79. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +27 -0
  80. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +8 -1
  81. package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +49 -21
  82. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +20 -10
  83. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +92 -48
  84. package/dist/templates/next-app/tsconfig.json +3 -1
  85. package/package.json +2 -2
  86. package/dist/templates/next-app/src/base/cases/ServerAuth.ts +0 -17
  87. package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +0 -3
  88. package/dist/templates/next-app/src/base/port/ServerInterface.ts +0 -12
  89. /package/dist/templates/next-app/src/{app/[locale]/login → uikit/components}/FeatureItem.tsx +0 -0
@@ -0,0 +1,48 @@
1
+ import { RequestCommonPlugin } from '@qlover/corekit-bridge';
2
+ import { FetchURLPlugin } from '@qlover/fe-corekit';
3
+ import { DialogErrorPlugin } from '@/base/cases/DialogErrorPlugin';
4
+ import { RequestEncryptPlugin } from '@/base/cases/RequestEncryptPlugin';
5
+ import { StringEncryptor } from '@/base/cases/StringEncryptor';
6
+ import { AppApiPlugin } from './AppApiPlugin';
7
+ import { AppUserApi } from './AppUserApi';
8
+ import type { UserApiConfig } from './AppUserType';
9
+ import type {
10
+ BootstrapContext,
11
+ BootstrapExecutorPlugin
12
+ } from '@qlover/corekit-bridge';
13
+ import type { ExecutorContext, SerializerIneterface } from '@qlover/fe-corekit';
14
+
15
+ export class AppUserApiBootstrap implements BootstrapExecutorPlugin {
16
+ readonly pluginName = 'AppUserApiBootstrap';
17
+
18
+ constructor(protected serializer: SerializerIneterface) {}
19
+
20
+ onBefore({ parameters: { ioc } }: BootstrapContext): void | Promise<void> {
21
+ const appUserApi = ioc.get<AppUserApi>(AppUserApi);
22
+
23
+ appUserApi.usePlugin(new FetchURLPlugin());
24
+ appUserApi.usePlugin(new RequestEncryptPlugin(ioc.get(StringEncryptor)));
25
+ appUserApi.usePlugin(
26
+ new RequestCommonPlugin({
27
+ requestDataSerializer: this.requestDataSerializer.bind(this)
28
+ })
29
+ );
30
+ appUserApi.usePlugin(new AppApiPlugin());
31
+ appUserApi.usePlugin(ioc.get(DialogErrorPlugin));
32
+ }
33
+
34
+ protected requestDataSerializer(
35
+ data: unknown,
36
+ context: ExecutorContext<UserApiConfig>
37
+ ): unknown {
38
+ if (data instanceof FormData) {
39
+ return data;
40
+ }
41
+
42
+ if (context.parameters?.responseType === 'json') {
43
+ return this.serializer.serialize(data);
44
+ }
45
+
46
+ return data;
47
+ }
48
+ }
@@ -0,0 +1,51 @@
1
+ import type { RequestEncryptPluginProps } from '@/base/cases/RequestEncryptPlugin';
2
+ import type { AppApiResponse } from '@/base/port/AppApiInterface';
3
+ import type {
4
+ RequestAdapterConfig,
5
+ RequestAdapterResponse,
6
+ RequestTransactionInterface
7
+ } from '@qlover/fe-corekit';
8
+
9
+ export interface UserApiConfig<Request = unknown>
10
+ extends RequestAdapterConfig<Request>,
11
+ RequestEncryptPluginProps<Request> {}
12
+
13
+ /**
14
+ * UserApiResponse
15
+ *
16
+ * @description
17
+ * UserApiResponse is the response for the UserApi.
18
+ *
19
+ * extends:
20
+ * - RequestAdapterResponse<Request, Response>
21
+ */
22
+ export type UserApiResponse<
23
+ Request = unknown,
24
+ Response = unknown
25
+ > = RequestAdapterResponse<Request, AppApiResponse<Response>>;
26
+
27
+ /**
28
+ * UserApi common transaction
29
+ */
30
+ export interface UserApiTransaction<Request = unknown, Response = unknown>
31
+ extends RequestTransactionInterface<
32
+ UserApiConfig<Request>,
33
+ UserApiResponse<Request, Response>
34
+ > {
35
+ data: UserApiConfig<Request>['data'];
36
+ }
37
+
38
+ export type UserApiLoginTransaction = UserApiTransaction<
39
+ { email: string; password: string },
40
+ {
41
+ token: string;
42
+ }
43
+ >;
44
+
45
+ export type UserApiRegisterTransaction = UserApiTransaction<
46
+ {
47
+ email: string;
48
+ password: string;
49
+ },
50
+ UserApiTransaction['response']['data']
51
+ >;
@@ -0,0 +1,43 @@
1
+ import { RequestCommonPlugin } from '@qlover/corekit-bridge';
2
+ import {
3
+ FetchAbortPlugin,
4
+ RequestTransaction,
5
+ RequestAdapterFetch,
6
+ FetchURLPlugin
7
+ } from '@qlover/fe-corekit';
8
+ import { inject, injectable } from 'inversify';
9
+ import type { MigrationApiInterface } from '@/base/port/MigrationApiInterface';
10
+ import type { RequestAdapterConfig } from '@qlover/fe-corekit';
11
+
12
+ /**
13
+ * UserApi
14
+ *
15
+ * @description
16
+ * UserApi is a client for the user API.
17
+ *
18
+ */
19
+ @injectable()
20
+ export class MigrationsApi
21
+ extends RequestTransaction<RequestAdapterConfig>
22
+ implements MigrationApiInterface
23
+ {
24
+ constructor(
25
+ @inject(FetchAbortPlugin) protected abortPlugin: FetchAbortPlugin
26
+ ) {
27
+ super(
28
+ new RequestAdapterFetch({
29
+ baseURL: '/api/admin/migrations',
30
+ responseType: 'json'
31
+ })
32
+ );
33
+
34
+ this.usePlugin(new FetchURLPlugin()).usePlugin(new RequestCommonPlugin());
35
+ }
36
+
37
+ async init(): Promise<unknown> {
38
+ return this.request({
39
+ url: '/init',
40
+ method: 'POST'
41
+ });
42
+ }
43
+ }
@@ -1,13 +1,19 @@
1
1
  import {
2
+ type ServiceIdentifier,
2
3
  type BootstrapContextValue,
3
4
  type BootstrapExecutorPlugin,
4
5
  type IOCContainerInterface,
5
6
  type IOCFunctionInterface,
6
7
  type LoggerInterface
7
8
  } from '@qlover/corekit-bridge';
8
- import { AsyncExecutor, type ExecutorPlugin } from '@qlover/fe-corekit';
9
+ import {
10
+ AsyncExecutor,
11
+ type ExecutorError,
12
+ type PromiseTask,
13
+ type ExecutorPlugin
14
+ } from '@qlover/fe-corekit';
9
15
  import { I, type IOCIdentifierMapServer } from '@config/IOCIdentifier';
10
- import type { ServerInterface } from '@/base/port/ServerInterface';
16
+ import type { ServerInterface } from '@/server/port/ServerInterface';
11
17
  import { ServerIOC } from '../serverIoc/ServerIOC';
12
18
 
13
19
  export interface BootstrapServerResult {
@@ -20,6 +26,12 @@ export interface BootstrapServerContextValue extends BootstrapContextValue {
20
26
  messages: Record<string, string>;
21
27
  }
22
28
 
29
+ interface BootstrapServerContext {
30
+ logger: LoggerInterface;
31
+ root: Record<string, unknown>;
32
+ IOC: IOCFunctionInterface<IOCIdentifierMapServer, IOCContainerInterface>;
33
+ }
34
+
23
35
  export class BootstrapServer implements ServerInterface {
24
36
  protected executor: AsyncExecutor;
25
37
  protected root: Record<string, unknown> = {};
@@ -43,11 +55,12 @@ export class BootstrapServer implements ServerInterface {
43
55
  getIOC<T extends keyof IOCIdentifierMapServer>(
44
56
  identifier: T
45
57
  ): IOCIdentifierMapServer[T];
46
- getIOC(
47
- identifier?: keyof IOCIdentifierMapServer
58
+ getIOC<T>(serviceIdentifier: ServiceIdentifier<T>): T;
59
+ getIOC<T extends keyof IOCIdentifierMapServer>(
60
+ identifier?: T
48
61
  ):
49
62
  | IOCFunctionInterface<IOCIdentifierMapServer, IOCContainerInterface>
50
- | IOCIdentifierMapServer[keyof IOCIdentifierMapServer] {
63
+ | IOCIdentifierMapServer[T] {
51
64
  if (identifier === undefined) {
52
65
  return this.IOC;
53
66
  }
@@ -75,4 +88,16 @@ export class BootstrapServer implements ServerInterface {
75
88
  this.executor.use(plugin as ExecutorPlugin<unknown>);
76
89
  return this;
77
90
  }
91
+
92
+ execNoError<Result>(
93
+ task?: PromiseTask<Result, BootstrapServerContext>
94
+ ): Promise<Result | ExecutorError> {
95
+ const context = {
96
+ logger: this.logger,
97
+ root: this.root,
98
+ IOC: this.IOC
99
+ };
100
+
101
+ return this.executor.execNoError(context, task);
102
+ }
78
103
  }
@@ -1,4 +1,5 @@
1
- import { IOCIdentifier } from '@config/IOCIdentifier';
1
+ import { I, IOCIdentifier } from '@config/IOCIdentifier';
2
+ import { AppUserApiBootstrap } from '@/base/services/appApi/AppUserApiBootstrap';
2
3
  import { IocIdentifierTest } from './IocIdentifierTest';
3
4
  import { printBootstrap } from './PrintBootstrap';
4
5
  import type { BootstrapAppArgs } from './BootstrapClient';
@@ -28,8 +29,8 @@ export class BootstrapsRegistry {
28
29
  i18nService.setPathname(this.args.pathname);
29
30
 
30
31
  const bootstrapList: BootstrapExecutorPlugin[] = [
31
- i18nService
32
- // new UserApiBootstarp(),
32
+ i18nService,
33
+ new AppUserApiBootstrap(IOC(I.JSONSerializer))
33
34
  // new FeApiBootstarp(),
34
35
  // AiApiBootstarp,
35
36
  // IOC(IOCIdentifier.I18nKeyErrorPlugin)
@@ -0,0 +1,10 @@
1
+ import type { AppApiErrorInterface } from '@/base/port/AppApiInterface';
2
+
3
+ export class AppErrorApi implements AppApiErrorInterface {
4
+ readonly success = false;
5
+
6
+ constructor(
7
+ public readonly id: string,
8
+ public readonly message?: string
9
+ ) {}
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { AppApiSuccessInterface } from '@/base/port/AppApiInterface';
2
+
3
+ export class AppSuccessApi<T = unknown> implements AppApiSuccessInterface<T> {
4
+ readonly success = true;
5
+
6
+ constructor(public readonly data?: T) {}
7
+ }
@@ -0,0 +1,12 @@
1
+ import crypto from 'crypto';
2
+ import type { Encryptor } from '@qlover/fe-corekit';
3
+
4
+ export class PasswordEncrypt implements Encryptor<string, string> {
5
+ encrypt(password: string): string {
6
+ return crypto.createHash('md5').update(password).digest('hex');
7
+ }
8
+
9
+ decrypt(): string {
10
+ throw new Error('Md5Encrypt is not decryptable');
11
+ }
12
+ }
@@ -0,0 +1,50 @@
1
+ import { inject, injectable } from 'inversify';
2
+ import { cookies } from 'next/headers';
3
+ import { I } from '@config/IOCIdentifier';
4
+ import type { AppConfig } from '@/base/cases/AppConfig';
5
+ import { UserCredentialToken } from './UserCredentialToken';
6
+ import type { UserAuthInterface } from './port/UserAuthInterface';
7
+
8
+ @injectable()
9
+ export class ServerAuth implements UserAuthInterface {
10
+ protected userTokenKey: string;
11
+ constructor(
12
+ @inject(I.AppConfig) protected server: AppConfig,
13
+ @inject(UserCredentialToken)
14
+ protected userCredentialToken: UserCredentialToken
15
+ ) {
16
+ this.userTokenKey = server.userTokenKey;
17
+ }
18
+
19
+ async setAuth(credential_token: string): Promise<void> {
20
+ const cookieStore = await cookies();
21
+
22
+ cookieStore.set(this.userTokenKey, credential_token);
23
+ }
24
+
25
+ async hasAuth(): Promise<boolean> {
26
+ const token = await this.getAuth();
27
+
28
+ if (!token) {
29
+ return false;
30
+ }
31
+
32
+ try {
33
+ const user = await this.userCredentialToken.parseToken(token);
34
+
35
+ return !!user;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ async getAuth(): Promise<string> {
42
+ const cookieStore = await cookies();
43
+ return cookieStore.get(this.userTokenKey)?.value || '';
44
+ }
45
+
46
+ async clear(): Promise<void> {
47
+ const cookieStore = await cookies();
48
+ cookieStore.delete(this.userTokenKey);
49
+ }
50
+ }
@@ -0,0 +1,124 @@
1
+ import {
2
+ createClient,
3
+ type PostgrestSingleResponse,
4
+ type SupabaseClient
5
+ } from '@supabase/supabase-js';
6
+ import { injectable, inject } from 'inversify';
7
+ import { I } from '@config/IOCIdentifier';
8
+ import type { AppConfig } from '@/base/cases/AppConfig';
9
+ import type {
10
+ BridgeEvent,
11
+ DBBridgeInterface,
12
+ Where
13
+ } from '@/base/port/DBBridgeInterface';
14
+ import type { LoggerInterface } from '@qlover/logger';
15
+ import type { PostgrestFilterBuilder } from '@supabase/postgrest-js';
16
+
17
+ const whereHandlerMaps = {
18
+ '=': 'eq',
19
+ '!=': 'neq',
20
+ '>': 'gt',
21
+ '<': 'lt',
22
+ '>=': 'gte',
23
+ '<=': 'lte'
24
+ };
25
+
26
+ @injectable()
27
+ export class SupabaseBridge implements DBBridgeInterface {
28
+ protected supabase: SupabaseClient;
29
+
30
+ constructor(
31
+ @inject(I.AppConfig) appConfig: AppConfig,
32
+ @inject(I.Logger) protected logger: LoggerInterface
33
+ ) {
34
+ this.supabase = createClient(
35
+ appConfig.supabaseUrl,
36
+ appConfig.supabaseAnonKey
37
+ );
38
+ }
39
+
40
+ getSupabase(): SupabaseClient {
41
+ return this.supabase;
42
+ }
43
+
44
+ async execSql(sql: string): Promise<PostgrestSingleResponse<unknown>> {
45
+ const res = await this.supabase.rpc('exec_sql', { sql });
46
+ return this.catch(res);
47
+ }
48
+
49
+ protected async catch(
50
+ result: PostgrestSingleResponse<unknown>
51
+ ): Promise<PostgrestSingleResponse<unknown>> {
52
+ if (result.error) {
53
+ this.logger.info(result);
54
+ throw new Error(result.error.message);
55
+ }
56
+ return result;
57
+ }
58
+
59
+ protected handleWhere(
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ handler: PostgrestFilterBuilder<any, any, any, any, string, unknown, any>,
62
+ wheres: Where[]
63
+ ): void {
64
+ for (const where of wheres) {
65
+ const [key, operator, value] = where;
66
+ const opr = whereHandlerMaps[operator];
67
+ if (!opr) {
68
+ throw new Error(`Unsupported where operation: ${value}`);
69
+ }
70
+
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ if (typeof (handler as any)[opr] !== 'function') {
73
+ throw new Error(`Unsupported where operation: ${value}`);
74
+ }
75
+
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ (handler as any)[opr](key, value);
78
+ }
79
+ }
80
+
81
+ async add(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>> {
82
+ const { table, data } = event;
83
+ if (!data) {
84
+ throw new Error('Data is required for add operation');
85
+ }
86
+ const res = await this.supabase
87
+ .from(table)
88
+ .insert(Array.isArray(data) ? data : [data])
89
+ .select();
90
+ return this.catch(res);
91
+ }
92
+
93
+ async update(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>> {
94
+ const { table, data, where } = event;
95
+ if (!data) {
96
+ throw new Error('Data is required for update operation');
97
+ }
98
+
99
+ const handler = this.supabase.from(table).update(data);
100
+
101
+ this.handleWhere(handler, where ?? []);
102
+
103
+ return this.catch(await handler);
104
+ }
105
+
106
+ async delete(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>> {
107
+ const { table, where } = event;
108
+ const handler = this.supabase.from(table).delete();
109
+
110
+ this.handleWhere(handler, where ?? []);
111
+
112
+ return this.catch(await handler);
113
+ }
114
+
115
+ async get(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>> {
116
+ const { table, fields = '*', where } = event;
117
+ const selectFields = Array.isArray(fields) ? fields.join(',') : fields;
118
+ const handler = this.supabase.from(table).select(selectFields);
119
+
120
+ this.handleWhere(handler, where ?? []);
121
+
122
+ return this.catch(await handler);
123
+ }
124
+ }
@@ -0,0 +1,49 @@
1
+ import { inject, injectable } from 'inversify';
2
+ import jwt from 'jsonwebtoken';
3
+ import { AppConfig } from '@/base/cases/AppConfig';
4
+ import type { CrentialTokenInterface } from './port/CrentialTokenInterface';
5
+ import type { UserSchema } from '@migrations/schema/UserSchema';
6
+
7
+ export type UserCredentialTokenValue = Pick<UserSchema, 'id' | 'email'>;
8
+
9
+ @injectable()
10
+ export class UserCredentialToken
11
+ implements CrentialTokenInterface<UserCredentialTokenValue>
12
+ {
13
+ protected jwtSecret: string;
14
+ protected jwtExpiresIn: string;
15
+
16
+ constructor(@inject(AppConfig) protected config: AppConfig) {
17
+ this.jwtSecret = config.jwtSecret;
18
+ this.jwtExpiresIn = config.jwtExpiresIn;
19
+ }
20
+
21
+ async generateToken(
22
+ data: UserCredentialTokenValue,
23
+ options: { expiresIn?: string } = {}
24
+ ): Promise<string> {
25
+ const { expiresIn = '30 days' } = options;
26
+
27
+ return jwt.sign({ i: data.id, e: data.email }, this.jwtSecret, {
28
+ expiresIn: expiresIn as jwt.SignOptions['expiresIn'],
29
+ algorithm: 'HS256',
30
+ noTimestamp: true
31
+ });
32
+ }
33
+
34
+ async parseToken(token: string): Promise<UserCredentialTokenValue> {
35
+ try {
36
+ const decoded = jwt.verify(token, this.jwtSecret) as {
37
+ i: UserSchema['id'];
38
+ e: string;
39
+ };
40
+
41
+ return {
42
+ id: decoded.i,
43
+ email: decoded.e
44
+ };
45
+ } catch {
46
+ throw new Error('Invalid token');
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,5 @@
1
+ export interface CrentialTokenInterface<T> {
2
+ generateToken(data: T, options?: { expiresIn?: string }): Promise<string>;
3
+
4
+ parseToken(token: string): Promise<T>;
5
+ }
@@ -0,0 +1,22 @@
1
+ import { type ExecutorError, type PromiseTask } from '@qlover/fe-corekit';
2
+ import { type IOCIdentifierMapServer } from '@config/IOCIdentifier';
3
+ import type {
4
+ ServiceIdentifier,
5
+ IOCContainerInterface,
6
+ IOCFunctionInterface,
7
+ LoggerInterface
8
+ } from '@qlover/corekit-bridge';
9
+
10
+ export interface ServerInterface {
11
+ readonly logger: LoggerInterface;
12
+
13
+ getIOC(): IOCFunctionInterface<IOCIdentifierMapServer, IOCContainerInterface>;
14
+ getIOC<T extends keyof IOCIdentifierMapServer>(
15
+ identifier: T
16
+ ): IOCIdentifierMapServer[T];
17
+ getIOC<T>(serviceIdentifier: ServiceIdentifier<T>): T;
18
+
19
+ execNoError<Result>(
20
+ task?: PromiseTask<Result, unknown>
21
+ ): Promise<Result | ExecutorError>;
22
+ }
@@ -0,0 +1,9 @@
1
+ export interface UserAuthInterface {
2
+ setAuth(credential_token: string): Promise<void>;
3
+
4
+ getAuth(): Promise<string>;
5
+
6
+ clear(): Promise<void>;
7
+
8
+ hasAuth(): Promise<boolean>;
9
+ }
@@ -0,0 +1,15 @@
1
+ import type { DBTableInterface } from '@/base/port/DBTableInterface';
2
+ import type { UserSchema } from '@migrations/schema/UserSchema';
3
+
4
+ export interface UserRepositoryInterface extends DBTableInterface {
5
+ getUserByEmail(email: string): Promise<UserSchema | null>;
6
+ add(params: {
7
+ email: string;
8
+ password: string;
9
+ }): Promise<UserSchema[] | null>;
10
+
11
+ updateById(
12
+ id: number,
13
+ params: Partial<Omit<UserSchema, 'id' | 'created_at'>>
14
+ ): Promise<void>;
15
+ }
@@ -0,0 +1,8 @@
1
+ import type { UserSchema } from '@migrations/schema/UserSchema';
2
+
3
+ export interface UserServiceInterface {
4
+ register(params: { email: string; password: string }): Promise<UserSchema>;
5
+ login(params: { email: string; password: string }): Promise<unknown>;
6
+
7
+ logout(): Promise<void>;
8
+ }
@@ -0,0 +1,23 @@
1
+ export interface ValidationFaildResult {
2
+ path: PropertyKey[];
3
+ message: string;
4
+ }
5
+
6
+ export interface ValidatorInterface {
7
+ /**
8
+ * Validate the data and return validation result
9
+ * @param data - The data to validate
10
+ * @returns true if validation passes, or ValidationError if validation fails
11
+ */
12
+ validate(
13
+ data: unknown
14
+ ): Promise<void | ValidationFaildResult> | void | ValidationFaildResult;
15
+
16
+ /**
17
+ * Get the data if it is valid, otherwise throw an error with validation details
18
+ * @param data - The data to validate
19
+ * @returns The data if it is valid
20
+ * @throws {import('@qlover/fe-corekit').ExecutorError} if the data is invalid, with validation errors
21
+ */
22
+ getThrow(data: unknown): unknown;
23
+ }
@@ -0,0 +1,63 @@
1
+ import { inject, injectable } from 'inversify';
2
+ import { isEmpty, last } from 'lodash';
3
+ import type { DBBridgeInterface } from '@/base/port/DBBridgeInterface';
4
+ import { SupabaseBridge } from '../SupabaseBridge';
5
+ import type { UserRepositoryInterface } from '../port/UserRepositoryInterface';
6
+ import type { UserSchema } from '@migrations/schema/UserSchema';
7
+
8
+ @injectable()
9
+ export class UserRepository implements UserRepositoryInterface {
10
+ readonly name = 'fe_users';
11
+
12
+ constructor(@inject(SupabaseBridge) protected dbBridge: DBBridgeInterface) {}
13
+
14
+ getAll(): Promise<unknown> {
15
+ return this.dbBridge.get({ table: this.name });
16
+ }
17
+
18
+ /**
19
+ * @override
20
+ */
21
+ async getUserByEmail(email: string): Promise<UserSchema | null> {
22
+ const result = await this.dbBridge.get({
23
+ table: this.name,
24
+ where: [['email', '=', email]]
25
+ });
26
+
27
+ if (isEmpty(result.data)) {
28
+ return null;
29
+ }
30
+
31
+ return last(result.data as UserSchema[]) ?? null;
32
+ }
33
+
34
+ /**
35
+ * @override
36
+ */
37
+ async add(params: {
38
+ email: string;
39
+ password: string;
40
+ }): Promise<UserSchema[] | null> {
41
+ const result = await this.dbBridge.add({
42
+ table: this.name,
43
+ data: params
44
+ });
45
+
46
+ if (isEmpty(result.data)) {
47
+ return null;
48
+ }
49
+
50
+ return result.data as UserSchema[];
51
+ }
52
+
53
+ async updateById(
54
+ id: number,
55
+ params: Partial<Omit<UserSchema, 'id' | 'created_at'>>
56
+ ): Promise<void> {
57
+ await this.dbBridge.update({
58
+ table: this.name,
59
+ data: params,
60
+ where: [['id', '=', id]]
61
+ });
62
+ }
63
+ }