create-forgeon 0.1.2 → 0.1.6
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/README.md +19 -17
- package/bin/create-forgeon.mjs +22 -22
- package/package.json +1 -1
- package/src/cli/add-help.mjs +12 -12
- package/src/cli/add-options.mjs +54 -54
- package/src/cli/add-options.test.mjs +24 -24
- package/src/cli/help.mjs +20 -20
- package/src/cli/options.mjs +121 -121
- package/src/cli/options.test.mjs +41 -41
- package/src/cli/prompt-select.mjs +94 -94
- package/src/cli/prompt-select.test.mjs +148 -148
- package/src/constants.mjs +13 -13
- package/src/core/docs.mjs +128 -128
- package/src/core/docs.test.mjs +91 -91
- package/src/core/install.mjs +14 -14
- package/src/core/scaffold.mjs +48 -45
- package/src/core/validate.mjs +12 -12
- package/src/core/validate.test.mjs +73 -73
- package/src/databases/index.mjs +26 -26
- package/src/frameworks/index.mjs +32 -32
- package/src/infrastructure/proxy.mjs +12 -12
- package/src/modules/docs.mjs +70 -70
- package/src/modules/executor.mjs +39 -21
- package/src/modules/executor.test.mjs +95 -45
- package/src/modules/i18n.mjs +283 -0
- package/src/modules/registry.mjs +43 -35
- package/src/presets/i18n.mjs +228 -180
- package/src/presets/index.mjs +2 -2
- package/src/presets/proxy.mjs +32 -32
- package/src/run-add-module.mjs +47 -47
- package/src/run-create-forgeon.mjs +72 -72
- package/src/utils/fs.mjs +26 -26
- package/src/utils/values.mjs +20 -20
- package/templates/base/.dockerignore +7 -7
- package/templates/base/.editorconfig +11 -11
- package/templates/base/README.md +46 -46
- package/templates/base/apps/api/Dockerfile +24 -24
- package/templates/base/apps/api/package.json +39 -39
- package/templates/base/apps/api/prisma/migrations/0001_init/migration.sql +11 -11
- package/templates/base/apps/api/prisma/schema.prisma +14 -14
- package/templates/base/apps/api/prisma/seed.ts +19 -19
- package/templates/base/apps/api/src/app.module.ts +32 -32
- package/templates/base/apps/api/src/common/dto/echo-query.dto.ts +5 -5
- package/templates/base/apps/api/src/common/filters/app-exception.filter.ts +129 -129
- package/templates/base/apps/api/src/config/app.config.ts +12 -12
- package/templates/base/apps/api/src/health/health.controller.ts +30 -30
- package/templates/base/apps/api/src/main.ts +25 -25
- package/templates/base/apps/api/src/prisma/prisma.module.ts +8 -8
- package/templates/base/apps/api/src/prisma/prisma.service.ts +26 -26
- package/templates/base/apps/api/tsconfig.build.json +8 -8
- package/templates/base/apps/api/tsconfig.json +8 -8
- package/templates/base/apps/web/Dockerfile +12 -12
- package/templates/base/apps/web/index.html +11 -11
- package/templates/base/apps/web/package.json +21 -21
- package/templates/base/apps/web/src/App.tsx +35 -35
- package/templates/base/apps/web/src/main.tsx +8 -8
- package/templates/base/apps/web/src/styles.css +32 -32
- package/templates/base/apps/web/tsconfig.json +17 -17
- package/templates/base/apps/web/vite.config.ts +14 -14
- package/templates/base/docs/AI/ARCHITECTURE.md +37 -37
- package/templates/base/docs/AI/MODULE_SPEC.md +56 -56
- package/templates/base/docs/AI/PROJECT.md +31 -31
- package/templates/base/docs/AI/TASKS.md +57 -57
- package/templates/base/docs/README.md +6 -6
- package/templates/base/infra/caddy/Caddyfile +15 -15
- package/templates/base/infra/docker/.env.example +9 -9
- package/templates/base/infra/docker/caddy.Dockerfile +15 -15
- package/templates/base/infra/docker/compose.caddy.yml +44 -44
- package/templates/base/infra/docker/compose.nginx.yml +44 -44
- package/templates/base/infra/docker/compose.none.yml +37 -37
- package/templates/base/infra/docker/compose.yml +44 -44
- package/templates/base/infra/docker/nginx.Dockerfile +15 -15
- package/templates/base/infra/nginx/nginx.conf +31 -31
- package/templates/base/package.json +23 -23
- package/templates/base/packages/core/README.md +2 -2
- package/templates/base/packages/core/package.json +13 -13
- package/templates/base/packages/core/tsconfig.json +7 -7
- package/templates/base/packages/i18n/package.json +18 -18
- package/templates/base/packages/i18n/src/forgeon-i18n.module.ts +45 -45
- package/templates/base/packages/i18n/tsconfig.json +8 -8
- package/templates/base/pnpm-workspace.yaml +2 -2
- package/templates/base/resources/i18n/en/common.json +4 -4
- package/templates/base/resources/i18n/en/validation.json +2 -2
- package/templates/base/resources/i18n/uk/common.json +4 -4
- package/templates/base/resources/i18n/uk/validation.json +2 -2
- package/templates/base/tsconfig.base.json +16 -16
- package/templates/docs-fragments/AI_ARCHITECTURE/00_title.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/10_layout_base.md +6 -6
- package/templates/docs-fragments/AI_ARCHITECTURE/11_layout_infra.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/12_layout_i18n_resources.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/20_env_base.md +4 -4
- package/templates/docs-fragments/AI_ARCHITECTURE/21_env_i18n.md +3 -3
- package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db.md +7 -7
- package/templates/docs-fragments/AI_ARCHITECTURE/31_docker_runtime.md +5 -5
- package/templates/docs-fragments/AI_ARCHITECTURE/32_scope_freeze.md +5 -5
- package/templates/docs-fragments/AI_ARCHITECTURE/40_docs_generation.md +9 -9
- package/templates/docs-fragments/AI_ARCHITECTURE/50_extension_points.md +8 -8
- package/templates/docs-fragments/AI_PROJECT/00_title.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/10_what_is.md +3 -3
- package/templates/docs-fragments/AI_PROJECT/20_structure_base.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/21_structure_i18n.md +2 -0
- package/templates/docs-fragments/AI_PROJECT/22_structure_docker.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/23_structure_docs.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/30_run_dev.md +8 -8
- package/templates/docs-fragments/AI_PROJECT/31_run_docker.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/32_proxy_notes.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/32_proxy_notes_none.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/33_i18n_notes.md +2 -0
- package/templates/docs-fragments/AI_PROJECT/40_change_boundaries_base.md +3 -3
- package/templates/docs-fragments/AI_PROJECT/41_change_boundaries_docker.md +1 -1
- package/templates/docs-fragments/README/00_title.md +3 -3
- package/templates/docs-fragments/README/10_stack.md +8 -8
- package/templates/docs-fragments/README/20_quick_start_dev_intro.md +6 -6
- package/templates/docs-fragments/README/21_quick_start_dev_db_docker.md +4 -4
- package/templates/docs-fragments/README/21_quick_start_dev_db_local.md +1 -1
- package/templates/docs-fragments/README/22_quick_start_dev_outro.md +7 -7
- package/templates/docs-fragments/README/30_quick_start_docker.md +7 -7
- package/templates/docs-fragments/README/30_quick_start_docker_none.md +9 -9
- package/templates/docs-fragments/README/31_proxy_preset_caddy.md +9 -9
- package/templates/docs-fragments/README/31_proxy_preset_nginx.md +8 -8
- package/templates/docs-fragments/README/31_proxy_preset_none.md +6 -6
- package/templates/docs-fragments/README/32_prisma_container_start.md +5 -5
- package/templates/docs-fragments/README/40_i18n.md +14 -8
- package/templates/docs-fragments/README/90_next_steps.md +7 -7
- package/templates/module-fragments/i18n/00_title.md +5 -0
- package/templates/module-fragments/i18n/10_overview.md +9 -0
- package/templates/module-fragments/i18n/20_scope.md +7 -0
- package/templates/module-fragments/i18n/90_status_implemented.md +3 -0
- package/templates/module-fragments/jwt-auth/00_title.md +1 -1
- package/templates/module-fragments/jwt-auth/10_overview.md +6 -6
- package/templates/module-fragments/jwt-auth/20_scope.md +7 -7
- package/templates/module-fragments/jwt-auth/90_status_planned.md +3 -3
- package/templates/module-fragments/queue/00_title.md +1 -1
- package/templates/module-fragments/queue/10_overview.md +6 -6
- package/templates/module-fragments/queue/20_scope.md +7 -7
- package/templates/module-fragments/queue/90_status_planned.md +3 -3
- package/templates/module-presets/i18n/apps/web/src/App.tsx +61 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/package.json +14 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/src/index.ts +7 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/tsconfig.json +8 -0
- package/templates/module-presets/i18n/packages/i18n-web/package.json +17 -0
- package/templates/module-presets/i18n/packages/i18n-web/src/index.ts +50 -0
- package/templates/module-presets/i18n/packages/i18n-web/tsconfig.json +8 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { IsNotEmpty } from 'class-validator';
|
|
2
|
-
|
|
3
|
-
export class EchoQueryDto {
|
|
4
|
-
@IsNotEmpty({ message: 'validation.required' })
|
|
5
|
-
value!: string;
|
|
1
|
+
import { IsNotEmpty } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
export class EchoQueryDto {
|
|
4
|
+
@IsNotEmpty({ message: 'validation.required' })
|
|
5
|
+
value!: string;
|
|
6
6
|
}
|
|
@@ -1,130 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArgumentsHost,
|
|
3
|
-
Catch,
|
|
4
|
-
ExceptionFilter,
|
|
5
|
-
HttpException,
|
|
6
|
-
HttpStatus,
|
|
7
|
-
Injectable,
|
|
8
|
-
Optional,
|
|
9
|
-
} from '@nestjs/common';
|
|
10
|
-
import { I18nService } from 'nestjs-i18n';
|
|
11
|
-
import { Request, Response } from 'express';
|
|
12
|
-
|
|
13
|
-
@Injectable()
|
|
14
|
-
@Catch()
|
|
15
|
-
export class AppExceptionFilter implements ExceptionFilter {
|
|
16
|
-
constructor(@Optional() private readonly i18n?: I18nService) {}
|
|
17
|
-
|
|
18
|
-
catch(exception: unknown, host: ArgumentsHost): void {
|
|
19
|
-
const context = host.switchToHttp();
|
|
20
|
-
const response = context.getResponse<Response>();
|
|
21
|
-
const request = context.getRequest<Request>();
|
|
22
|
-
|
|
23
|
-
const status =
|
|
24
|
-
exception instanceof HttpException
|
|
25
|
-
? exception.getStatus()
|
|
26
|
-
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
27
|
-
|
|
28
|
-
const payload =
|
|
29
|
-
exception instanceof HttpException
|
|
30
|
-
? exception.getResponse()
|
|
31
|
-
: { message: 'Internal server error' };
|
|
32
|
-
|
|
33
|
-
const code = this.resolveCode(status);
|
|
34
|
-
const message = this.resolveMessage(payload, status, request);
|
|
35
|
-
const details = this.resolveDetails(payload);
|
|
36
|
-
|
|
37
|
-
response.status(status).json({
|
|
38
|
-
error: {
|
|
39
|
-
code,
|
|
40
|
-
message,
|
|
41
|
-
...(details !== undefined ? { details } : {}),
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private resolveCode(status: number): string {
|
|
47
|
-
switch (status) {
|
|
48
|
-
case HttpStatus.BAD_REQUEST:
|
|
49
|
-
return 'validation_error';
|
|
50
|
-
case HttpStatus.UNAUTHORIZED:
|
|
51
|
-
return 'unauthorized';
|
|
52
|
-
case HttpStatus.FORBIDDEN:
|
|
53
|
-
return 'forbidden';
|
|
54
|
-
case HttpStatus.NOT_FOUND:
|
|
55
|
-
return 'not_found';
|
|
56
|
-
case HttpStatus.CONFLICT:
|
|
57
|
-
return 'conflict';
|
|
58
|
-
default:
|
|
59
|
-
return 'internal_error';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private resolveMessage(
|
|
64
|
-
payload: unknown,
|
|
65
|
-
status: number,
|
|
66
|
-
request: Request,
|
|
67
|
-
): string {
|
|
68
|
-
const lang = this.resolveLang(request);
|
|
69
|
-
|
|
70
|
-
if (status === HttpStatus.NOT_FOUND) {
|
|
71
|
-
return this.translate('errors.notFound', lang);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (typeof payload === 'string') {
|
|
75
|
-
return this.translate(payload, lang);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (typeof payload === 'object' && payload !== null) {
|
|
79
|
-
const obj = payload as { message?: string | string[] };
|
|
80
|
-
if (Array.isArray(obj.message) && obj.message.length > 0) {
|
|
81
|
-
return this.translate(obj.message[0], lang);
|
|
82
|
-
}
|
|
83
|
-
if (typeof obj.message === 'string') {
|
|
84
|
-
return this.translate(obj.message, lang);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return 'Internal server error';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private resolveDetails(payload: unknown): unknown {
|
|
92
|
-
if (typeof payload !== 'object' || payload === null) {
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const obj = payload as { message?: string | string[]; error?: string };
|
|
97
|
-
if (Array.isArray(obj.message) && obj.message.length > 1) {
|
|
98
|
-
return obj.message;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private resolveLang(request: Request): string | undefined {
|
|
105
|
-
const queryLang = request.query?.lang;
|
|
106
|
-
if (typeof queryLang === 'string' && queryLang.length > 0) {
|
|
107
|
-
return queryLang;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const header = request.headers['accept-language'];
|
|
111
|
-
if (typeof header === 'string' && header.length > 0) {
|
|
112
|
-
return header.split(',')[0].trim();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private translate(keyOrMessage: string, lang?: string): string {
|
|
119
|
-
if (!this.i18n) {
|
|
120
|
-
return keyOrMessage;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const translated = this.i18n.t(keyOrMessage, {
|
|
124
|
-
lang,
|
|
125
|
-
defaultValue: keyOrMessage,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
return typeof translated === 'string' ? translated : keyOrMessage;
|
|
129
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
ArgumentsHost,
|
|
3
|
+
Catch,
|
|
4
|
+
ExceptionFilter,
|
|
5
|
+
HttpException,
|
|
6
|
+
HttpStatus,
|
|
7
|
+
Injectable,
|
|
8
|
+
Optional,
|
|
9
|
+
} from '@nestjs/common';
|
|
10
|
+
import { I18nService } from 'nestjs-i18n';
|
|
11
|
+
import { Request, Response } from 'express';
|
|
12
|
+
|
|
13
|
+
@Injectable()
|
|
14
|
+
@Catch()
|
|
15
|
+
export class AppExceptionFilter implements ExceptionFilter {
|
|
16
|
+
constructor(@Optional() private readonly i18n?: I18nService) {}
|
|
17
|
+
|
|
18
|
+
catch(exception: unknown, host: ArgumentsHost): void {
|
|
19
|
+
const context = host.switchToHttp();
|
|
20
|
+
const response = context.getResponse<Response>();
|
|
21
|
+
const request = context.getRequest<Request>();
|
|
22
|
+
|
|
23
|
+
const status =
|
|
24
|
+
exception instanceof HttpException
|
|
25
|
+
? exception.getStatus()
|
|
26
|
+
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
27
|
+
|
|
28
|
+
const payload =
|
|
29
|
+
exception instanceof HttpException
|
|
30
|
+
? exception.getResponse()
|
|
31
|
+
: { message: 'Internal server error' };
|
|
32
|
+
|
|
33
|
+
const code = this.resolveCode(status);
|
|
34
|
+
const message = this.resolveMessage(payload, status, request);
|
|
35
|
+
const details = this.resolveDetails(payload);
|
|
36
|
+
|
|
37
|
+
response.status(status).json({
|
|
38
|
+
error: {
|
|
39
|
+
code,
|
|
40
|
+
message,
|
|
41
|
+
...(details !== undefined ? { details } : {}),
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private resolveCode(status: number): string {
|
|
47
|
+
switch (status) {
|
|
48
|
+
case HttpStatus.BAD_REQUEST:
|
|
49
|
+
return 'validation_error';
|
|
50
|
+
case HttpStatus.UNAUTHORIZED:
|
|
51
|
+
return 'unauthorized';
|
|
52
|
+
case HttpStatus.FORBIDDEN:
|
|
53
|
+
return 'forbidden';
|
|
54
|
+
case HttpStatus.NOT_FOUND:
|
|
55
|
+
return 'not_found';
|
|
56
|
+
case HttpStatus.CONFLICT:
|
|
57
|
+
return 'conflict';
|
|
58
|
+
default:
|
|
59
|
+
return 'internal_error';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private resolveMessage(
|
|
64
|
+
payload: unknown,
|
|
65
|
+
status: number,
|
|
66
|
+
request: Request,
|
|
67
|
+
): string {
|
|
68
|
+
const lang = this.resolveLang(request);
|
|
69
|
+
|
|
70
|
+
if (status === HttpStatus.NOT_FOUND) {
|
|
71
|
+
return this.translate('errors.notFound', lang);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof payload === 'string') {
|
|
75
|
+
return this.translate(payload, lang);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof payload === 'object' && payload !== null) {
|
|
79
|
+
const obj = payload as { message?: string | string[] };
|
|
80
|
+
if (Array.isArray(obj.message) && obj.message.length > 0) {
|
|
81
|
+
return this.translate(obj.message[0], lang);
|
|
82
|
+
}
|
|
83
|
+
if (typeof obj.message === 'string') {
|
|
84
|
+
return this.translate(obj.message, lang);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return 'Internal server error';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private resolveDetails(payload: unknown): unknown {
|
|
92
|
+
if (typeof payload !== 'object' || payload === null) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const obj = payload as { message?: string | string[]; error?: string };
|
|
97
|
+
if (Array.isArray(obj.message) && obj.message.length > 1) {
|
|
98
|
+
return obj.message;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private resolveLang(request: Request): string | undefined {
|
|
105
|
+
const queryLang = request.query?.lang;
|
|
106
|
+
if (typeof queryLang === 'string' && queryLang.length > 0) {
|
|
107
|
+
return queryLang;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const header = request.headers['accept-language'];
|
|
111
|
+
if (typeof header === 'string' && header.length > 0) {
|
|
112
|
+
return header.split(',')[0].trim();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private translate(keyOrMessage: string, lang?: string): string {
|
|
119
|
+
if (!this.i18n) {
|
|
120
|
+
return keyOrMessage;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const translated = this.i18n.t(keyOrMessage, {
|
|
124
|
+
lang,
|
|
125
|
+
defaultValue: keyOrMessage,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return typeof translated === 'string' ? translated : keyOrMessage;
|
|
129
|
+
}
|
|
130
130
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { registerAs } from '@nestjs/config';
|
|
2
|
-
|
|
3
|
-
const toBoolean = (value: string | undefined, defaultValue: boolean): boolean => {
|
|
4
|
-
if (value === undefined) return defaultValue;
|
|
5
|
-
return value.toLowerCase() === 'true';
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export default registerAs('app', () => ({
|
|
9
|
-
port: Number(process.env.PORT ?? 3000),
|
|
10
|
-
i18nEnabled: toBoolean(process.env.I18N_ENABLED, true),
|
|
11
|
-
i18nDefaultLang: process.env.I18N_DEFAULT_LANG ?? 'en',
|
|
12
|
-
i18nFallbackLang: process.env.I18N_FALLBACK_LANG ?? 'en',
|
|
1
|
+
import { registerAs } from '@nestjs/config';
|
|
2
|
+
|
|
3
|
+
const toBoolean = (value: string | undefined, defaultValue: boolean): boolean => {
|
|
4
|
+
if (value === undefined) return defaultValue;
|
|
5
|
+
return value.toLowerCase() === 'true';
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default registerAs('app', () => ({
|
|
9
|
+
port: Number(process.env.PORT ?? 3000),
|
|
10
|
+
i18nEnabled: toBoolean(process.env.I18N_ENABLED, true),
|
|
11
|
+
i18nDefaultLang: process.env.I18N_DEFAULT_LANG ?? 'en',
|
|
12
|
+
i18nFallbackLang: process.env.I18N_FALLBACK_LANG ?? 'en',
|
|
13
13
|
}));
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { Controller, Get, Optional, Query } from '@nestjs/common';
|
|
2
|
-
import { I18nService } from 'nestjs-i18n';
|
|
3
|
-
import { EchoQueryDto } from '../common/dto/echo-query.dto';
|
|
4
|
-
|
|
5
|
-
@Controller('health')
|
|
6
|
-
export class HealthController {
|
|
7
|
-
constructor(@Optional() private readonly i18n?: I18nService) {}
|
|
8
|
-
|
|
9
|
-
@Get()
|
|
10
|
-
getHealth(@Query('lang') lang?: string) {
|
|
11
|
-
return {
|
|
12
|
-
status: 'ok',
|
|
13
|
-
message: this.translate('common.ok', lang),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@Get('echo')
|
|
18
|
-
getEcho(@Query() query: EchoQueryDto) {
|
|
19
|
-
return { value: query.value };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private translate(key: string, lang?: string): string {
|
|
23
|
-
if (!this.i18n) {
|
|
24
|
-
if (key === 'common.ok') return 'OK';
|
|
25
|
-
return key;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const value = this.i18n.t(key, { lang, defaultValue: key });
|
|
29
|
-
return typeof value === 'string' ? value : key;
|
|
30
|
-
}
|
|
1
|
+
import { Controller, Get, Optional, Query } from '@nestjs/common';
|
|
2
|
+
import { I18nService } from 'nestjs-i18n';
|
|
3
|
+
import { EchoQueryDto } from '../common/dto/echo-query.dto';
|
|
4
|
+
|
|
5
|
+
@Controller('health')
|
|
6
|
+
export class HealthController {
|
|
7
|
+
constructor(@Optional() private readonly i18n?: I18nService) {}
|
|
8
|
+
|
|
9
|
+
@Get()
|
|
10
|
+
getHealth(@Query('lang') lang?: string) {
|
|
11
|
+
return {
|
|
12
|
+
status: 'ok',
|
|
13
|
+
message: this.translate('common.ok', lang),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@Get('echo')
|
|
18
|
+
getEcho(@Query() query: EchoQueryDto) {
|
|
19
|
+
return { value: query.value };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private translate(key: string, lang?: string): string {
|
|
23
|
+
if (!this.i18n) {
|
|
24
|
+
if (key === 'common.ok') return 'OK';
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = this.i18n.t(key, { lang, defaultValue: key });
|
|
29
|
+
return typeof value === 'string' ? value : key;
|
|
30
|
+
}
|
|
31
31
|
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import 'reflect-metadata';
|
|
2
|
-
import { ValidationPipe } from '@nestjs/common';
|
|
3
|
-
import { ConfigService } from '@nestjs/config';
|
|
4
|
-
import { NestFactory } from '@nestjs/core';
|
|
5
|
-
import { AppModule } from './app.module';
|
|
6
|
-
import { AppExceptionFilter } from './common/filters/app-exception.filter';
|
|
7
|
-
|
|
8
|
-
async function bootstrap() {
|
|
9
|
-
const app = await NestFactory.create(AppModule);
|
|
10
|
-
|
|
11
|
-
app.setGlobalPrefix('api');
|
|
12
|
-
app.useGlobalPipes(
|
|
13
|
-
new ValidationPipe({
|
|
14
|
-
whitelist: true,
|
|
15
|
-
transform: true,
|
|
16
|
-
}),
|
|
17
|
-
);
|
|
18
|
-
app.useGlobalFilters(app.get(AppExceptionFilter));
|
|
19
|
-
|
|
20
|
-
const configService = app.get(ConfigService);
|
|
21
|
-
const port = configService.get<number>('app.port') ?? 3000;
|
|
22
|
-
|
|
23
|
-
await app.listen(port);
|
|
24
|
-
}
|
|
25
|
-
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { ValidationPipe } from '@nestjs/common';
|
|
3
|
+
import { ConfigService } from '@nestjs/config';
|
|
4
|
+
import { NestFactory } from '@nestjs/core';
|
|
5
|
+
import { AppModule } from './app.module';
|
|
6
|
+
import { AppExceptionFilter } from './common/filters/app-exception.filter';
|
|
7
|
+
|
|
8
|
+
async function bootstrap() {
|
|
9
|
+
const app = await NestFactory.create(AppModule);
|
|
10
|
+
|
|
11
|
+
app.setGlobalPrefix('api');
|
|
12
|
+
app.useGlobalPipes(
|
|
13
|
+
new ValidationPipe({
|
|
14
|
+
whitelist: true,
|
|
15
|
+
transform: true,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
app.useGlobalFilters(app.get(AppExceptionFilter));
|
|
19
|
+
|
|
20
|
+
const configService = app.get(ConfigService);
|
|
21
|
+
const port = configService.get<number>('app.port') ?? 3000;
|
|
22
|
+
|
|
23
|
+
await app.listen(port);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
26
|
bootstrap();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Global, Module } from '@nestjs/common';
|
|
2
|
-
import { PrismaService } from './prisma.service';
|
|
3
|
-
|
|
4
|
-
@Global()
|
|
5
|
-
@Module({
|
|
6
|
-
providers: [PrismaService],
|
|
7
|
-
exports: [PrismaService],
|
|
8
|
-
})
|
|
1
|
+
import { Global, Module } from '@nestjs/common';
|
|
2
|
+
import { PrismaService } from './prisma.service';
|
|
3
|
+
|
|
4
|
+
@Global()
|
|
5
|
+
@Module({
|
|
6
|
+
providers: [PrismaService],
|
|
7
|
+
exports: [PrismaService],
|
|
8
|
+
})
|
|
9
9
|
export class PrismaModule {}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
2
|
-
import { PrismaClient } from '@prisma/client';
|
|
3
|
-
|
|
4
|
-
@Injectable()
|
|
5
|
-
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
|
6
|
-
constructor() {
|
|
7
|
-
const databaseUrl =
|
|
8
|
-
process.env.DATABASE_URL ??
|
|
9
|
-
'postgresql://postgres:postgres@localhost:5432/app?schema=public';
|
|
10
|
-
|
|
11
|
-
super({
|
|
12
|
-
datasources: {
|
|
13
|
-
db: {
|
|
14
|
-
url: databaseUrl,
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async onModuleInit(): Promise<void> {
|
|
21
|
-
await this.$connect();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async onModuleDestroy(): Promise<void> {
|
|
25
|
-
await this.$disconnect();
|
|
26
|
-
}
|
|
1
|
+
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { PrismaClient } from '@prisma/client';
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
|
6
|
+
constructor() {
|
|
7
|
+
const databaseUrl =
|
|
8
|
+
process.env.DATABASE_URL ??
|
|
9
|
+
'postgresql://postgres:postgres@localhost:5432/app?schema=public';
|
|
10
|
+
|
|
11
|
+
super({
|
|
12
|
+
datasources: {
|
|
13
|
+
db: {
|
|
14
|
+
url: databaseUrl,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async onModuleInit(): Promise<void> {
|
|
21
|
+
await this.$connect();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async onModuleDestroy(): Promise<void> {
|
|
25
|
+
await this.$disconnect();
|
|
26
|
+
}
|
|
27
27
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"outDir": "dist"
|
|
6
|
-
},
|
|
7
|
-
"include": ["src/**/*.ts"],
|
|
8
|
-
"exclude": ["**/*.spec.ts", "test", "dist", "node_modules"]
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "dist"
|
|
6
|
+
},
|
|
7
|
+
"include": ["src/**/*.ts"],
|
|
8
|
+
"exclude": ["**/*.spec.ts", "test", "dist", "node_modules"]
|
|
9
9
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"outDir": "dist"
|
|
6
|
-
},
|
|
7
|
-
"include": ["src/**/*.ts", "prisma/**/*.ts"],
|
|
8
|
-
"exclude": ["dist", "node_modules"]
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "dist"
|
|
6
|
+
},
|
|
7
|
+
"include": ["src/**/*.ts", "prisma/**/*.ts"],
|
|
8
|
+
"exclude": ["dist", "node_modules"]
|
|
9
9
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
FROM node:20-alpine
|
|
2
|
-
WORKDIR /app
|
|
3
|
-
RUN corepack enable
|
|
4
|
-
|
|
5
|
-
COPY package.json pnpm-workspace.yaml ./
|
|
6
|
-
COPY apps/web/package.json apps/web/package.json
|
|
7
|
-
RUN pnpm install --frozen-lockfile=false
|
|
8
|
-
|
|
9
|
-
COPY apps/web apps/web
|
|
10
|
-
WORKDIR /app/apps/web
|
|
11
|
-
RUN pnpm build
|
|
12
|
-
|
|
1
|
+
FROM node:20-alpine
|
|
2
|
+
WORKDIR /app
|
|
3
|
+
RUN corepack enable
|
|
4
|
+
|
|
5
|
+
COPY package.json pnpm-workspace.yaml ./
|
|
6
|
+
COPY apps/web/package.json apps/web/package.json
|
|
7
|
+
RUN pnpm install --frozen-lockfile=false
|
|
8
|
+
|
|
9
|
+
COPY apps/web apps/web
|
|
10
|
+
WORKDIR /app/apps/web
|
|
11
|
+
RUN pnpm build
|
|
12
|
+
|
|
13
13
|
CMD ["pnpm", "preview", "--host", "0.0.0.0", "--port", "5173"]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Forgeon Web</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id="root"></div>
|
|
10
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
-
</body>
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Forgeon Web</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
12
|
</html>
|
|
13
13
|
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@forgeon/web",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "vite",
|
|
8
|
-
"build": "tsc -b && vite build",
|
|
9
|
-
"preview": "vite preview"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"react": "^18.3.1",
|
|
13
|
-
"react-dom": "^18.3.1"
|
|
14
|
-
},
|
|
15
|
-
"devDependencies": {
|
|
16
|
-
"@types/react": "^18.3.3",
|
|
17
|
-
"@types/react-dom": "^18.3.0",
|
|
18
|
-
"@vitejs/plugin-react": "^4.3.1",
|
|
19
|
-
"typescript": "^5.7.3",
|
|
20
|
-
"vite": "^5.4.11"
|
|
21
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgeon/web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^18.3.1",
|
|
13
|
+
"react-dom": "^18.3.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^18.3.3",
|
|
17
|
+
"@types/react-dom": "^18.3.0",
|
|
18
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
19
|
+
"typescript": "^5.7.3",
|
|
20
|
+
"vite": "^5.4.11"
|
|
21
|
+
}
|
|
22
22
|
}
|
|
23
23
|
|