@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,70 @@
1
+ import { clone, isObject } from 'lodash';
2
+ import type {
3
+ Encryptor,
4
+ ExecutorContext,
5
+ ExecutorPlugin,
6
+ RequestAdapterConfig
7
+ } from '@qlover/fe-corekit';
8
+
9
+ export interface RequestEncryptPluginProps<Request = unknown>
10
+ extends RequestAdapterConfig<Request> {
11
+ /**
12
+ * 加密密码在 HTTP 请求中
13
+ *
14
+ * - 如果为空,则不加密密码
15
+ * - 如果为字符串,则加密密码
16
+ * - 如果为数组,则加密密码
17
+ */
18
+ encryptProps?: string[] | string;
19
+ }
20
+
21
+ export class RequestEncryptPlugin
22
+ implements ExecutorPlugin<RequestEncryptPluginProps>
23
+ {
24
+ readonly pluginName = 'RequestEncryptPlugin';
25
+
26
+ constructor(protected encryptor: Encryptor<string, string>) {}
27
+
28
+ onBefore(
29
+ context: ExecutorContext<RequestEncryptPluginProps>
30
+ ): void | Promise<void> {
31
+ const { responseType, encryptProps } = context.parameters;
32
+
33
+ if (
34
+ responseType === 'json' &&
35
+ isObject(context.parameters.data) &&
36
+ encryptProps
37
+ ) {
38
+ context.parameters.data = this.encryptData(
39
+ clone(context.parameters.data),
40
+ encryptProps
41
+ );
42
+ }
43
+ }
44
+
45
+ protected encryptData<T extends object>(
46
+ data: T,
47
+ encryptProps?: string | string[]
48
+ ): T {
49
+ if (typeof encryptProps === 'string') {
50
+ const targetValue = data[encryptProps as keyof T];
51
+
52
+ if (typeof targetValue === 'string') {
53
+ const newValue = this.encryptor.encrypt(targetValue);
54
+ Object.assign(data, { [encryptProps]: newValue });
55
+ }
56
+ }
57
+
58
+ if (Array.isArray(encryptProps)) {
59
+ encryptProps.forEach((prop) => {
60
+ const targetValue = data[prop as keyof T];
61
+ if (typeof targetValue === 'string') {
62
+ const newValue = this.encryptor.encrypt(targetValue);
63
+ Object.assign(data, { [prop]: newValue });
64
+ }
65
+ });
66
+ }
67
+
68
+ return data;
69
+ }
70
+ }
@@ -25,6 +25,10 @@ export class RouterService implements RouterInterface {
25
25
  this.goto('/');
26
26
  }
27
27
 
28
+ gotoLogin(): void {
29
+ this.goto('/login');
30
+ }
31
+
28
32
  setLocale(locale: string): void {
29
33
  this.locale = locale;
30
34
  }
@@ -0,0 +1,67 @@
1
+ import { Base64Serializer, type Encryptor } from '@qlover/fe-corekit';
2
+ import { inject, injectable } from 'inversify';
3
+ import { I } from '@config/IOCIdentifier';
4
+ import type { AppConfig } from './AppConfig';
5
+
6
+ @injectable()
7
+ export class StringEncryptor implements Encryptor<string, string> {
8
+ private readonly key;
9
+
10
+ constructor(
11
+ @inject(I.AppConfig) appConfig: AppConfig,
12
+ @inject(Base64Serializer) protected base64Serializer: Base64Serializer
13
+ ) {
14
+ if (!appConfig.stringEncryptorKey) {
15
+ throw new Error('StringEncryptorKey is not set');
16
+ }
17
+
18
+ this.key = appConfig.stringEncryptorKey;
19
+ }
20
+
21
+ protected encryptWithKey(str: string, key: string): string {
22
+ const result = [];
23
+ for (let i = 0; i < str.length; i++) {
24
+ // 使用字符的 Unicode 值和密钥进行混合运算
25
+ const charCode = str.charCodeAt(i);
26
+ const keyChar = key.charCodeAt(i % key.length);
27
+ // 使用多个运算来增加复杂度
28
+ const encrypted = (charCode + keyChar * 13) ^ (keyChar + i * 7);
29
+ result.push(String.fromCharCode(encrypted % 65536)); // 确保结果在有效的 Unicode 范围内
30
+ }
31
+ return result.join('');
32
+ }
33
+
34
+ protected decryptWithKey(str: string, key: string): string {
35
+ const result = [];
36
+ for (let i = 0; i < str.length; i++) {
37
+ const charCode = str.charCodeAt(i);
38
+ const keyChar = key.charCodeAt(i % key.length);
39
+ // 反向运算还原原始字符
40
+ const decrypted = (charCode ^ (keyChar + i * 7)) - keyChar * 13;
41
+ result.push(String.fromCharCode(decrypted));
42
+ }
43
+ return result.join('');
44
+ }
45
+
46
+ encrypt(value: string): string {
47
+ try {
48
+ const encrypted = this.encryptWithKey(value, this.key);
49
+ return this.base64Serializer.serialize(encrypted);
50
+ } catch (error) {
51
+ console.error('Encryption failed:', error);
52
+ throw new Error('Encryption failed');
53
+ }
54
+ }
55
+
56
+ decrypt(encryptedValue: string): string {
57
+ try {
58
+ // 1. 先用 base64 反序列化
59
+ const decoded = this.base64Serializer.deserialize(encryptedValue);
60
+ // 2. 然后用密钥解密
61
+ return this.decryptWithKey(decoded, this.key);
62
+ } catch (error) {
63
+ console.error('Decryption failed:', error);
64
+ throw new Error('Decryption failed');
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,48 @@
1
+ import { inject, injectable } from 'inversify';
2
+ import { AppUserApi } from '../services/appApi/AppUserApi';
3
+ import type { AppApiSuccessInterface } from '../port/AppApiInterface';
4
+ import type { AppUserApiInterface } from '../port/AppUserApiInterface';
5
+ import type { UserSchema } from '@migrations/schema/UserSchema';
6
+ import type {
7
+ LoginResponseData,
8
+ UserAuthApiInterface,
9
+ UserAuthStoreInterface
10
+ } from '@qlover/corekit-bridge';
11
+
12
+ @injectable()
13
+ export class UserServiceApi implements UserAuthApiInterface<UserSchema> {
14
+ protected store: UserAuthStoreInterface<UserSchema> | null = null;
15
+
16
+ constructor(@inject(AppUserApi) protected appUserApi: AppUserApiInterface) {}
17
+
18
+ getStore(): UserAuthStoreInterface<UserSchema> | null {
19
+ return this.store;
20
+ }
21
+ setStore(store: UserAuthStoreInterface<UserSchema>): void {
22
+ this.store = store;
23
+ }
24
+
25
+ async login(params: {
26
+ email: string;
27
+ password: string;
28
+ }): Promise<LoginResponseData> {
29
+ const response = await this.appUserApi.login(params);
30
+ return (response as AppApiSuccessInterface).data as LoginResponseData;
31
+ }
32
+
33
+ async register(params: {
34
+ email: string;
35
+ password: string;
36
+ }): Promise<LoginResponseData> {
37
+ const response = await this.appUserApi.register(params);
38
+ return (response as AppApiSuccessInterface).data as LoginResponseData;
39
+ }
40
+
41
+ async logout(): Promise<void> {
42
+ await this.appUserApi.logout();
43
+ }
44
+
45
+ getUserInfo(loginData: LoginResponseData): Promise<UserSchema> {
46
+ return Promise.resolve(loginData as unknown as UserSchema);
47
+ }
48
+ }
@@ -0,0 +1,14 @@
1
+ export interface AppApiErrorInterface {
2
+ success: false;
3
+ id: string;
4
+ message?: string;
5
+ }
6
+
7
+ export interface AppApiSuccessInterface<T = unknown> {
8
+ success: true;
9
+ data?: T;
10
+ }
11
+
12
+ export type AppApiResponse<T = unknown> =
13
+ | AppApiErrorInterface
14
+ | AppApiSuccessInterface<T>;
@@ -0,0 +1,15 @@
1
+ import type { AppApiResponse } from './AppApiInterface';
2
+
3
+ export interface AppUserApiInterface {
4
+ login(params: {
5
+ email: string;
6
+ password: string;
7
+ }): Promise<AppApiResponse<unknown>>;
8
+
9
+ register(params: {
10
+ email: string;
11
+ password: string;
12
+ }): Promise<AppApiResponse<unknown>>;
13
+
14
+ logout(): Promise<AppApiResponse<unknown>>;
15
+ }
@@ -0,0 +1,18 @@
1
+ import type { PostgrestSingleResponse } from '@supabase/supabase-js';
2
+
3
+ export type WhereOperation = '=' | '!=' | '>' | '<' | '>=' | '<=';
4
+ export type Where = [string, WhereOperation, string | number];
5
+
6
+ export interface BridgeEvent {
7
+ table: string;
8
+ fields?: string | string[];
9
+ where?: Where[];
10
+ data?: unknown;
11
+ }
12
+
13
+ export interface DBBridgeInterface {
14
+ add(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
15
+ update(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
16
+ delete(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
17
+ get(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
18
+ }
@@ -0,0 +1,92 @@
1
+ export interface MigrationConfig {
2
+ /**
3
+ * Migration version number or identifier
4
+ */
5
+ version: string | number;
6
+
7
+ /**
8
+ * Description of what this migration does
9
+ */
10
+ description: string;
11
+
12
+ /**
13
+ * Timestamp when migration was created
14
+ */
15
+ timestamp?: number;
16
+ }
17
+
18
+ export interface MigrationResult {
19
+ /**
20
+ * Whether the migration was successful
21
+ */
22
+ success: boolean;
23
+
24
+ /**
25
+ * Error message if migration failed
26
+ */
27
+ error?: string;
28
+
29
+ /**
30
+ * Migration version that was executed
31
+ */
32
+ version: string | number;
33
+
34
+ /**
35
+ * Time taken to execute the migration in milliseconds
36
+ */
37
+ executionTime?: number;
38
+ }
39
+
40
+ export interface DBMigrationInterface {
41
+ /**
42
+ * Get current database version
43
+ */
44
+ getCurrentVersion(): Promise<string | number>;
45
+
46
+ /**
47
+ * Get list of all applied migrations
48
+ */
49
+ getAppliedMigrations(): Promise<MigrationConfig[]>;
50
+
51
+ /**
52
+ * Get list of pending migrations that need to be applied
53
+ */
54
+ getPendingMigrations(): Promise<MigrationConfig[]>;
55
+
56
+ /**
57
+ * Apply a specific migration
58
+ * @param migration Migration configuration
59
+ */
60
+ applyMigration(migration: MigrationConfig): Promise<MigrationResult>;
61
+
62
+ /**
63
+ * Rollback a specific migration
64
+ * @param migration Migration configuration
65
+ */
66
+ rollbackMigration(migration: MigrationConfig): Promise<MigrationResult>;
67
+
68
+ /**
69
+ * Apply all pending migrations
70
+ */
71
+ migrateUp(): Promise<MigrationResult[]>;
72
+
73
+ /**
74
+ * Rollback to a specific version
75
+ * @param targetVersion Version to rollback to
76
+ */
77
+ migrateDown(targetVersion: string | number): Promise<MigrationResult[]>;
78
+
79
+ /**
80
+ * Create a new migration file/record
81
+ * @param name Name/description of the migration
82
+ */
83
+ createMigration(name: string): Promise<MigrationConfig>;
84
+
85
+ /**
86
+ * Validate migration history and database state
87
+ */
88
+ validateMigrations(): Promise<{
89
+ valid: boolean;
90
+ errors?: string[];
91
+ }>;
92
+ }
@@ -0,0 +1,3 @@
1
+ export interface DBTableInterface {
2
+ readonly name: string;
3
+ }
@@ -2,11 +2,12 @@ import {
2
2
  StoreInterface,
3
3
  type StoreStateInterface
4
4
  } from '@qlover/corekit-bridge';
5
+ import type { Locale } from '@/i18n/routing';
5
6
  import type { i18nConfig } from '@config/i18n';
6
7
 
7
8
  export type SupportedLocale = (typeof i18nConfig.supportedLngs)[number];
8
9
  export type SupportedNamespace = typeof i18nConfig.fallbackLng;
9
- export type I18nServiceLocale = SupportedLocale;
10
+ export type I18nServiceLocale = Locale;
10
11
 
11
12
  export class I18nServiceState implements StoreStateInterface {
12
13
  loading: boolean = false;
@@ -16,7 +17,7 @@ export abstract class I18nServiceInterface
16
17
  extends StoreInterface<I18nServiceState>
17
18
  implements I18nServiceInterface
18
19
  {
19
- abstract t(key: string, params?: Record<string, unknown>): Promise<string>;
20
+ abstract t(key: string, params?: Record<string, unknown>): string;
20
21
  abstract changeLanguage(language: I18nServiceLocale): Promise<void>;
21
22
  abstract changeLoading(loading: boolean): void;
22
23
  abstract getCurrentLanguage(): Promise<I18nServiceLocale>;
@@ -0,0 +1,3 @@
1
+ export interface MigrationApiInterface {
2
+ init(): Promise<unknown>;
3
+ }
@@ -0,0 +1,6 @@
1
+ export interface ServerApiResponseInterface<T> {
2
+ success?: boolean;
3
+ message?: string;
4
+ data?: T;
5
+ error?: string;
6
+ }
@@ -1,8 +1,9 @@
1
- import { ImagicaAuthService } from '@brain-toolkit/bridge';
1
+ import { UserAuthService } from '@qlover/corekit-bridge';
2
+ import type { UserSchema } from '@migrations/schema/UserSchema';
2
3
  import type { ExecutorPlugin } from '@qlover/fe-corekit';
3
4
 
4
5
  export abstract class UserServiceInterface
5
- extends ImagicaAuthService
6
+ extends UserAuthService<UserSchema>
6
7
  implements ExecutorPlugin
7
8
  {
8
9
  readonly pluginName = 'UserService';
@@ -1,4 +1,3 @@
1
- import { useLocaleRoutes } from '@config/common';
2
1
  import { i18nConfig } from '@config/i18n';
3
2
  import {
4
3
  I18nServiceInterface,
@@ -11,20 +10,13 @@ type TranslationFunction = ReturnType<typeof useTranslations>;
11
10
 
12
11
  export class I18nService extends I18nServiceInterface {
13
12
  readonly pluginName = 'I18nService';
14
- private initialized: boolean = false;
15
- private pathname: string = '';
16
- private translator: TranslationFunction | null = null;
13
+ protected pathname: string = '';
14
+ protected translator: TranslationFunction | null = null;
17
15
 
18
16
  constructor() {
19
17
  super(() => new I18nServiceState(i18nConfig.fallbackLng));
20
18
  }
21
19
 
22
- private async ensureInitialized(): Promise<void> {
23
- if (!this.initialized) {
24
- throw new Error('I18nService not initialized');
25
- }
26
- }
27
-
28
20
  setPathname(pathname: string): void {
29
21
  this.pathname = pathname;
30
22
  }
@@ -36,44 +28,19 @@ export class I18nService extends I18nServiceInterface {
36
28
  /**
37
29
  * @override
38
30
  */
39
- async onBefore(): Promise<void> {
40
- if (this.initialized) {
41
- return;
42
- }
43
-
44
- this.initialized = true;
45
-
46
- // 初始化语言状态
47
- const currentLang = this.getCurrentLanguageFromPath();
48
- if (this.isValidLanguage(currentLang)) {
49
- this.emit({ ...this.state, language: currentLang });
50
- }
51
- }
52
-
53
- private getCurrentLanguageFromPath(): string {
54
- const paths = this.pathname.split('/');
55
-
56
- for (const path of paths) {
57
- if (this.isValidLanguage(path)) {
58
- return path;
59
- }
60
- }
61
-
62
- return i18nConfig.fallbackLng;
63
- }
31
+ async onBefore(): Promise<void> {}
64
32
 
65
33
  override async changeLanguage(language: I18nServiceLocale): Promise<void> {
66
34
  try {
67
35
  this.changeLoading(true);
68
- await this.ensureInitialized();
69
36
 
70
37
  // 在这里我们只需要更新状态,因为实际的语言切换会通过路由处理
71
38
  this.emit({ ...this.state, language });
72
39
 
73
40
  // 如果不使用本地化路由,则保存语言设置到本地存储
74
- if (!useLocaleRoutes && typeof window !== 'undefined') {
75
- window.localStorage.setItem('i18nextLng', language);
76
- }
41
+ // if (!useLocaleRoutes && typeof window !== 'undefined') {
42
+ // window.localStorage.setItem('i18nextLng', language);
43
+ // }
77
44
  } finally {
78
45
  this.changeLoading(false);
79
46
  }
@@ -84,8 +51,7 @@ export class I18nService extends I18nServiceInterface {
84
51
  }
85
52
 
86
53
  override async getCurrentLanguage(): Promise<I18nServiceLocale> {
87
- await this.ensureInitialized();
88
- return this.getCurrentLanguageFromPath() as I18nServiceLocale;
54
+ return this.state.language;
89
55
  }
90
56
 
91
57
  override isValidLanguage(language: string): language is I18nServiceLocale {
@@ -96,12 +62,10 @@ export class I18nService extends I18nServiceInterface {
96
62
  return [...i18nConfig.supportedLngs];
97
63
  }
98
64
 
99
- override async t(
65
+ override t(
100
66
  key: string,
101
67
  params?: Record<string, string | number | Date>
102
- ): Promise<string> {
103
- await this.ensureInitialized();
104
-
68
+ ): string {
105
69
  if (!this.translator) {
106
70
  return key;
107
71
  }
@@ -1,18 +1,19 @@
1
- import { CookieStorage } from '@qlover/corekit-bridge';
2
1
  import { injectable, inject } from 'inversify';
3
2
  import { AppConfig } from '../cases/AppConfig';
3
+ import { UserServiceApi } from '../cases/UserServiceApi';
4
4
  import { UserServiceInterface } from '../port/UserServiceInterface';
5
+ import type { UserSchema } from '@migrations/schema/UserSchema';
6
+ import type { UserAuthApiInterface } from '@qlover/corekit-bridge';
5
7
 
6
8
  @injectable()
7
9
  export class UserService extends UserServiceInterface {
8
- constructor(@inject(AppConfig) protected appConfig: AppConfig) {
9
- super({
10
+ constructor(
11
+ @inject(AppConfig) protected appConfig: AppConfig,
12
+ @inject(UserServiceApi) protected userApi: UserAuthApiInterface<UserSchema>
13
+ ) {
14
+ super(userApi, {
10
15
  credentialStorage: {
11
- key: appConfig.userTokenKey,
12
- storage: new CookieStorage()
13
- },
14
- requestConfig: {
15
- env: appConfig.env !== 'production' ? 'development' : 'production'
16
+ key: appConfig.userTokenKey
16
17
  }
17
18
  });
18
19
  }
@@ -0,0 +1,63 @@
1
+ import {
2
+ ExecutorError,
3
+ RequestError,
4
+ type ExecutorContext,
5
+ type ExecutorPlugin
6
+ } from '@qlover/fe-corekit';
7
+ import type { AppApiErrorInterface } from '@/base/port/AppApiInterface';
8
+ import type { UserApiConfig } from './AppUserType';
9
+
10
+ export class AppApiPlugin implements ExecutorPlugin {
11
+ readonly pluginName = 'AppApiPlugin';
12
+
13
+ isAppApiErrorInterface(value: unknown): value is AppApiErrorInterface {
14
+ return (
15
+ typeof value === 'object' &&
16
+ value !== null &&
17
+ 'success' in value &&
18
+ value.success === false &&
19
+ 'id' in value &&
20
+ typeof value.id === 'string'
21
+ );
22
+ }
23
+
24
+ onSuccess(context: ExecutorContext<unknown>): void | Promise<void> {
25
+ const response = context.returnValue;
26
+
27
+ if (this.isAppApiErrorInterface(response)) {
28
+ throw new Error(response.message || response.id);
29
+ }
30
+ }
31
+
32
+ async onError(
33
+ context: ExecutorContext<UserApiConfig>
34
+ ): Promise<ExecutorError | void> {
35
+ const { error, parameters } = context;
36
+
37
+ if (error instanceof RequestError && parameters.responseType === 'json') {
38
+ // @ts-expect-error response is not defined in Error
39
+ let response = error?.response;
40
+
41
+ if (response instanceof Response) {
42
+ // clone the response to avoid mutating the original response
43
+ response = response.clone();
44
+
45
+ const json = await this.getResponseJson(response);
46
+
47
+ if (this.isAppApiErrorInterface(json)) {
48
+ const newError = new ExecutorError(json.message || json.id);
49
+ // context.error = newError;
50
+ return newError;
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ protected async getResponseJson(response: Response): Promise<unknown> {
57
+ try {
58
+ return await response.json();
59
+ } catch {
60
+ return {};
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,72 @@
1
+ import {
2
+ FetchAbortPlugin,
3
+ RequestTransaction,
4
+ RequestAdapterFetch
5
+ } from '@qlover/fe-corekit';
6
+ import { inject, injectable } from 'inversify';
7
+ import type { AppApiResponse } from '@/base/port/AppApiInterface';
8
+ import type { AppUserApiInterface } from '@/base/port/AppUserApiInterface';
9
+ import type {
10
+ UserApiConfig,
11
+ UserApiLoginTransaction,
12
+ UserApiRegisterTransaction
13
+ } from './AppUserType';
14
+
15
+ /**
16
+ * UserApi
17
+ *
18
+ * @description
19
+ * UserApi is a client for the user API.
20
+ *
21
+ */
22
+ @injectable()
23
+ export class AppUserApi
24
+ extends RequestTransaction<UserApiConfig>
25
+ implements AppUserApiInterface
26
+ {
27
+ constructor(
28
+ @inject(FetchAbortPlugin) protected abortPlugin: FetchAbortPlugin
29
+ ) {
30
+ super(
31
+ new RequestAdapterFetch({
32
+ baseURL: '/api',
33
+ responseType: 'json'
34
+ })
35
+ );
36
+ }
37
+
38
+ async login(
39
+ params: UserApiLoginTransaction['data']
40
+ ): Promise<AppApiResponse<unknown>> {
41
+ const response = await this.request<UserApiLoginTransaction>({
42
+ url: '/user/login',
43
+ method: 'POST',
44
+ data: params,
45
+ encryptProps: 'password'
46
+ });
47
+
48
+ return response.data;
49
+ }
50
+
51
+ async register(
52
+ params: UserApiRegisterTransaction['data']
53
+ ): Promise<AppApiResponse<unknown>> {
54
+ const response = await this.request<UserApiRegisterTransaction>({
55
+ url: '/user/register',
56
+ method: 'POST',
57
+ data: params,
58
+ encryptProps: 'password'
59
+ });
60
+
61
+ return response.data;
62
+ }
63
+
64
+ async logout(): Promise<AppApiResponse<unknown>> {
65
+ const response = await this.request({
66
+ url: '/user/logout',
67
+ method: 'POST'
68
+ });
69
+
70
+ return response.data as AppApiResponse<unknown>;
71
+ }
72
+ }