Skip to content

feat: source upload API contract with atomic persistence — closes #7, #8 - #22

Merged
mikim merged 1 commit into
mainfrom
feat/upload-api
Jul 14, 2026
Merged

feat: source upload API contract with atomic persistence — closes #7, #8#22
mikim merged 1 commit into
mainfrom
feat/upload-api

Conversation

@mikim

@mikim mikim commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

Implements the upload API contract (#7) and StudySource/StudyUnit persistence (#8).

Closes #7
Closes #8

Contract (zod-validated, text-only in M1)

Route Behavior
POST /api/sources {userId, title, sourceType: text|markdown, originalFilename?, rawText} → Korean-aware ingestion → atomic persistIngestionResult (real FKs) → 201 {sourceId, unitIds, unitCount}
GET /api/sources?userId= Newest-first list with unit counts
GET /api/sources/:id Source + units in document order with citation offsets

Error mapping: zod/ingestion validation → 400 (+issues) · unknown user (FK P2003) → 404 · missing source → 404 · DB not configured → 503.

No auth yetuserId in the body is an explicit pre-public blocker, now documented in the README status table.

Wiring

  • buildApp({prisma}) injection; server.ts creates the client only when DATABASE_URL is set, disconnects on graceful shutdown.
  • /readyz now verifies real database connectivity (SELECT 1) when configured; 503 when unreachable.
  • API smoke test upgraded: with DATABASE_URL (CI: after migrate+seed) it performs a full upload + fetch round-trip against the booted artifact — the deployment gate now covers the product path, not just health checks.

Verified

  • +3 DB-less unit tests (503 paths, readyz) — run everywhere.
  • +7 integration tests against real Postgres in CI — including the citation invariant surviving HTTP + DB round-trips (rawText.slice(start, end) === content on API responses).
  • Local: 63 tests (11 api + integration skipped w/o DB), lint/typecheck/build/smoke green.

🤖 Generated with Claude Code

Implements the upload API contract (#7) and StudySource/StudyUnit
persistence through the API (#8).

Contract (zod-validated, text-only in M1):
- POST /api/sources: {userId, title, sourceType: text|markdown,
  originalFilename?, rawText} -> ingestion -> persistIngestionResult
  (one transaction, real foreign keys) -> 201 {sourceId, unitIds}
- GET /api/sources?userId=: newest-first list with unit counts
- GET /api/sources/:id: source + units in document order, including
  citation offsets (integration test proves the citation invariant
  survives HTTP + database round-trips)

Error mapping: zod/ingestion validation -> 400 with issues; unknown
user (FK P2003) -> 404; missing source -> 404; database not
configured -> 503. No auth yet: userId in the body is an explicit
pre-public blocker, documented in README.

Wiring:
- buildApp({prisma}) injection; server.ts creates the client only when
  DATABASE_URL is set and disconnects it on graceful shutdown
- /readyz now verifies real database connectivity (SELECT 1) when a
  database is configured; 503 when unreachable
- API smoke test now performs a full upload + fetch round-trip against
  the booted artifact when DATABASE_URL is present (CI: after
  migrate + seed)

Tests: +3 database-less unit tests (503 paths, readyz), +7 integration
tests against real Postgres in CI (create/list/get/citations/404s/400).

Closes #7
Closes #8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikim
mikim merged commit c972437 into main Jul 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persist StudySource and StudyUnit records Create source upload API contract

1 participant