feat: prepare six-lesson trial backend with reliable rewards and operations
Backend CI / type-check, build, tests (push) Successful in 1m0s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-12 14:35:42 +03:30
co-authored by Cursor
parent 5e5a14030a
commit dfb20ca92c
46 changed files with 2589 additions and 123 deletions
+43 -5
View File
@@ -1,6 +1,15 @@
import { plainToInstance } from 'class-transformer'
import { IsIn, IsInt, IsOptional, IsString, Min, validateSync } from 'class-validator'
const DEV_JWT_FALLBACK = 'gold-land-dev-secret-change-in-production'
const FORBIDDEN_PRODUCTION_JWT_SECRETS = new Set([
DEV_JWT_FALLBACK,
'e2e-local-jwt-not-for-production',
'e2e-ci-jwt-not-for-production',
'ci-test-jwt-not-for-production',
])
export class EnvironmentVariables {
@IsIn(['development', 'production', 'test'])
NODE_ENV!: 'development' | 'production' | 'test'
@@ -27,14 +36,42 @@ export class EnvironmentVariables {
@IsOptional()
@IsString()
JWT_SECRET?: string
/** When `true`, Fastify trusts X-Forwarded-* (only enable behind a known reverse proxy). */
@IsOptional()
@IsString()
TRUST_PROXY?: string
}
function assertProductionJwtSecret(secret: string): void {
const trimmed = secret.trim()
if (trimmed.length < 32) {
throw new Error('JWT_SECRET must be at least 32 characters when NODE_ENV=production')
}
if (FORBIDDEN_PRODUCTION_JWT_SECRETS.has(trimmed)) {
throw new Error('JWT_SECRET is a known development placeholder — set a unique production secret')
}
}
export function validateEnv(config: Record<string, unknown>): EnvironmentVariables {
const nodeEnv = (config.NODE_ENV as string | undefined) ?? 'development'
const jwtSecret = config.JWT_SECRET as string | undefined
if (nodeEnv === 'production' && (!jwtSecret || jwtSecret.trim().length === 0)) {
throw new Error('JWT_SECRET is required when NODE_ENV=production')
const trustProxyEnabled =
config.TRUST_PROXY === 'true' ||
config.TRUST_PROXY === '1' ||
config.TRUST_PROXY === true
if (nodeEnv === 'production') {
if (!jwtSecret || jwtSecret.trim().length === 0) {
throw new Error('JWT_SECRET is required when NODE_ENV=production')
}
assertProductionJwtSecret(jwtSecret)
if (trustProxyEnabled && !(config.TRUST_PROXY_CIDRS as string | undefined)?.trim()) {
throw new Error(
'TRUST_PROXY_CIDRS is required when TRUST_PROXY is enabled in production (set the reverse-proxy network CIDR, not broad defaults)',
)
}
}
if (nodeEnv !== 'production' && !jwtSecret) {
@@ -45,11 +82,12 @@ export function validateEnv(config: Record<string, unknown>): EnvironmentVariabl
const normalized = {
NODE_ENV: nodeEnv,
PORT: Number(config.PORT ?? 3001),
PORT: Number(config.PORT ?? 3000),
HOST: (config.HOST as string | undefined) ?? '0.0.0.0',
CORS_ORIGIN: (config.CORS_ORIGIN as string | undefined) ?? 'http://localhost:5173',
CORS_ORIGIN: (config.CORS_ORIGIN as string | undefined) ?? 'http://localhost:5174',
DB_PATH: (config.DB_PATH ?? config.DATABASE_PATH) as string | undefined,
JWT_SECRET: jwtSecret ?? 'gold-land-dev-secret-change-in-production',
JWT_SECRET: jwtSecret ?? DEV_JWT_FALLBACK,
TRUST_PROXY: (config.TRUST_PROXY as string | undefined) ?? 'false',
}
const validated = plainToInstance(EnvironmentVariables, normalized, {