Download apps/api/src/server.ts from Lazywords/Rl-Auto: direct link, hf CLI and curl.
- Browser
- Download file 4.86 kB
-
https://huggingface.co/spaces/Lazywords/Rl-Auto/resolve/main/apps/api/src/server.ts
- Command line
-
hf download hf://spaces/Lazywords/Rl-Auto/apps/api/src/server.ts
-
curl -L -o server.ts https://huggingface.co/spaces/Lazywords/Rl-Auto/resolve/main/apps/api/src/server.ts
4.86 kB
| // apps/api/src/server.ts | |
| // Fastify server entry. Hosts: | |
| // - /api/health | |
| // - /api/analysis/* (REST endpoints in routes/analysis.ts) | |
| // - / (static legacy SPA from apps/web-legacy/, with SPA fallback) | |
| import Fastify, { type FastifyInstance } from "fastify"; | |
| import fastifyStatic from "@fastify/static"; | |
| import fastifyMultipart from "@fastify/multipart"; | |
| import { fileURLToPath } from "node:url"; | |
| import { dirname, resolve } from "node:path"; | |
| import { existsSync, mkdirSync } from "node:fs"; | |
| import { loadConfig } from "./config.js"; | |
| import { createLogger } from "./log.js"; | |
| import { createStore } from "./queue/store-factory.js"; | |
| import { registerHealthRoutes } from "./routes/health.js"; | |
| import { registerAnalysisRoutes } from "./routes/analysis.js"; | |
| import { registerPolishRoutes } from "./routes/polish.js"; | |
| import { QueueRunner } from "./queue/runner.js"; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = dirname(__filename); | |
| // Inside the container the layout is: | |
| // /app/apps/api/dist/server.js β this file | |
| // /app/apps/web-legacy/dist/index.html | |
| // In dev (tsx) it's the source path with the same parent layout. | |
| function resolveWebLegacyRoot(): string { | |
| const webRoot = resolve(__dirname, "../../web-legacy/dist"); | |
| if (!existsSync(resolve(webRoot, "index.html"))) { | |
| throw new Error("εη«―ιζδΊ§η©δΈεε¨οΌθ―·ε θΏθ‘ pnpm --filter @task-optimizer/web-legacy build ζ pnpm build"); | |
| } | |
| return webRoot; | |
| } | |
| async function buildServer(): Promise<{ app: FastifyInstance; runner: QueueRunner }> { | |
| const config = loadConfig(); | |
| const logger = createLogger(config.logLevel); | |
| // Ensure storage directories exist (volume-mounted in Docker) | |
| for (const dir of [config.uploadDir, config.exportDir]) { | |
| try { | |
| mkdirSync(dir, { recursive: true }); | |
| } catch (err) { | |
| logger.error({ err, dir }, "Failed to create storage directory"); | |
| throw err; | |
| } | |
| } | |
| const store = await createStore(config); | |
| const runner = new QueueRunner({ store, config, logger }); | |
| // Recover any jobs left in `running` state across restarts (mark queued items | |
| // as queued is a no-op; the runner picks them back up on demand). | |
| for (const job of await store.listResumableJobs()) { | |
| logger.info({ jobId: job.id }, "Recovering resumable job from previous run"); | |
| await store.recoverInterruptedJob(job.id); | |
| runner.scheduleJob(job.id); | |
| } | |
| const app = Fastify({ | |
| // Fastify 5 requires `loggerInstance` (not `logger`) when passing an | |
| // already-constructed pino instance. | |
| loggerInstance: logger, | |
| bodyLimit: config.maxUploadMB * 1024 * 1024, | |
| disableRequestLogging: false, | |
| trustProxy: true, | |
| }); | |
| await app.register(fastifyMultipart, { | |
| limits: { | |
| fileSize: config.maxUploadMB * 1024 * 1024, | |
| files: config.maxBatchFiles, | |
| }, | |
| }); | |
| // ββ /api routes βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| await registerHealthRoutes(app); | |
| await registerPolishRoutes(app, { config }); | |
| await registerAnalysisRoutes(app, { store, config, runner }); | |
| app.addHook("onClose", async () => { | |
| await store.close(); | |
| }); | |
| // ββ static SPA ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const webRoot = resolveWebLegacyRoot(); | |
| await app.register(fastifyStatic, { | |
| root: webRoot, | |
| prefix: "/", | |
| decorateReply: false, | |
| }); | |
| // SPA fallback β anything that isn't /api/* and isn't a real file falls back | |
| // to index.html so the legacy frontend can handle its own routes. | |
| app.setNotFoundHandler((req, reply) => { | |
| if (req.raw.url && req.raw.url.startsWith("/api/")) { | |
| reply.code(404).send({ code: 404, msg: "Not Found" }); | |
| return; | |
| } | |
| reply.sendFile("index.html", webRoot); | |
| }); | |
| return { app, runner }; | |
| } | |
| async function main() { | |
| const { app, runner } = await buildServer(); | |
| const config = loadConfig(); | |
| const shutdown = async (signal: string) => { | |
| app.log.info({ signal }, "Shutdown signal received, closing server"); | |
| runner.requestShutdown(); | |
| try { | |
| await app.close(); | |
| } catch (err) { | |
| app.log.error({ err }, "Error closing Fastify"); | |
| } | |
| process.exit(0); | |
| }; | |
| process.once("SIGINT", () => void shutdown("SIGINT")); | |
| process.once("SIGTERM", () => void shutdown("SIGTERM")); | |
| try { | |
| await app.listen({ port: config.port, host: "0.0.0.0" }); | |
| app.log.info({ port: config.port }, "Server listening"); | |
| } catch (err) { | |
| app.log.error({ err }, "Server failed to start"); | |
| process.exit(1); | |
| } | |
| } | |
| main().catch((err) => { | |
| console.error("Fatal startup error:", err); | |
| process.exit(1); | |
| }); | |