@trinacria/cli 0.1.1-alpha.1
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.
Potentially problematic release.
This version of @trinacria/cli might be problematic. Click here for more details.
- package/README.md +109 -0
- package/README.npm.md +109 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +88 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.js +102 -0
- package/dist/commands/dev.d.ts +28 -0
- package/dist/commands/dev.js +186 -0
- package/dist/commands/new.d.ts +41 -0
- package/dist/commands/new.js +280 -0
- package/dist/commands/start.d.ts +13 -0
- package/dist/commands/start.js +110 -0
- package/dist/config/config.contract.d.ts +9 -0
- package/dist/config/config.contract.js +2 -0
- package/dist/config/default-config.d.ts +2 -0
- package/dist/config/default-config.js +11 -0
- package/dist/config/load-config.d.ts +2 -0
- package/dist/config/load-config.js +113 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/templates/api-events-rabbitmq/.env.example +23 -0
- package/dist/templates/api-events-rabbitmq/Dockerfile +18 -0
- package/dist/templates/api-events-rabbitmq/README.md +97 -0
- package/dist/templates/api-events-rabbitmq/docker-compose.yml +30 -0
- package/dist/templates/api-events-rabbitmq/package.json +21 -0
- package/dist/templates/api-events-rabbitmq/src/global/config.service.ts +95 -0
- package/dist/templates/api-events-rabbitmq/src/global/controllers/swagger/docs.html +28 -0
- package/dist/templates/api-events-rabbitmq/src/global/controllers/swagger/swagger-docs.controller.ts +79 -0
- package/dist/templates/api-events-rabbitmq/src/global/rabbitmq.service.ts +86 -0
- package/dist/templates/api-events-rabbitmq/src/global/register-global-controllers.ts +20 -0
- package/dist/templates/api-events-rabbitmq/src/main.ts +89 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/dto/event.dto.ts +24 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/dto/index.ts +2 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/dto/publish-event.dto.ts +12 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/events.controller.ts +76 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/events.module.ts +27 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/events.provider.ts +28 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/events.service.ts +19 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/events.store.ts +42 -0
- package/dist/templates/api-events-rabbitmq/src/modules/events/events.tokens.ts +11 -0
- package/dist/templates/api-events-rabbitmq/trinacria.config.mjs +12 -0
- package/dist/templates/api-events-rabbitmq/tsconfig.json +14 -0
- package/dist/templates/api-events-redis/.env.example +18 -0
- package/dist/templates/api-events-redis/Dockerfile +18 -0
- package/dist/templates/api-events-redis/README.md +97 -0
- package/dist/templates/api-events-redis/docker-compose.yml +33 -0
- package/dist/templates/api-events-redis/package.json +18 -0
- package/dist/templates/api-events-redis/src/global/config.service.ts +93 -0
- package/dist/templates/api-events-redis/src/global/controllers/swagger/docs.html +28 -0
- package/dist/templates/api-events-redis/src/global/controllers/swagger/swagger-docs.controller.ts +79 -0
- package/dist/templates/api-events-redis/src/global/redis.service.ts +50 -0
- package/dist/templates/api-events-redis/src/global/register-global-controllers.ts +20 -0
- package/dist/templates/api-events-redis/src/main.ts +88 -0
- package/dist/templates/api-events-redis/src/modules/events/dto/event.dto.ts +24 -0
- package/dist/templates/api-events-redis/src/modules/events/dto/index.ts +2 -0
- package/dist/templates/api-events-redis/src/modules/events/dto/publish-event.dto.ts +12 -0
- package/dist/templates/api-events-redis/src/modules/events/events.controller.ts +76 -0
- package/dist/templates/api-events-redis/src/modules/events/events.module.ts +27 -0
- package/dist/templates/api-events-redis/src/modules/events/events.provider.ts +28 -0
- package/dist/templates/api-events-redis/src/modules/events/events.service.ts +19 -0
- package/dist/templates/api-events-redis/src/modules/events/events.store.ts +42 -0
- package/dist/templates/api-events-redis/src/modules/events/events.tokens.ts +11 -0
- package/dist/templates/api-events-redis/trinacria.config.mjs +12 -0
- package/dist/templates/api-events-redis/tsconfig.json +14 -0
- package/dist/templates/api-mongoose-mongodb/.env.example +17 -0
- package/dist/templates/api-mongoose-mongodb/Dockerfile +18 -0
- package/dist/templates/api-mongoose-mongodb/README.md +98 -0
- package/dist/templates/api-mongoose-mongodb/docker-compose.yml +34 -0
- package/dist/templates/api-mongoose-mongodb/package.json +17 -0
- package/dist/templates/api-mongoose-mongodb/src/global/config.service.ts +92 -0
- package/dist/templates/api-mongoose-mongodb/src/global/controllers/swagger/docs.html +28 -0
- package/dist/templates/api-mongoose-mongodb/src/global/controllers/swagger/swagger-docs.controller.ts +79 -0
- package/dist/templates/api-mongoose-mongodb/src/global/mongoose-schema.provider.ts +18 -0
- package/dist/templates/api-mongoose-mongodb/src/global/mongoose.service.ts +36 -0
- package/dist/templates/api-mongoose-mongodb/src/global/register-global-controllers.ts +20 -0
- package/dist/templates/api-mongoose-mongodb/src/main.ts +70 -0
- package/dist/templates/api-mongoose-mongodb/src/modules/users/dto/create-user.dto.ts +14 -0
- package/dist/templates/api-mongoose-mongodb/src/modules/users/dto/index.ts +2 -0
- package/dist/templates/api-mongoose-mongodb/src/modules/users/dto/public-user.dto.ts +22 -0
- package/dist/templates/api-mongoose-mongodb/src/modules/users/users.controller.ts +89 -0
- package/dist/templates/api-mongoose-mongodb/src/modules/users/users.module.ts +17 -0
- package/dist/templates/api-mongoose-mongodb/src/modules/users/users.schema.ts +35 -0
- package/dist/templates/api-mongoose-mongodb/src/modules/users/users.service.ts +35 -0
- package/dist/templates/api-mongoose-mongodb/src/modules/users/users.tokens.ts +9 -0
- package/dist/templates/api-mongoose-mongodb/trinacria.config.mjs +12 -0
- package/dist/templates/api-mongoose-mongodb/tsconfig.json +14 -0
- package/dist/templates/api-prisma-postgresql/.env.example +19 -0
- package/dist/templates/api-prisma-postgresql/Dockerfile +24 -0
- package/dist/templates/api-prisma-postgresql/README.md +107 -0
- package/dist/templates/api-prisma-postgresql/docker-compose.yml +38 -0
- package/dist/templates/api-prisma-postgresql/package.json +26 -0
- package/dist/templates/api-prisma-postgresql/prisma/schema.prisma +15 -0
- package/dist/templates/api-prisma-postgresql/prisma.config.ts +9 -0
- package/dist/templates/api-prisma-postgresql/src/global/config.service.ts +92 -0
- package/dist/templates/api-prisma-postgresql/src/global/controllers/swagger/docs.html +28 -0
- package/dist/templates/api-prisma-postgresql/src/global/controllers/swagger/swagger-docs.controller.ts +79 -0
- package/dist/templates/api-prisma-postgresql/src/global/prisma.service.ts +20 -0
- package/dist/templates/api-prisma-postgresql/src/global/register-global-controllers.ts +20 -0
- package/dist/templates/api-prisma-postgresql/src/main.ts +70 -0
- package/dist/templates/api-prisma-postgresql/src/modules/users/dto/create-user.dto.ts +14 -0
- package/dist/templates/api-prisma-postgresql/src/modules/users/dto/index.ts +2 -0
- package/dist/templates/api-prisma-postgresql/src/modules/users/dto/public-user.dto.ts +22 -0
- package/dist/templates/api-prisma-postgresql/src/modules/users/users.controller.ts +89 -0
- package/dist/templates/api-prisma-postgresql/src/modules/users/users.module.ts +15 -0
- package/dist/templates/api-prisma-postgresql/src/modules/users/users.service.ts +36 -0
- package/dist/templates/api-prisma-postgresql/src/modules/users/users.tokens.ts +7 -0
- package/dist/templates/api-prisma-postgresql/trinacria.config.mjs +12 -0
- package/dist/templates/api-prisma-postgresql/tsconfig.json +14 -0
- package/dist/templates/app-starter/.env.example +1 -0
- package/dist/templates/app-starter/Dockerfile +13 -0
- package/dist/templates/app-starter/README.md +32 -0
- package/dist/templates/app-starter/docker-compose.yml +8 -0
- package/dist/templates/app-starter/package.json +14 -0
- package/dist/templates/app-starter/src/main.ts +14 -0
- package/dist/templates/app-starter/trinacria.config.mjs +12 -0
- package/dist/templates/app-starter/tsconfig.json +14 -0
- package/dist/templates/cron-example/.env.example +11 -0
- package/dist/templates/cron-example/Dockerfile +13 -0
- package/dist/templates/cron-example/README.md +68 -0
- package/dist/templates/cron-example/docker-compose.yml +11 -0
- package/dist/templates/cron-example/package.json +15 -0
- package/dist/templates/cron-example/src/config.service.ts +46 -0
- package/dist/templates/cron-example/src/main.ts +41 -0
- package/dist/templates/cron-example/src/modules/cron/cron.module.ts +15 -0
- package/dist/templates/cron-example/src/modules/cron/cron.tokens.ts +6 -0
- package/dist/templates/cron-example/src/modules/cron/example-cron-jobs.provider.ts +48 -0
- package/dist/templates/cron-example/trinacria.config.mjs +12 -0
- package/dist/templates/cron-example/tsconfig.json +14 -0
- package/package.json +32 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createToken } from "@trinacria/core";
|
|
4
|
+
import {
|
|
5
|
+
formatValidationError,
|
|
6
|
+
Infer,
|
|
7
|
+
s,
|
|
8
|
+
ValidationError,
|
|
9
|
+
} from "@trinacria/schema";
|
|
10
|
+
|
|
11
|
+
export const configSchema = s.object({
|
|
12
|
+
ENV: s.enum(["development", "staging", "production"]).default("development"),
|
|
13
|
+
PORT: s.number({ coerce: true, int: true, min: 1, max: 65535 }).default(4001),
|
|
14
|
+
HOST: s.string().default("127.0.0.1"),
|
|
15
|
+
DATABASE_URL: s.string(),
|
|
16
|
+
OPENAPI_ENABLED: s.boolean({ coerce: true }).default(true),
|
|
17
|
+
CORS_ALLOWED_ORIGINS: s.array(s.string(), { coerce: true }).default([]),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type ConfigModel = Infer<typeof configSchema>;
|
|
21
|
+
export const CONFIG_SERVICE = createToken<ConfigService>("CONFIG_SERVICE");
|
|
22
|
+
|
|
23
|
+
export class ConfigService {
|
|
24
|
+
private readonly config: ConfigModel;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this.loadEnvFile();
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
this.config = configSchema.parse(process.env);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof ValidationError) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
formatValidationError(error, {
|
|
35
|
+
prefix: "Invalid environment configuration:",
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private loadEnvFile() {
|
|
45
|
+
const currentEnv = process.env.ENV || "development";
|
|
46
|
+
const fileNames = [".env", `.env.${currentEnv}`];
|
|
47
|
+
const roots = [
|
|
48
|
+
process.cwd(),
|
|
49
|
+
path.join(process.cwd(), "apps/api-prisma-postgresql"),
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
fileNames.forEach((fileName) => {
|
|
53
|
+
roots.forEach((root) => {
|
|
54
|
+
const filePath = path.join(root, fileName);
|
|
55
|
+
if (!fs.existsSync(filePath)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const data = fs.readFileSync(filePath, "utf8");
|
|
60
|
+
data.split("\n").forEach((line) => {
|
|
61
|
+
const trimmed = line.trim();
|
|
62
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const separatorIndex = trimmed.indexOf("=");
|
|
67
|
+
if (separatorIndex === -1) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
72
|
+
const rawValue = trimmed.slice(separatorIndex + 1).trim();
|
|
73
|
+
const value =
|
|
74
|
+
(rawValue.startsWith('"') && rawValue.endsWith('"')) ||
|
|
75
|
+
(rawValue.startsWith("'") && rawValue.endsWith("'"))
|
|
76
|
+
? rawValue.slice(1, -1)
|
|
77
|
+
: rawValue;
|
|
78
|
+
|
|
79
|
+
process.env[key] = value;
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get<K extends keyof ConfigModel>(key: K): ConfigModel[K] {
|
|
86
|
+
return this.config[key];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getAll(): ConfigModel {
|
|
90
|
+
return this.config;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>API Prisma PostgreSQL Docs</title>
|
|
7
|
+
<link
|
|
8
|
+
rel="stylesheet"
|
|
9
|
+
href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"
|
|
10
|
+
/>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="swagger-ui"></div>
|
|
14
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
15
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"></script>
|
|
16
|
+
<script>
|
|
17
|
+
window.ui = SwaggerUIBundle({
|
|
18
|
+
url: "/openapi.json",
|
|
19
|
+
dom_id: "#swagger-ui",
|
|
20
|
+
deepLinking: true,
|
|
21
|
+
tryItOutEnabled: true,
|
|
22
|
+
persistAuthorization: true,
|
|
23
|
+
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
|
24
|
+
layout: "StandaloneLayout",
|
|
25
|
+
});
|
|
26
|
+
</script>
|
|
27
|
+
</body>
|
|
28
|
+
</html>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createToken } from "@trinacria/core";
|
|
2
|
+
import { HttpController, response } from "@trinacria/http";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import type { ConfigService } from "../../config.service";
|
|
6
|
+
|
|
7
|
+
export const SWAGGER_DOCS_CONTROLLER = createToken<SwaggerDocsController>(
|
|
8
|
+
"SWAGGER_DOCS_CONTROLLER",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export class SwaggerDocsController extends HttpController {
|
|
12
|
+
constructor(private readonly config: ConfigService) {
|
|
13
|
+
super();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
routes() {
|
|
17
|
+
return this.router()
|
|
18
|
+
.get("/docs", this.renderDocs, {
|
|
19
|
+
docs: {
|
|
20
|
+
excludeFromOpenApi: true,
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
.build();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async renderDocs() {
|
|
27
|
+
if (!this.config.get("OPENAPI_ENABLED")) {
|
|
28
|
+
return response({ message: "OpenAPI is disabled" }, { status: 404 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return response(await readSwaggerHtml(), {
|
|
32
|
+
headers: {
|
|
33
|
+
"content-type": "text/html; charset=utf-8",
|
|
34
|
+
"content-security-policy": docsContentSecurityPolicy(),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let cachedSwaggerHtml: string | undefined;
|
|
41
|
+
|
|
42
|
+
async function readSwaggerHtml(): Promise<string> {
|
|
43
|
+
if (cachedSwaggerHtml) {
|
|
44
|
+
return cachedSwaggerHtml;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const candidates = [
|
|
48
|
+
path.resolve(process.cwd(), "src/global/controllers/swagger/docs.html"),
|
|
49
|
+
path.resolve(
|
|
50
|
+
process.cwd(),
|
|
51
|
+
"apps/api-prisma-postgresql/src/global/controllers/swagger/docs.html",
|
|
52
|
+
),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
for (const filePath of candidates) {
|
|
56
|
+
try {
|
|
57
|
+
cachedSwaggerHtml = await fs.readFile(filePath, "utf8");
|
|
58
|
+
return cachedSwaggerHtml;
|
|
59
|
+
} catch {
|
|
60
|
+
// keep trying next candidate
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new Error("Swagger docs HTML template not found.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function docsContentSecurityPolicy(): string {
|
|
68
|
+
return [
|
|
69
|
+
"default-src 'self'",
|
|
70
|
+
"base-uri 'self'",
|
|
71
|
+
"object-src 'none'",
|
|
72
|
+
"frame-ancestors 'none'",
|
|
73
|
+
"script-src 'self' 'unsafe-inline' https://unpkg.com",
|
|
74
|
+
"style-src 'self' 'unsafe-inline' https://unpkg.com",
|
|
75
|
+
"img-src 'self' data: blob:",
|
|
76
|
+
"font-src 'self' data: https://unpkg.com",
|
|
77
|
+
"connect-src 'self'",
|
|
78
|
+
].join("; ");
|
|
79
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
2
|
+
import { PrismaClient } from "@prisma/client";
|
|
3
|
+
import { createToken } from "@trinacria/core";
|
|
4
|
+
import type { ConfigService } from "./config.service";
|
|
5
|
+
|
|
6
|
+
export const PRISMA_SERVICE = createToken<PrismaService>("PRISMA_SERVICE");
|
|
7
|
+
|
|
8
|
+
export class PrismaService extends PrismaClient {
|
|
9
|
+
constructor(private readonly configService: ConfigService) {
|
|
10
|
+
super({
|
|
11
|
+
adapter: new PrismaPg({
|
|
12
|
+
connectionString: configService.get("DATABASE_URL"),
|
|
13
|
+
}),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async onDestroy(): Promise<void> {
|
|
18
|
+
await this.$disconnect();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TrinacriaApp } from "@trinacria/core";
|
|
2
|
+
import { httpProvider } from "@trinacria/http";
|
|
3
|
+
import { ConfigService, CONFIG_SERVICE } from "./config.service";
|
|
4
|
+
import {
|
|
5
|
+
SWAGGER_DOCS_CONTROLLER,
|
|
6
|
+
SwaggerDocsController,
|
|
7
|
+
} from "./controllers/swagger/swagger-docs.controller";
|
|
8
|
+
|
|
9
|
+
export function registerGlobalControllers(
|
|
10
|
+
app: TrinacriaApp,
|
|
11
|
+
config: ConfigService,
|
|
12
|
+
): void {
|
|
13
|
+
if (config.get("OPENAPI_ENABLED")) {
|
|
14
|
+
app.registerGlobalProvider(
|
|
15
|
+
httpProvider(SWAGGER_DOCS_CONTROLLER, SwaggerDocsController, [
|
|
16
|
+
CONFIG_SERVICE,
|
|
17
|
+
]),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { TrinacriaApp, classProvider, valueProvider } from "@trinacria/core";
|
|
2
|
+
import {
|
|
3
|
+
cors,
|
|
4
|
+
createHttpPlugin,
|
|
5
|
+
createSecurityHeadersBuilder,
|
|
6
|
+
rateLimit,
|
|
7
|
+
requestId,
|
|
8
|
+
requestLogger,
|
|
9
|
+
requestTimeout,
|
|
10
|
+
} from "@trinacria/http";
|
|
11
|
+
import { CONFIG_SERVICE, ConfigService } from "./global/config.service";
|
|
12
|
+
import { PrismaService, PRISMA_SERVICE } from "./global/prisma.service";
|
|
13
|
+
import { registerGlobalControllers } from "./global/register-global-controllers";
|
|
14
|
+
import { UsersModule } from "./modules/users/users.module";
|
|
15
|
+
|
|
16
|
+
async function bootstrap() {
|
|
17
|
+
const app = new TrinacriaApp();
|
|
18
|
+
const configService = new ConfigService();
|
|
19
|
+
const config = configService.getAll();
|
|
20
|
+
const isProduction = config.ENV === "production";
|
|
21
|
+
const corsOrigins = config.CORS_ALLOWED_ORIGINS;
|
|
22
|
+
const securityHeadersMiddleware = createSecurityHeadersBuilder()
|
|
23
|
+
.preset(config.ENV)
|
|
24
|
+
.trustProxy(false)
|
|
25
|
+
.build();
|
|
26
|
+
|
|
27
|
+
app.registerGlobalProvider(valueProvider(CONFIG_SERVICE, configService));
|
|
28
|
+
app.registerGlobalProvider(
|
|
29
|
+
classProvider(PRISMA_SERVICE, PrismaService, [CONFIG_SERVICE]),
|
|
30
|
+
);
|
|
31
|
+
registerGlobalControllers(app, configService);
|
|
32
|
+
|
|
33
|
+
app.use(
|
|
34
|
+
createHttpPlugin({
|
|
35
|
+
host: config.HOST,
|
|
36
|
+
port: config.PORT,
|
|
37
|
+
middlewares: [
|
|
38
|
+
requestId(),
|
|
39
|
+
requestLogger({ includeUserAgent: !isProduction }),
|
|
40
|
+
cors({
|
|
41
|
+
origin: corsOrigins.length > 0 ? corsOrigins : "*",
|
|
42
|
+
credentials: true,
|
|
43
|
+
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
|
|
44
|
+
}),
|
|
45
|
+
rateLimit({
|
|
46
|
+
windowMs: 60_000,
|
|
47
|
+
max: isProduction ? 240 : 2_000,
|
|
48
|
+
trustProxy: false,
|
|
49
|
+
}),
|
|
50
|
+
requestTimeout({ timeoutMs: 15_000 }),
|
|
51
|
+
securityHeadersMiddleware,
|
|
52
|
+
],
|
|
53
|
+
openApi: config.OPENAPI_ENABLED
|
|
54
|
+
? {
|
|
55
|
+
enabled: true,
|
|
56
|
+
title: "API Prisma PostgreSQL Example",
|
|
57
|
+
version: "1.0.0",
|
|
58
|
+
}
|
|
59
|
+
: undefined,
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
await app.registerModule(UsersModule);
|
|
64
|
+
await app.start();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
bootstrap().catch((error) => {
|
|
68
|
+
console.error(error);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { s } from "@trinacria/schema";
|
|
2
|
+
|
|
3
|
+
export interface CreateUserDto {
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const CreateUserDtoSchema = s.objectOf<CreateUserDto>()(
|
|
9
|
+
{
|
|
10
|
+
name: s.string({ trim: true, minLength: 1 }),
|
|
11
|
+
email: s.string({ trim: true, toLowerCase: true, email: true }),
|
|
12
|
+
},
|
|
13
|
+
{ strict: true },
|
|
14
|
+
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { s } from "@trinacria/schema";
|
|
2
|
+
|
|
3
|
+
export interface PublicUserDto {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
email: string;
|
|
7
|
+
createdAt: Date;
|
|
8
|
+
updatedAt: Date;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const PublicUserDtoSchema = s.objectOf<PublicUserDto>()(
|
|
12
|
+
{
|
|
13
|
+
id: s.string({ trim: true, minLength: 1 }),
|
|
14
|
+
name: s.string({ trim: true, minLength: 1 }),
|
|
15
|
+
email: s.string({ trim: true, toLowerCase: true, email: true }),
|
|
16
|
+
createdAt: s.date(),
|
|
17
|
+
updatedAt: s.date(),
|
|
18
|
+
},
|
|
19
|
+
{ strict: true },
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const PublicUserListDtoSchema = s.array(PublicUserDtoSchema);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConflictException,
|
|
3
|
+
HttpController,
|
|
4
|
+
HttpContext,
|
|
5
|
+
response,
|
|
6
|
+
} from "@trinacria/http";
|
|
7
|
+
import { UsersService } from "./users.service";
|
|
8
|
+
import {
|
|
9
|
+
CreateUserDtoSchema,
|
|
10
|
+
PublicUserDtoSchema,
|
|
11
|
+
PublicUserListDtoSchema,
|
|
12
|
+
} from "./dto";
|
|
13
|
+
|
|
14
|
+
export class UsersController extends HttpController {
|
|
15
|
+
constructor(private readonly users: UsersService) {
|
|
16
|
+
super();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
routes() {
|
|
20
|
+
return this.router()
|
|
21
|
+
.get("/health", () => ({ status: "ok" }), {
|
|
22
|
+
docs: {
|
|
23
|
+
tags: ["Health"],
|
|
24
|
+
summary: "Health check",
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
.get("/users", this.listUsers, {
|
|
28
|
+
docs: {
|
|
29
|
+
tags: ["Users"],
|
|
30
|
+
summary: "List users",
|
|
31
|
+
responses: {
|
|
32
|
+
200: {
|
|
33
|
+
description: "Users list",
|
|
34
|
+
schema: PublicUserListDtoSchema.toOpenApi(),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
.post("/users", this.createUser, {
|
|
40
|
+
docs: {
|
|
41
|
+
tags: ["Users"],
|
|
42
|
+
summary: "Create user",
|
|
43
|
+
requestBody: {
|
|
44
|
+
required: true,
|
|
45
|
+
schema: CreateUserDtoSchema.toOpenApi(),
|
|
46
|
+
},
|
|
47
|
+
responses: {
|
|
48
|
+
201: {
|
|
49
|
+
description: "Created user",
|
|
50
|
+
schema: PublicUserDtoSchema.toOpenApi(),
|
|
51
|
+
},
|
|
52
|
+
409: {
|
|
53
|
+
description: "Email already exists",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
.build();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async listUsers() {
|
|
62
|
+
return this.users.list();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async createUser(ctx: HttpContext) {
|
|
66
|
+
const payload = CreateUserDtoSchema.parse(ctx.body);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const created = await this.users.create(payload);
|
|
70
|
+
return response(created, {
|
|
71
|
+
status: 201,
|
|
72
|
+
headers: {
|
|
73
|
+
location: `/users/${created.id}`,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (
|
|
78
|
+
error &&
|
|
79
|
+
typeof error === "object" &&
|
|
80
|
+
"code" in error &&
|
|
81
|
+
error.code === "P2002"
|
|
82
|
+
) {
|
|
83
|
+
throw new ConflictException("Email already exists");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { classProvider, defineModule } from "@trinacria/core";
|
|
2
|
+
import { httpProvider } from "@trinacria/http";
|
|
3
|
+
import { PRISMA_SERVICE } from "../../global/prisma.service";
|
|
4
|
+
import { USERS_CONTROLLER, USERS_SERVICE } from "./users.tokens";
|
|
5
|
+
import { UsersController } from "./users.controller";
|
|
6
|
+
import { UsersService } from "./users.service";
|
|
7
|
+
|
|
8
|
+
export const UsersModule = defineModule({
|
|
9
|
+
name: "UsersModule",
|
|
10
|
+
providers: [
|
|
11
|
+
classProvider(USERS_SERVICE, UsersService, [PRISMA_SERVICE]),
|
|
12
|
+
httpProvider(USERS_CONTROLLER, UsersController, [USERS_SERVICE]),
|
|
13
|
+
],
|
|
14
|
+
exports: [USERS_SERVICE, USERS_CONTROLLER],
|
|
15
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Prisma } from "@prisma/client";
|
|
2
|
+
import { PrismaService } from "../../global/prisma.service";
|
|
3
|
+
import type { CreateUserDto } from "./dto";
|
|
4
|
+
|
|
5
|
+
const userSelect = {
|
|
6
|
+
id: true,
|
|
7
|
+
name: true,
|
|
8
|
+
email: true,
|
|
9
|
+
createdAt: true,
|
|
10
|
+
updatedAt: true,
|
|
11
|
+
} satisfies Prisma.UserSelect;
|
|
12
|
+
|
|
13
|
+
type UserRecord = Prisma.UserGetPayload<{
|
|
14
|
+
select: typeof userSelect;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
export class UsersService {
|
|
18
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
19
|
+
|
|
20
|
+
async list(): Promise<UserRecord[]> {
|
|
21
|
+
return this.prisma.user.findMany({
|
|
22
|
+
select: userSelect,
|
|
23
|
+
orderBy: { createdAt: "desc" },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async create(input: CreateUserDto): Promise<UserRecord> {
|
|
28
|
+
return this.prisma.user.create({
|
|
29
|
+
select: userSelect,
|
|
30
|
+
data: {
|
|
31
|
+
name: input.name,
|
|
32
|
+
email: input.email.toLowerCase(),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createToken } from "@trinacria/core";
|
|
2
|
+
import { UsersController } from "./users.controller";
|
|
3
|
+
import { UsersService } from "./users.service";
|
|
4
|
+
|
|
5
|
+
export const USERS_SERVICE = createToken<UsersService>("USERS_SERVICE");
|
|
6
|
+
export const USERS_CONTROLLER =
|
|
7
|
+
createToken<UsersController>("USERS_CONTROLLER");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @type {import('@trinacria/cli').TrinacriaConfig} */
|
|
2
|
+
|
|
3
|
+
const config = {
|
|
4
|
+
entry: "src/main.ts",
|
|
5
|
+
outDir: "dist",
|
|
6
|
+
watchDir: "src",
|
|
7
|
+
env: "development",
|
|
8
|
+
crashLoopWindowMs: 15_000,
|
|
9
|
+
maxConsecutiveCrashRestarts: 3,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default config;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"types": ["node"],
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "CommonJS",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"outDir": "dist"
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# No environment variables required for this app.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
FROM node:24-bookworm-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
COPY package.json package-lock.json tsconfig.base.json ./
|
|
6
|
+
COPY packages ./packages
|
|
7
|
+
COPY apps/app-starter ./apps/app-starter
|
|
8
|
+
|
|
9
|
+
RUN npm ci
|
|
10
|
+
RUN npm run build:packages
|
|
11
|
+
RUN npm run build -w app-starter
|
|
12
|
+
|
|
13
|
+
CMD ["npm", "run", "start", "-w", "app-starter"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# App Starter
|
|
2
|
+
|
|
3
|
+
A minimal Trinacria app with only `TrinacriaApp` bootstrap and a `Hello World` log.
|
|
4
|
+
|
|
5
|
+
This template does not include:
|
|
6
|
+
|
|
7
|
+
- plugins
|
|
8
|
+
- modules
|
|
9
|
+
- database
|
|
10
|
+
- HTTP API
|
|
11
|
+
|
|
12
|
+
## Start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm run dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Docker quick start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
docker compose \
|
|
22
|
+
-f docker-compose.yml \
|
|
23
|
+
up --build -d
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To stop:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
docker compose \
|
|
30
|
+
-f docker-compose.yml \
|
|
31
|
+
down
|
|
32
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "app-starter",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "node ../../packages/cli/dist/index.js start",
|
|
7
|
+
"build": "node ../../packages/cli/dist/index.js build",
|
|
8
|
+
"dev": "node ../../packages/cli/dist/index.js dev"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@trinacria/cli": "*",
|
|
12
|
+
"@trinacria/core": "*"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ConsoleLogger, TrinacriaApp } from "@trinacria/core";
|
|
2
|
+
|
|
3
|
+
async function bootstrap() {
|
|
4
|
+
const app = new TrinacriaApp();
|
|
5
|
+
const logger = new ConsoleLogger("app-starter");
|
|
6
|
+
|
|
7
|
+
logger.info("Hello World");
|
|
8
|
+
await app.start();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
bootstrap().catch((error) => {
|
|
12
|
+
console.error(error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @type {import('@trinacria/cli').TrinacriaConfig} */
|
|
2
|
+
|
|
3
|
+
const config = {
|
|
4
|
+
entry: "src/main.ts",
|
|
5
|
+
outDir: "dist",
|
|
6
|
+
watchDir: "src",
|
|
7
|
+
env: "development",
|
|
8
|
+
crashLoopWindowMs: 15_000,
|
|
9
|
+
maxConsecutiveCrashRestarts: 3,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default config;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"types": ["node"],
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "CommonJS",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"outDir": "dist"
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Cron example environment template
|
|
2
|
+
# Copy to .env.development (or .env) and tweak values.
|
|
3
|
+
|
|
4
|
+
CRON_ENABLED=true
|
|
5
|
+
CRON_TICK_MS=1000
|
|
6
|
+
HEARTBEAT_INTERVAL_MS=10000
|
|
7
|
+
|
|
8
|
+
# Docker overrides used by docker-compose.yml
|
|
9
|
+
DOCKER_CRON_ENABLED=true
|
|
10
|
+
DOCKER_CRON_TICK_MS=1000
|
|
11
|
+
DOCKER_HEARTBEAT_INTERVAL_MS=10000
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
FROM node:24-bookworm-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
COPY package.json package-lock.json tsconfig.base.json ./
|
|
6
|
+
COPY packages ./packages
|
|
7
|
+
COPY apps/cron-example ./apps/cron-example
|
|
8
|
+
|
|
9
|
+
RUN npm ci
|
|
10
|
+
RUN npm run build:packages
|
|
11
|
+
RUN npm run build -w cron-example
|
|
12
|
+
|
|
13
|
+
CMD ["npm", "run", "start", "-w", "cron-example"]
|