Merge gitea/main into github/main
Some checks failed
Deploy / Deploy to Production (push) Has been cancelled
Some checks failed
Deploy / Deploy to Production (push) Has been cancelled
Brings 9 Coolify deploy / Traefik routing / SPA fallback / CSP fix commits from Gitea origin/main into GitHub main, keeping both histories. Operational fixes preserved alongside the GitHub-side work (Sentry, translation pipeline, deploy script refinements). # Conflicts: # apps/api/src/app.module.ts # apps/api/src/common/filters/http-exception.filter.ts # apps/api/src/vehicles/vehicles.service.ts # pnpm-lock.yaml
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
"@nestjs/core": "^10.4.0",
|
||||
"@nestjs/platform-express": "^10.4.0",
|
||||
"@nestjs/schedule": "^4.1.0",
|
||||
"@nestjs/serve-static": "^5.0.4",
|
||||
"@nestjs/swagger": "^8.1.0",
|
||||
"@nestjs/throttler": "^6.3.0",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { resolve } from "node:path";
|
||||
import { join, resolve } from "node:path";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core";
|
||||
import { ServeStaticModule } from "@nestjs/serve-static";
|
||||
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
|
||||
import { SentryModule } from "@sentry/nestjs/setup";
|
||||
import { AdminModule } from "./admin/admin.module";
|
||||
@@ -47,6 +48,10 @@ import { VehiclesModule } from "./vehicles/vehicles.module";
|
||||
load: [configuration],
|
||||
validate,
|
||||
}),
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: join(__dirname, "..", "..", "web", "dist"),
|
||||
exclude: ["/api/(.*)"],
|
||||
}),
|
||||
ThrottlerModule.forRoot([
|
||||
{
|
||||
name: "default",
|
||||
|
||||
@@ -6,17 +6,20 @@ import {
|
||||
HttpStatus,
|
||||
Logger,
|
||||
} from "@nestjs/common";
|
||||
import { join } from "node:path";
|
||||
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
||||
import { SentryExceptionCaptured } from "@sentry/nestjs";
|
||||
import { Response } from "express";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
@Catch()
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
private readonly logger = new Logger("ExceptionFilter");
|
||||
private readonly indexPath = join(__dirname, "..", "..", "..", "..", "web", "dist", "index.html");
|
||||
|
||||
@SentryExceptionCaptured()
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const request = ctx.getRequest<Request>();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
@@ -39,6 +42,11 @@ export class HttpExceptionFilter implements ExceptionFilter {
|
||||
this.logger.error(`Unhandled error: ${exception.message}`, exception.stack);
|
||||
}
|
||||
|
||||
// SPA fallback: serve index.html for non-API GET 404s
|
||||
if (status === HttpStatus.NOT_FOUND && request.method === "GET" && !request.path.startsWith("/api")) {
|
||||
return response.sendFile(this.indexPath);
|
||||
}
|
||||
|
||||
// Record error on active OTel span
|
||||
const span = trace.getActiveSpan();
|
||||
if (span) {
|
||||
|
||||
@@ -18,7 +18,22 @@ async function bootstrap() {
|
||||
app.setGlobalPrefix("api");
|
||||
|
||||
// Security headers
|
||||
app.use(helmet());
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "https:", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", "data:", "https://storage.sase.tr"],
|
||||
fontSrc: ["'self'", "https:", "data:"],
|
||||
connectSrc: ["'self'", "https://storage.sase.tr"],
|
||||
objectSrc: ["'none'"],
|
||||
frameSrc: ["'none'"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
app.enableCors({
|
||||
origin: corsOrigins,
|
||||
|
||||
Reference in New Issue
Block a user