import app from "./app"; import { logger } from "./lib/logger"; import { db } from "@workspace/db"; import { conversionsTable, filesTable } from "@workspace/db"; import { eq, inArray, and, or } from "drizzle-orm"; const rawPort = process.env["PORT"]; if (!rawPort) { throw new Error( "PORT environment variable is required but was not provided.", ); } const port = Number(rawPort); if (Number.isNaN(port) || port <= 0) { throw new Error(`Invalid PORT value: "${rawPort}"`); } async function cleanupStuckJobs() { try { const stuckStatuses = ["queued", "analyzing", "routing", "ocr", "layout", "scoring", "merging", "cleanup"] as const; const stuckConversions = await db.query.conversionsTable.findMany({ where: inArray(conversionsTable.status, stuckStatuses as any), }); if (stuckConversions.length === 0) return; logger.info({ count: stuckConversions.length }, "Marking stuck conversions as failed on startup"); for (const conv of stuckConversions) { await db.update(conversionsTable) .set({ status: "failed", errorMessage: "انقطعت المعالجة بسبب إعادة تشغيل الخادم — يرجى المحاولة مجدداً", completedAt: new Date(), }) .where(eq(conversionsTable.id, conv.id)); if (conv.fileId) { await db.update(filesTable) .set({ status: "failed", updatedAt: new Date() }) .where(and( eq(filesTable.id, conv.fileId), inArray(filesTable.status, ["queued", "processing"] as any), )); } } logger.info({ count: stuckConversions.length }, "Startup cleanup complete"); } catch (err) { logger.error({ err }, "Startup cleanup failed — continuing anyway"); } } app.listen(port, async (err) => { if (err) { logger.error({ err }, "Error listening on port"); process.exit(1); } logger.info({ port }, "Server listening"); await cleanupStuckJobs(); });