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:
16
.dockerignore
Normal file
16
.dockerignore
Normal file
@@ -0,0 +1,16 @@
|
||||
node_modules
|
||||
**/node_modules
|
||||
**/dist
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
docs
|
||||
scripts
|
||||
docker
|
||||
.env*
|
||||
.claude
|
||||
logs
|
||||
*.log
|
||||
.turbo
|
||||
coverage
|
||||
.vscode
|
||||
69
Dockerfile
Normal file
69
Dockerfile
Normal file
@@ -0,0 +1,69 @@
|
||||
# ── Stage 1: Base ──────────────────────────────────────
|
||||
FROM node:22-alpine AS base
|
||||
RUN corepack enable && corepack prepare pnpm@10.29.3 --activate
|
||||
WORKDIR /app
|
||||
|
||||
# ── Stage 2: Dependencies ─────────────────────────────
|
||||
FROM base AS deps
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY apps/api/package.json apps/api/package.json
|
||||
COPY apps/web/package.json apps/web/package.json
|
||||
COPY packages/shared/package.json packages/shared/package.json
|
||||
COPY packages/config/package.json packages/config/package.json
|
||||
COPY packages/ui/package.json packages/ui/package.json
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# ── Stage 3: Build ─────────────────────────────────────
|
||||
FROM base AS build
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules
|
||||
COPY --from=deps /app/apps/web/node_modules ./apps/web/node_modules
|
||||
COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules
|
||||
COPY --from=deps /app/packages/config/node_modules ./packages/config/node_modules
|
||||
COPY --from=deps /app/packages/ui/node_modules ./packages/ui/node_modules
|
||||
COPY . .
|
||||
|
||||
# Vite env vars (baked at build time)
|
||||
ARG VITE_FARO_ENABLED=false
|
||||
ARG VITE_FARO_COLLECTOR_URL=
|
||||
ARG VITE_POSTHOG_KEY=
|
||||
|
||||
RUN pnpm build
|
||||
|
||||
# ── Stage 4: Production ───────────────────────────────
|
||||
FROM node:22-alpine AS production
|
||||
|
||||
# Chromium for Playwright (EMEX/PartsCatalogs)
|
||||
RUN apk add --no-cache chromium nss freetype harfbuzz ca-certificates ttf-freefont
|
||||
ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built artifacts
|
||||
COPY --from=build /app/apps/api/dist ./apps/api/dist
|
||||
COPY --from=build /app/apps/web/dist ./apps/web/dist
|
||||
COPY --from=build /app/packages/shared/dist ./packages/shared/dist
|
||||
COPY --from=build /app/packages/config/dist ./packages/config/dist
|
||||
|
||||
# Copy package files for production install
|
||||
COPY --from=build /app/package.json ./
|
||||
COPY --from=build /app/pnpm-lock.yaml ./
|
||||
COPY --from=build /app/pnpm-workspace.yaml ./
|
||||
COPY --from=build /app/apps/api/package.json ./apps/api/
|
||||
COPY --from=build /app/apps/web/package.json ./apps/web/
|
||||
COPY --from=build /app/packages/shared/package.json ./packages/shared/
|
||||
COPY --from=build /app/packages/config/package.json ./packages/config/
|
||||
COPY --from=build /app/packages/ui/package.json ./packages/ui/
|
||||
|
||||
# NestJS CLI config (needed for serve-static)
|
||||
COPY --from=build /app/apps/api/nest-cli.json ./apps/api/
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.29.3 --activate \
|
||||
&& pnpm install --frozen-lockfile --prod \
|
||||
&& pnpm store prune
|
||||
|
||||
ENV NODE_ENV=production
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["node", "--enable-source-maps", "apps/api/dist/main.js"]
|
||||
@@ -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,
|
||||
|
||||
133
docker-compose.coolify.yml
Normal file
133
docker-compose.coolify.yml
Normal file
@@ -0,0 +1,133 @@
|
||||
services:
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- VITE_FARO_ENABLED=${VITE_FARO_ENABLED:-false}
|
||||
- VITE_FARO_COLLECTOR_URL=${VITE_FARO_COLLECTOR_URL:-}
|
||||
- VITE_POSTHOG_KEY=${VITE_POSTHOG_KEY:-}
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=4000
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- REDIS_HOST=sase-redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||
- BETTER_AUTH_URL=${BETTER_AUTH_URL}
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
|
||||
- MINIO_ENDPOINT=http://minio-global:9000
|
||||
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
|
||||
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
|
||||
- MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME:-sase-schemas}
|
||||
- MINIO_PUBLIC_URL=${MINIO_PUBLIC_URL}
|
||||
- MINIO_USE_SSL=false
|
||||
- CORS_ORIGIN=${CORS_ORIGIN}
|
||||
- IYZICO_API_KEY=${IYZICO_API_KEY:-}
|
||||
- IYZICO_SECRET_KEY=${IYZICO_SECRET_KEY:-}
|
||||
- IYZICO_BASE_URL=${IYZICO_BASE_URL:-}
|
||||
- PL24_BASE_URL=${PL24_BASE_URL:-https://www.partslink24.com}
|
||||
- PL24_COMPANY_CODE=${PL24_COMPANY_CODE:-}
|
||||
- PL24_USERNAME=${PL24_USERNAME:-}
|
||||
- PL24_PASSWORD=${PL24_PASSWORD:-}
|
||||
- PL24_COMPANY_CODE_2=${PL24_COMPANY_CODE_2:-}
|
||||
- PL24_USERNAME_2=${PL24_USERNAME_2:-}
|
||||
- PL24_PASSWORD_2=${PL24_PASSWORD_2:-}
|
||||
- PL24_PROXY_DE=${PL24_PROXY_DE:-}
|
||||
- EMEX_USERNAME=${EMEX_USERNAME:-}
|
||||
- EMEX_PASSWORD=${EMEX_PASSWORD:-}
|
||||
- PCAT_USE_PROXY=${PCAT_USE_PROXY:-true}
|
||||
- PCAT_PROXY_HOST=${PCAT_PROXY_HOST:-gw.dataimpulse.com}
|
||||
- PCAT_PROXY_USER=${PCAT_PROXY_USER:-}
|
||||
- PCAT_PROXY_PASS=${PCAT_PROXY_PASS:-}
|
||||
- POSTAL_API_URL=${POSTAL_API_URL:-}
|
||||
- POSTAL_API_KEY=${POSTAL_API_KEY:-}
|
||||
- POSTAL_FROM_ADDRESS=${POSTAL_FROM_ADDRESS:-noreply@sase.tr}
|
||||
- POSTAL_FROM_NAME=${POSTAL_FROM_NAME:-Sase.tr}
|
||||
- OTEL_ENABLED=${OTEL_ENABLED:-false}
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-}
|
||||
- OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS:-}
|
||||
- OTEL_SERVICE_NAME=${OTEL_SERVICE_NAME:-sase-api}
|
||||
- OTEL_TRACE_SAMPLE_RATE=${OTEL_TRACE_SAMPLE_RATE:-1.0}
|
||||
- ML_PREDICTION_ENABLED=${ML_PREDICTION_ENABLED:-false}
|
||||
depends_on:
|
||||
sase-redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4000/api/health"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
labels:
|
||||
- "traefik.docker.network=coolify"
|
||||
expose:
|
||||
- "4000"
|
||||
networks:
|
||||
- default
|
||||
- coolify
|
||||
|
||||
worker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
command: ["node", "--enable-source-maps", "apps/api/dist/worker.js"]
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- REDIS_HOST=sase-redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
- MINIO_ENDPOINT=http://minio-global:9000
|
||||
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
|
||||
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
|
||||
- MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME:-sase-schemas}
|
||||
- MINIO_PUBLIC_URL=${MINIO_PUBLIC_URL}
|
||||
- MINIO_USE_SSL=false
|
||||
- PL24_BASE_URL=${PL24_BASE_URL:-https://www.partslink24.com}
|
||||
- PL24_COMPANY_CODE=${PL24_COMPANY_CODE:-}
|
||||
- PL24_USERNAME=${PL24_USERNAME:-}
|
||||
- PL24_PASSWORD=${PL24_PASSWORD:-}
|
||||
- PL24_COMPANY_CODE_2=${PL24_COMPANY_CODE_2:-}
|
||||
- PL24_USERNAME_2=${PL24_USERNAME_2:-}
|
||||
- PL24_PASSWORD_2=${PL24_PASSWORD_2:-}
|
||||
- PL24_PROXY_DE=${PL24_PROXY_DE:-}
|
||||
- EMEX_USERNAME=${EMEX_USERNAME:-}
|
||||
- EMEX_PASSWORD=${EMEX_PASSWORD:-}
|
||||
- PCAT_USE_PROXY=${PCAT_USE_PROXY:-true}
|
||||
- PCAT_PROXY_HOST=${PCAT_PROXY_HOST:-gw.dataimpulse.com}
|
||||
- PCAT_PROXY_USER=${PCAT_PROXY_USER:-}
|
||||
- PCAT_PROXY_PASS=${PCAT_PROXY_PASS:-}
|
||||
- OTEL_ENABLED=${OTEL_ENABLED:-false}
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-}
|
||||
- OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS:-}
|
||||
- OTEL_SERVICE_NAME=${OTEL_SERVICE_NAME:-sase-worker}
|
||||
- OTEL_TRACE_SAMPLE_RATE=${OTEL_TRACE_SAMPLE_RATE:-1.0}
|
||||
depends_on:
|
||||
sase-redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- default
|
||||
- coolify
|
||||
|
||||
sase-redis:
|
||||
image: redis:7.4-alpine
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- default
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
|
||||
networks:
|
||||
coolify:
|
||||
external: true
|
||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -42,6 +42,9 @@ importers:
|
||||
'@nestjs/schedule':
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.2(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)
|
||||
'@nestjs/serve-static':
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.5(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)
|
||||
'@nestjs/swagger':
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.1(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.2.2)
|
||||
@@ -1629,6 +1632,22 @@ packages:
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.2'
|
||||
|
||||
'@nestjs/serve-static@5.0.5':
|
||||
resolution: {integrity: sha512-AhYx3N9aMwR2cb0w5Nlb5nHNYiAcF74ea/D/xna+PxlXwjmwGN/PpC/5fuMtOwmPBMgOTxNPOnB8C9LDZBSgyw==}
|
||||
peerDependencies:
|
||||
'@fastify/static': ^8.0.4 || ^9.0.0
|
||||
'@nestjs/common': ^11.0.2
|
||||
'@nestjs/core': ^11.0.2
|
||||
express: ^5.0.1
|
||||
fastify: ^5.2.1
|
||||
peerDependenciesMeta:
|
||||
'@fastify/static':
|
||||
optional: true
|
||||
express:
|
||||
optional: true
|
||||
fastify:
|
||||
optional: true
|
||||
|
||||
'@nestjs/swagger@8.1.1':
|
||||
resolution: {integrity: sha512-5Mda7H1DKnhKtlsb0C7PYshcvILv8UFyUotHzxmWh0G65Z21R3LZH/J8wmpnlzL4bmXIfr42YwbEwRxgzpJ5sQ==}
|
||||
peerDependencies:
|
||||
@@ -5023,6 +5042,9 @@ packages:
|
||||
path-to-regexp@3.3.0:
|
||||
resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==}
|
||||
|
||||
path-to-regexp@8.4.2:
|
||||
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
|
||||
|
||||
path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7364,6 +7386,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- chokidar
|
||||
|
||||
'@nestjs/serve-static@5.0.5(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)':
|
||||
dependencies:
|
||||
'@nestjs/common': 10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 10.4.22(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
path-to-regexp: 8.4.2
|
||||
|
||||
'@nestjs/swagger@8.1.1(@nestjs/common@10.4.22(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.2.2)':
|
||||
dependencies:
|
||||
'@microsoft/tsdoc': 0.15.1
|
||||
@@ -11055,6 +11083,8 @@ snapshots:
|
||||
|
||||
path-to-regexp@3.3.0: {}
|
||||
|
||||
path-to-regexp@8.4.2: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
Reference in New Issue
Block a user