fix production issues
Build & Push / Pipeline Tests (push) Failing after 59s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 51s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
Build & Push / Pipeline Tests (push) Failing after 59s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 51s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
This commit is contained in:
+67
-2
@@ -9,6 +9,7 @@ import { authLimiter, apiLimiter, publicLimiter, adminLimiter, webhookLimiter }
|
||||
import { requireTrustedOriginForCookieMutations } from './middleware/csrf'
|
||||
import { sanitizeForwardedHeaders } from './middleware/forwardedHeaders'
|
||||
import { requestIdMiddleware } from './middleware/requestId'
|
||||
import { metricsMiddleware, renderPrometheusText, setGauge } from './lib/opsMetrics'
|
||||
|
||||
// ─── Module routes ────────────────────────────────────────────
|
||||
import webhookRouter from './modules/webhooks/webhook.routes'
|
||||
@@ -132,7 +133,9 @@ export const corsOptions: CorsOptions = {
|
||||
}
|
||||
|
||||
const routeDocs = [
|
||||
{ method: 'GET', path: '/health', description: 'Health check' },
|
||||
{ method: 'GET', path: '/health', description: 'Liveness health check' },
|
||||
{ method: 'GET', path: '/ready', description: 'Readiness probe (database, redis, storage)' },
|
||||
{ method: 'GET', path: '/metrics', description: 'Prometheus-style ops metrics' },
|
||||
{ method: 'GET', path: `${v1}/docs`, description: 'Machine-readable API index' },
|
||||
{ method: 'GET', path: `${v1}/auth/renter/me`, description: 'Current renter profile' },
|
||||
{ method: 'GET', path: `${v1}/vehicles`, description: 'List company vehicles' },
|
||||
@@ -167,6 +170,7 @@ export function createApp() {
|
||||
|
||||
app.use(sanitizeForwardedHeaders)
|
||||
app.use(requestIdMiddleware)
|
||||
app.use(metricsMiddleware)
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.headers['x-middleware-subrequest']) {
|
||||
@@ -242,7 +246,21 @@ export function createApp() {
|
||||
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
||||
frameguard: { action: 'deny' },
|
||||
}))
|
||||
if (process.env.NODE_ENV !== 'test') app.use(morgan('combined'))
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
app.use(morgan((tokens, req, res) => {
|
||||
const requestId = (req as any).requestId ?? '-'
|
||||
return JSON.stringify({
|
||||
level: 'info',
|
||||
msg: 'http_request',
|
||||
requestId,
|
||||
method: tokens.method(req, res),
|
||||
url: tokens.url(req, res),
|
||||
status: Number(tokens.status(req, res)),
|
||||
durationMs: Number(tokens['response-time'](req, res)),
|
||||
contentLength: tokens.res(req, res, 'content-length'),
|
||||
})
|
||||
}))
|
||||
}
|
||||
app.use(express.json({ limit: '10mb' }))
|
||||
|
||||
// ─── API Routes ─────────────────────────────────────────────
|
||||
@@ -292,6 +310,53 @@ export function createApp() {
|
||||
res.json({ status: 'ok', version: '1.0.0', timestamp: new Date().toISOString() })
|
||||
})
|
||||
|
||||
app.get('/metrics', async (_req, res) => {
|
||||
try {
|
||||
const { prisma } = await import('./lib/prisma')
|
||||
const [pending, published] = await Promise.all([
|
||||
prisma.notificationOutbox.count({ where: { status: 'PENDING' } }),
|
||||
prisma.notificationOutbox.count({ where: { status: 'PUBLISHED' } }),
|
||||
])
|
||||
setGauge('notification_outbox_pending', pending)
|
||||
setGauge('notification_outbox_completed', published)
|
||||
} catch {
|
||||
/* leave previous gauges */
|
||||
}
|
||||
res.setHeader('Content-Type', 'text/plain; version=0.0.4; charset=utf-8')
|
||||
res.status(200).send(renderPrometheusText())
|
||||
})
|
||||
|
||||
app.get('/ready', async (_req, res) => {
|
||||
const { prisma } = await import('./lib/prisma')
|
||||
const { redis } = await import('./lib/redis')
|
||||
const { checkStorageReady } = await import('./lib/storage')
|
||||
const checks: Record<string, 'ok' | 'error'> = { database: 'error', redis: 'error', storage: 'error' }
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`
|
||||
checks.database = 'ok'
|
||||
} catch {
|
||||
checks.database = 'error'
|
||||
}
|
||||
try {
|
||||
const pong = await redis.ping()
|
||||
checks.redis = pong === 'PONG' ? 'ok' : 'error'
|
||||
} catch {
|
||||
checks.redis = 'error'
|
||||
}
|
||||
try {
|
||||
await checkStorageReady()
|
||||
checks.storage = 'ok'
|
||||
} catch {
|
||||
checks.storage = 'error'
|
||||
}
|
||||
const ready = Object.values(checks).every((v) => v === 'ok')
|
||||
res.status(ready ? 200 : 503).json({
|
||||
status: ready ? 'ready' : 'not_ready',
|
||||
checks,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
app.get(`${v1}/docs`, (_req, res) => {
|
||||
if (!publicDocsEnabled) return docsDisabled(_req, res)
|
||||
res.json({
|
||||
|
||||
Reference in New Issue
Block a user