---
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)
