--- phase: 40-job-infrastructure plan: "02" subsystem: content-jobs tags: [http-routes, sse, async-jobs, live-events, integration-tests] dependency_graph: requires: - content_jobs DB table (40-01) - contentJobStore CRUD service (40-01) - contentJobRunner async dispatcher (40-01) - subscribeCompanyLiveEvents from live-events.ts - assertCompanyAccess from authz.ts provides: - POST /api/companies/:companyId/content-jobs (202 job submission) - GET /api/companies/:companyId/content-jobs (list) - GET /api/companies/:companyId/content-jobs/:jobId (single job) - GET /api/companies/:companyId/content-jobs/:jobId/events (SSE progress stream) affects: - server/src/routes/content-jobs.ts - server/src/app.ts - server/src/__tests__/content-jobs-routes.test.ts - server/src/__tests__/content-jobs-sse.test.ts tech_stack: added: [] patterns: - fire-and-forget dispatch via void contentJobRunner.dispatch() - SSE with res.flushHeaders() + res.write() + req.on("close") cleanup - EventEmitter subscription for job progress (no polling) - Supertest + vitest mocks for route integration testing key_files: created: - server/src/routes/content-jobs.ts - server/src/__tests__/content-jobs-routes.test.ts - server/src/__tests__/content-jobs-sse.test.ts modified: - server/src/app.ts decisions: - SSE streams use EventEmitter subscription not polling — no setTimeout/setInterval - req.on("close") cleanup prevents listener leaks when client disconnects mid-job - Terminal jobs (done/failed) end SSE stream immediately after initial status event - jobType validation rejects empty strings (whitespace-only) as well as missing field metrics: duration: "~3 minutes" completed: "2026-04-04" tasks_completed: 2 tasks_total: 2 files_created: 3 files_modified: 1 --- # Phase 40 Plan 02: HTTP routes for content job submission and SSE progress Summary **One-liner:** Four Express route endpoints (POST 202, GET list, GET by ID, GET SSE events) wired to contentJobStore/contentJobRunner/live-events with 13 integration tests covering INFRA-01 through INFRA-04. ## What Was Built HTTP API surface for content job submission and monitoring: 1. **POST /api/companies/:companyId/content-jobs** — validates jobType, creates job via contentJobStore, dispatches async runner (fire-and-forget), returns 202 `{ jobId, status, createdAt }`. 2. **GET /api/companies/:companyId/content-jobs** — lists all jobs for a company ordered by createdAt desc. 3. **GET /api/companies/:companyId/content-jobs/:jobId** — retrieves a single job, returns 404 if not found. 4. **GET /api/companies/:companyId/content-jobs/:jobId/events** — SSE endpoint: sets `text/event-stream` headers, flushes, sends initial status immediately, ends for terminal jobs, subscribes to `content_job.*` live events for running jobs, cleans up on `req.close`. 5. **app.ts mount** — `contentJobRoutes(db, opts.storageService)` added after voiceRoutes. ## Tasks Completed | Task | Name | Commit | Files | |------|------|--------|-------| | 1 | Content job routes and app.ts wiring | 4c6335b7 | content-jobs.ts (created), app.ts (modified) | | 2 | Integration tests for content job routes and SSE | ae28542b | content-jobs-routes.test.ts, content-jobs-sse.test.ts (both created) | ## Verification - `pnpm tsc --noEmit --project server/tsconfig.json` — PASS - `pnpm vitest run src/__tests__/content-jobs-routes.test.ts src/__tests__/content-jobs-sse.test.ts` — 13/13 tests green - POST /api/companies/:id/content-jobs returns 202 (INFRA-01) — verified - GET /api/companies/:id/content-jobs/:id/events returns SSE stream (INFRA-02) — verified - Job created with sourceTaskId shows it in GET response (INFRA-04) — verified ## Deviations from Plan None — plan executed exactly as written. ## Known Stubs None in this plan. The `renderContent` stub lives in `content-job-runner.ts` (documented in 40-01-SUMMARY.md) and is intentionally deferred to phases 41-45. ## Self-Check: PASSED Files verified to exist: - `/opt/nexus/server/src/routes/content-jobs.ts` — FOUND - `/opt/nexus/server/src/__tests__/content-jobs-routes.test.ts` — FOUND - `/opt/nexus/server/src/__tests__/content-jobs-sse.test.ts` — FOUND Commits verified: - `4c6335b7` — FOUND (feat(40-02): content job routes and app.ts wiring) - `ae28542b` — FOUND (test(40-02): integration tests for content job routes and SSE)