Refactor API and worker applications to use NestJS framework. Update package versions and scripts for improved functionality. Enhance TypeScript configurations and add new dependencies for better development support. Remove deprecated main.ts file from worker application.

This commit is contained in:
smueller
2026-06-26 10:51:27 +02:00
parent 8d659317b9
commit fdf8392d42
23 changed files with 4383 additions and 81 deletions

View File

@@ -0,0 +1 @@
{"files":{"packages/ui/.turbo/turbo-typecheck.log":{"size":110,"mtime_nanos":1782463882776752800,"mode":420,"is_dir":false}},"order":["packages/ui/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"1bb9376dadaf6981","duration":3041,"sha":"8d659317b9d990b14324262928aa78e4e15fa4b6","dirty_hash":"fa9510cea9388d4bc05912f5175c0d4e7a04f11461edc6bd345dc3b51e6b0716"}

BIN
.turbo/cache/1bb9376dadaf6981.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/config/.turbo/turbo-typecheck.log":{"size":118,"mtime_nanos":1782463883269330100,"mode":420,"is_dir":false}},"order":["packages/config/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"6abc0126d916703e","duration":3531,"sha":"8d659317b9d990b14324262928aa78e4e15fa4b6","dirty_hash":"fa9510cea9388d4bc05912f5175c0d4e7a04f11461edc6bd345dc3b51e6b0716"}

BIN
.turbo/cache/6abc0126d916703e.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/contracts/.turbo/turbo-typecheck.log":{"size":124,"mtime_nanos":1782463881488108400,"mode":420,"is_dir":false}},"order":["packages/contracts/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"80b11947ece0af90","duration":1747,"sha":"8d659317b9d990b14324262928aa78e4e15fa4b6","dirty_hash":"fa9510cea9388d4bc05912f5175c0d4e7a04f11461edc6bd345dc3b51e6b0716"}

BIN
.turbo/cache/80b11947ece0af90.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"files":{"packages/auth/.turbo/turbo-typecheck.log":{"size":114,"mtime_nanos":1782463881442659700,"mode":420,"is_dir":false}},"order":["packages/auth/.turbo/turbo-typecheck.log"]}

View File

@@ -0,0 +1 @@
{"hash":"eccb56f09230a2d5","duration":1701,"sha":"8d659317b9d990b14324262928aa78e4e15fa4b6","dirty_hash":"fa9510cea9388d4bc05912f5175c0d4e7a04f11461edc6bd345dc3b51e6b0716"}

BIN
.turbo/cache/eccb56f09230a2d5.tar.zst vendored Normal file

Binary file not shown.

View File

@@ -1,17 +1,39 @@
{ {
"name": "@hexahost/api", "name": "@hexahost/api",
"version": "0.1.0", "version": "0.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tsc", "build": "nest build",
"dev": "nest start --watch",
"start": "node dist/main.js",
"start:prod": "node dist/main.js",
"lint": "node -e \"process.exit(0)\"", "lint": "node -e \"process.exit(0)\"",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "node --test test/**/*.test.js", "test": "node --test test/**/*.test.js"
"start": "node dist/main.js", },
"dev": "tsc && node dist/main.js" "dependencies": {
"@fastify/static": "^8.0.4",
"@hexahost/config": "workspace:*",
"@hexahost/contracts": "workspace:*",
"@hexahost/database": "workspace:*",
"@nestjs/common": "^11.0.7",
"@nestjs/core": "^11.0.7",
"@nestjs/platform-fastify": "^11.0.7",
"@nestjs/swagger": "^11.0.3",
"@nestjs/throttler": "^6.4.0",
"fastify": "^5.2.1",
"nestjs-pino": "^4.3.0",
"pino": "^9.6.0",
"pino-http": "^10.4.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.2", "@hexahost/typescript-config": "workspace:*",
"typescript": "^5.7.2" "@nestjs/cli": "^11.0.2",
"@nestjs/schematics": "^11.0.0",
"@types/node": "^22.10.7",
"pino-pretty": "^13.0.0",
"typescript": "^5.7.3"
} }
} }

View File

@@ -39,7 +39,8 @@ export class Rfc7807ExceptionFilter implements ExceptionFilter {
instance: string, instance: string,
): ProblemDetails { ): ProblemDetails {
if (exception instanceof HttpException) { if (exception instanceof HttpException) {
const response = exception.getResponse(); const httpException = exception;
const response = httpException.getResponse();
if (typeof response === 'object' && response !== null) { if (typeof response === 'object' && response !== null) {
const body = response as Record<string, unknown>; const body = response as Record<string, unknown>;
@@ -55,7 +56,7 @@ export class Rfc7807ExceptionFilter implements ExceptionFilter {
if (typeof body['message'] === 'string') { if (typeof body['message'] === 'string') {
return createProblemDetails({ return createProblemDetails({
title: exception.name, title: httpException.name,
status, status,
detail: body['message'], detail: body['message'],
instance, instance,
@@ -68,7 +69,7 @@ export class Rfc7807ExceptionFilter implements ExceptionFilter {
status, status,
detail: 'Request validation failed', detail: 'Request validation failed',
instance, instance,
errors: body['message'].map((message) => ({ errors: body['message'].map((message: unknown) => ({
message: String(message), message: String(message),
})), })),
}); });
@@ -76,9 +77,9 @@ export class Rfc7807ExceptionFilter implements ExceptionFilter {
} }
return createProblemDetails({ return createProblemDetails({
title: exception.name, title: httpException.name,
status, status,
detail: exception.message, detail: httpException.message,
instance, instance,
}); });
} }

View File

@@ -1,26 +1,48 @@
import http from "node:http"; import { randomUUID } from 'node:crypto';
const port = Number(process.env.API_PORT ?? 3001); import { NestFactory } from '@nestjs/core';
const appName = process.env.APP_NAME ?? "HexaHost GameCloud"; import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { Logger } from 'nestjs-pino';
const server = http.createServer((req, res) => { import { validateConfig } from '@hexahost/config';
if (req.url === "/healthz" || req.url === "/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
status: "ok",
service: "api",
app: appName,
phase: "0",
}),
);
return;
}
res.writeHead(404, { "Content-Type": "application/json" }); import { AppModule } from './app.module';
res.end(JSON.stringify({ status: "not_found" })); import { Rfc7807ExceptionFilter } from './common/filters/rfc7807-exception.filter';
});
server.listen(port, () => { async function bootstrap(): Promise<void> {
console.log(JSON.stringify({ level: "info", msg: "api listening", port })); const config = validateConfig();
});
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({
genReqId: () => randomUUID(),
requestIdHeader: 'x-request-id',
requestIdLogLabel: 'requestId',
}),
{
bufferLogs: true,
},
);
app.useLogger(app.get(Logger));
app.setGlobalPrefix('api/v1');
app.useGlobalFilters(new Rfc7807ExceptionFilter());
const swaggerConfig = new DocumentBuilder()
.setTitle(config.APP_NAME)
.setDescription('HexaHost GameCloud REST API')
.setVersion('0.0.0')
.addServer(config.API_URL)
.build();
const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('api/v1/docs', app, document);
await app.listen(config.API_PORT, '0.0.0.0');
}
void bootstrap();

View File

@@ -1,13 +1,10 @@
{ {
"extends": "@hexahost/typescript-config/nestjs.json",
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "outDir": "./dist",
"module": "NodeNext", "rootDir": "./src",
"moduleResolution": "NodeNext", "baseUrl": "./"
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}, },
"include": ["src/**/*"] "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test"]
} }

View File

@@ -1,17 +1,27 @@
{ {
"name": "@hexahost/worker", "name": "@hexahost/worker",
"version": "0.1.0", "version": "0.0.0",
"private": true, "private": true,
"main": "./dist/index.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js",
"lint": "node -e \"process.exit(0)\"", "lint": "node -e \"process.exit(0)\"",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "node --test test/**/*.test.js", "test": "node --test test/**/*.test.js"
"start": "node dist/main.js", },
"dev": "tsc && node dist/main.js" "dependencies": {
"@hexahost/config": "workspace:*",
"@hexahost/database": "workspace:*",
"bullmq": "^5.34.10",
"ioredis": "^5.4.2",
"pino": "^9.6.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.2", "@hexahost/typescript-config": "workspace:*",
"typescript": "^5.7.2" "@types/node": "^22.10.7",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
} }
} }

View File

@@ -1,4 +1,4 @@
import { Worker, type ConnectionOptions } from 'bullmq'; import { Job, Worker, type ConnectionOptions } from 'bullmq';
import IORedis from 'ioredis'; import IORedis from 'ioredis';
import { validateConfig } from '@hexahost/config'; import { validateConfig } from '@hexahost/config';
@@ -20,7 +20,7 @@ function createQueueWorker(
): Worker { ): Worker {
return new Worker( return new Worker(
queueName, queueName,
async (job) => { async (job: Job) => {
logger.info( logger.info(
{ queue: queueName, jobId: job.id, jobName: job.name }, { queue: queueName, jobId: job.id, jobName: job.name },
'Processing job', 'Processing job',
@@ -42,11 +42,11 @@ async function bootstrap(): Promise<void> {
); );
for (const worker of workers) { for (const worker of workers) {
worker.on('completed', (job) => { worker.on('completed', (job: Job | undefined) => {
logger.info({ queue: worker.name, jobId: job.id }, 'Job completed'); logger.info({ queue: worker.name, jobId: job?.id }, 'Job completed');
}); });
worker.on('failed', (job, error) => { worker.on('failed', (job: Job | undefined, error: Error) => {
logger.error( logger.error(
{ queue: worker.name, jobId: job?.id, err: error }, { queue: worker.name, jobId: job?.id, err: error },
'Job failed', 'Job failed',

View File

@@ -1,14 +0,0 @@
const redisUrl = process.env.REDIS_URL ?? "redis://localhost:6379";
console.log(
JSON.stringify({
level: "info",
msg: "worker started (phase 0 stub)",
redis: redisUrl.replace(/:[^:@]+@/, ":***@"),
}),
);
// Phase 0: worker process stays alive for compose; BullMQ queues land in Phase 1+
setInterval(() => {
console.log(JSON.stringify({ level: "debug", msg: "worker heartbeat" }));
}, 60_000);

View File

@@ -1,13 +1,9 @@
{ {
"extends": "@hexahost/typescript-config/library.json",
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "outDir": "./dist",
"module": "NodeNext", "rootDir": "./src"
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}, },
"include": ["src/**/*"] "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "src/main.ts"]
} }

View File

@@ -11,7 +11,10 @@
"build": "turbo run build", "build": "turbo run build",
"lint": "turbo run lint", "lint": "turbo run lint",
"typecheck": "turbo run typecheck", "typecheck": "turbo run typecheck",
"test": "turbo run test" "test": "turbo run test",
"db:generate": "pnpm --filter @hexahost/database db:generate",
"db:migrate": "pnpm --filter @hexahost/database db:migrate",
"db:push": "pnpm --filter @hexahost/database db:push"
}, },
"devDependencies": { "devDependencies": {
"turbo": "^2.3.3", "turbo": "^2.3.3",

File diff suppressed because one or more lines are too long

4261
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff