Import apply — three modes, transactional, both engines, user_id re-scoping #318

Open
opened 2026-06-28 00:17:50 +00:00 by james · 0 comments
Owner

Child of #286 (import apply — third slice).

Apply a previewed import archive in a single transaction, all-or-nothing.

Scope

  • POST /api/import (multipart: file + mode + confirm) → applies in one transaction, returns a result summary. Re-sends the same file (no server-side staging to expire).
  • Three modes:
    • Replace — delete the importer's domain rows, then restore from the archive (UUIDs preserved). Destructive → requires explicit confirm.
    • Merge by UUID — upsert: matching UUIDs updated, the rest inserted; children reconciled under their parent.
    • Import as copies — regenerate every UUID (consistently remapping FKs) and always insert.
  • Never trust the file's user_id — every imported row is re-scoped to the authenticated importer (prevents cross-user injection).
  • Transactional via a real db.transaction() through the DB abstraction; must pass on SQLite and Postgres. Per the ADR (ticket 5), the both-engine test for this path runs against a temp file-backed SQLite DB (not sqlite::memory:), superseding the libsql in-memory transaction caveat at apps/api/db/repositories/skill-sections.ts.
  • Media: write referenced media/* blobs to the importer's storage paths.
  • Round-trip export→import both-engine test.

Depends on the parser/validator from ticket 2.

Child of #286 (import apply — third slice). Apply a previewed import archive in a single transaction, all-or-nothing. ## Scope - `POST /api/import` (multipart: file + `mode` + confirm) → applies in one transaction, returns a result summary. Re-sends the same file (no server-side staging to expire). - Three modes: - **Replace** — delete the importer's domain rows, then restore from the archive (UUIDs preserved). Destructive → requires explicit confirm. - **Merge by UUID** — upsert: matching UUIDs updated, the rest inserted; children reconciled under their parent. - **Import as copies** — regenerate every UUID (consistently remapping FKs) and always insert. - **Never trust the file's `user_id`** — every imported row is re-scoped to the authenticated importer (prevents cross-user injection). - **Transactional via a real `db.transaction()`** through the DB abstraction; must pass on **SQLite and Postgres**. Per the ADR (ticket 5), the both-engine test for this path runs against a temp **file-backed** SQLite DB (not `sqlite::memory:`), superseding the libsql in-memory transaction caveat at `apps/api/db/repositories/skill-sections.ts`. - Media: write referenced `media/*` blobs to the importer's storage paths. - Round-trip export→import both-engine test. Depends on the parser/validator from ticket 2.
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#318
No description provided.