Export serializer + GET /api/export (tar.gz assembly, media copy) #316

Closed
opened 2026-06-28 00:17:38 +00:00 by james · 0 comments
Owner

Child of #286 (export path — first slice).

Serialize all of a user's domain data into a portable carol-export-<date>.tar.gz: YAML docs at the archive root plus a media/ dir for blobs.

Scope

  • apps/api/lib/export/serialize.tsbuildExportData(db, userId): gather every domain entity for the user via the repositories, group into the archive's YAML docs, nest children under their parents, omit user_id from every emitted row, compute per-entity counts. Pure data (no YAML/tar).
  • apps/api/lib/export/archive.tsbuildExportArchive(data): YAML-serialize each doc (yaml), pack with tar-stream, gzip (zlib). Manifest carries formatVersion: 1, exportedAt, appVersion (from apps/api/package.json), and per-entity counts. Copies media from blob storage (profile picture today).
  • GET /api/export — authed, returns application/gzip with Content-Disposition: attachment; filename="carol-export-<YYYY-MM-DD>.tar.gz". Auth required (not in the public-routes allowlist).
  • OpenAPI registration under a new data tag; regenerate the committed openapi.json; openapi:check + openapi:coverage pass.
  • ADR documenting the archive format (this PR also lands the ADR — see ticket 5).
  • Both-engine serializer test + an API-level route test (gunzip + untar, verify manifest counts, YAML contents, media bytes, 401 unauth).

Notes

  • #286's file list says projects.yaml holds projects + contributions, but contributions are children of positions (contributions.position_id → positions → jobs); they nest under experience.yaml. projects.yaml holds projects only.

Archive shape, serialization rules, and the file list per the approved plan.

Child of #286 (export path — first slice). Serialize all of a user's domain data into a portable `carol-export-<date>.tar.gz`: YAML docs at the archive root plus a `media/` dir for blobs. ## Scope - `apps/api/lib/export/serialize.ts` — `buildExportData(db, userId)`: gather every domain entity for the user via the repositories, group into the archive's YAML docs, nest children under their parents, omit `user_id` from every emitted row, compute per-entity counts. Pure data (no YAML/tar). - `apps/api/lib/export/archive.ts` — `buildExportArchive(data)`: YAML-serialize each doc (`yaml`), pack with `tar-stream`, gzip (`zlib`). Manifest carries `formatVersion: 1`, `exportedAt`, `appVersion` (from `apps/api/package.json`), and per-entity counts. Copies media from blob storage (profile picture today). - `GET /api/export` — authed, returns `application/gzip` with `Content-Disposition: attachment; filename="carol-export-<YYYY-MM-DD>.tar.gz"`. Auth required (not in the public-routes allowlist). - OpenAPI registration under a new `data` tag; regenerate the committed `openapi.json`; `openapi:check` + `openapi:coverage` pass. - ADR documenting the archive format (this PR also lands the ADR — see ticket 5). - Both-engine serializer test + an API-level route test (gunzip + untar, verify manifest counts, YAML contents, media bytes, 401 unauth). ## Notes - #286's file list says `projects.yaml` holds `projects + contributions`, but `contributions` are children of `positions` (`contributions.position_id → positions → jobs`); they nest under `experience.yaml`. `projects.yaml` holds projects only. Archive shape, serialization rules, and the file list per the approved plan.
james closed this issue 2026-06-28 19:01:48 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#316
No description provided.