@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.
- package/CHANGELOG.md +140 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +3 -3
- package/dist/templates/next-app/.env.template +8 -4
- package/dist/templates/next-app/config/IOCIdentifier.ts +4 -1
- package/dist/templates/next-app/config/Identifier/api.ts +20 -0
- package/dist/templates/next-app/config/Identifier/index.ts +2 -0
- package/dist/templates/next-app/config/Identifier/page.home.ts +7 -0
- package/dist/templates/next-app/config/Identifier/page.login.ts +2 -2
- package/dist/templates/next-app/config/Identifier/page.register.ts +43 -22
- package/dist/templates/next-app/config/Identifier/validator.ts +34 -0
- package/dist/templates/next-app/config/i18n/HomeI18n .ts +22 -0
- package/dist/templates/next-app/config/i18n/index.ts +1 -0
- package/dist/templates/next-app/config/i18n/register18n.ts +44 -0
- package/dist/templates/next-app/config/theme.ts +1 -0
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +13 -0
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +10 -0
- package/dist/templates/next-app/package.json +15 -6
- package/dist/templates/next-app/public/locales/en.json +17 -2
- package/dist/templates/next-app/public/locales/zh.json +17 -2
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +21 -0
- package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +10 -0
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -7
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +28 -16
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +10 -17
- package/dist/templates/next-app/src/app/[locale]/page.tsx +94 -102
- package/dist/templates/next-app/src/app/[locale]/register/RegisterForm.tsx +176 -0
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +79 -0
- package/dist/templates/next-app/src/app/api/user/login/route.ts +50 -0
- package/dist/templates/next-app/src/app/api/user/logout/route.ts +27 -0
- package/dist/templates/next-app/src/app/api/user/register/route.ts +50 -0
- package/dist/templates/next-app/src/base/cases/AppConfig.ts +19 -0
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +35 -0
- package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +70 -0
- package/dist/templates/next-app/src/base/cases/RouterService.ts +4 -0
- package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +67 -0
- package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +48 -0
- package/dist/templates/next-app/src/base/port/AppApiInterface.ts +14 -0
- package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +15 -0
- package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +18 -0
- package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +92 -0
- package/dist/templates/next-app/src/base/port/DBTableInterface.ts +3 -0
- package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +3 -2
- package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +3 -0
- package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +6 -0
- package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +3 -2
- package/dist/templates/next-app/src/base/services/I18nService.ts +9 -45
- package/dist/templates/next-app/src/base/services/UserService.ts +9 -8
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +63 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +72 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +48 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserType.ts +51 -0
- package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +43 -0
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +30 -5
- package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +4 -3
- package/dist/templates/next-app/src/server/AppErrorApi.ts +10 -0
- package/dist/templates/next-app/src/server/AppSuccessApi.ts +7 -0
- package/dist/templates/next-app/src/server/PasswordEncrypt.ts +12 -0
- package/dist/templates/next-app/src/server/ServerAuth.ts +50 -0
- package/dist/templates/next-app/src/server/SupabaseBridge.ts +124 -0
- package/dist/templates/next-app/src/server/UserCredentialToken.ts +49 -0
- package/dist/templates/next-app/src/server/port/CrentialTokenInterface.ts +5 -0
- package/dist/templates/next-app/src/server/port/ServerInterface.ts +22 -0
- package/dist/templates/next-app/src/server/port/UserAuthInterface.ts +9 -0
- package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +15 -0
- package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +8 -0
- package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +23 -0
- package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +63 -0
- package/dist/templates/next-app/src/server/services/UserService.ts +105 -0
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +79 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/_default.css +12 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/dark.css +26 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pink.css +16 -0
- package/dist/templates/next-app/src/styles/css/page.css +4 -3
- package/dist/templates/next-app/src/styles/css/themes/_default.css +1 -0
- package/dist/templates/next-app/src/styles/css/themes/dark.css +1 -0
- package/dist/templates/next-app/src/styles/css/themes/pink.css +1 -0
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +6 -11
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +27 -0
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +8 -1
- package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +49 -21
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +20 -10
- package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +92 -48
- package/dist/templates/next-app/tsconfig.json +3 -1
- package/package.json +2 -2
- package/dist/templates/next-app/src/base/cases/ServerAuth.ts +0 -17
- package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +0 -3
- package/dist/templates/next-app/src/base/port/ServerInterface.ts +0 -12
- /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 {
|
|
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 '@/
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
32
|
+
i18nService,
|
|
33
|
+
new AppUserApiBootstrap(IOC(I.JSONSerializer))
|
|
33
34
|
// new FeApiBootstarp(),
|
|
34
35
|
// AiApiBootstarp,
|
|
35
36
|
// IOC(IOCIdentifier.I18nKeyErrorPlugin)
|
|
@@ -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,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,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
|
+
}
|