**DB clarification**: The plan (§5) describes the admin backend as writing to SQLite at `/var/www/quinn.admin/data/quinn.db`. This is inaccurate as of current code. `admin/backend-api/src/db.ts` imports `postgres` and reads `QUINN_ADMIN_DB_URL` — it already runs against postgres. Column 3 below therefore answers "does an equivalent postgres-backed entity already exist in quinn-api's schema?" rather than "does SQLite need a postgres mate?"
---
## Summary counts
| Metric | Value |
|---|---|
| Route files audited | 27 |
| Distinct resources | 24 |
| Fully covered in quinn-api (`/admin/*`) | 13 |
| Partial (some sub-routes missing) | 3 |
| Retire without migrating (proxy passthrough) | 1 |
| PUT | `/api/gallery/reorder` | `gallery_items.sort_order` |
| POST | `/api/gallery/:id/protect` | delegates to image-protection svc (:3030), polls async |
| POST | `/api/gallery/:id/adversary-view` | delegates to image-protection svc (:3030) |
| GET | `/api/gallery/adversary-view/jobs/:jobId` | — (proxy) |
**Quinn-api equivalent**: `src/surfaces/admin/gallery-items.ts` — GET/POST/PUT/:id/DELETE/:id/POST /reorder. The `protect` and `adversary-view` proxy endpoints have no quinn-api equivalent.
**Postgres schema**: `gallery_items` entity exists in quinn-api (`src/entities/gallery-item/`).
**Port collision hazard**: `gallery.ts` hardcodes `IMAGE_PROTECTION_URL` default as `http://localhost:3030` — the same port quinn-api itself binds. This must be resolved before migration: add an explicit non-default port for image-protection or force the env var in the unit file.
**Purge paths on mutation**: `/provider-api/gallery`, `/photos/<filename>.jpg`, `/photos/<filename>.webp`
**Quinn-api equivalent**: `src/surfaces/admin/rate-cards.ts` — full CRUD for sections + entries.
**Status**: EXISTS (admin uses `rate_sections`/`rate_entries`; quinn-api uses `rate_cards`/`rate_card_entries` — verify table names match or confirm the entity is the same object before retiring).
**Quinn-api equivalent**: `src/surfaces/admin/about.ts` — GET + PUT / + activity CRUD.
**Status**: EXISTS (confirm field parity before retiring admin copy — admin manages about content through `profile.ts`/`site-text.ts`; may not need a separate about route).
| POST | `/api/mail-admin/accounts` | docker-mailserver API |
| DELETE | `/api/mail-admin/accounts/:email` | docker-mailserver API |
**Quinn-api equivalent**: `src/surfaces/admin/mail-admin.ts` — full CRUD (mailserver proxy).
**Status**: EXISTS.
**Purge paths**: none (not public content)
---
### 15. System Status
| Method | Path | Writes |
|---|---|---|
| GET | `/api/system/status` | — |
**Quinn-api equivalent**: `src/surfaces/admin/system-status.ts` — GET /.
**Status**: EXISTS (read-only — migrate first in the sequence).
**Purge paths**: none
---
### 16. Bookings (proxy passthrough)
| Method | Path | Writes |
|---|---|---|
| POST | `/api/bookings` | proxies to quinn-api `/public/bookings` |
**Quinn-api equivalent**: this IS quinn-api — the admin backend is a passthrough to the public bookings endpoint.
**Status**: RETIRE without migration. Point the admin frontend directly at quinn-api `/public/bookings`.
**Purge paths**: none
---
### 17. Touring Subscribers — PARTIAL
| Method | Path | Writes |
|---|---|---|
| POST | `/api/touring/subscribe` | `touring_subscriptions` (unauthenticated) |
| GET | `/api/touring/subscribers` | — |
**Quinn-api equivalent**: `src/surfaces/admin/touring-subscribers.ts` — GET / only.
**Status**: PARTIAL — read list exists. The unauthenticated `subscribe` POST belongs in the quinn-api public surface; check `/public/touring` before migrating.
**Purge paths**: none
---
### 18. MIGRATE — Cult of Lilith
| Method | Path | Writes |
|---|---|---|
| GET | `/api/cult-of-lilith` | — |
| PUT | `/api/cult-of-lilith/:key` | `cult_of_lilith_sections` |
| PUT | `/api/cult-of-lilith/batch` | batch upsert |
**Quinn-api equivalent**: none. `lore_sections` entity exists in quinn-api; `cult_of_lilith` may be a distinct table — verify on `QUINN_ADMIN_DB_URL` before migration.
**Status**: MIGRATE.
**Purge paths**: `/www/cult-of-lilith`
---
### 19. MIGRATE — Page Illustrations
| Method | Path | Writes |
|---|---|---|
| GET | `/api/page-illustrations` | — |
| PUT | `/api/page-illustrations` | illustration config (file-backed or in DB — unclear) |
**Quinn-api equivalent**: none. Backing store needs investigation before migration.
**Status**: MIGRATE.
**Purge paths**: `/www/*` (illustrations appear across pages)
---
### 20. MIGRATE — Mail Threads (email client)
| Method | Path | Writes |
|---|---|---|
| GET | `/api/mail/inboxes` | — |
| GET/GET/:uid | `/api/mail/threads` | — |
| POST | `/api/mail/threads/:uid/reply` | sends email via IMAP/SMTP |
**Quinn-api equivalent**: `src/surfaces/admin/pseo-destinations.ts` covers pSEO metro destinations (slug-based, different shape). The admin `destinations` table (id/sort_order-based) may be a separate concept. Verify schema before conflating.
**Status**: MIGRATE or RETIRE. These sync the admin DB to a remote. With both services on postgres, the push/pull mechanism may be fully obsolete. Audit live callers before deciding.
**Status**: MIGRATE. Belongs in quinn-api's SSO/auth surface.
**Purge paths**: none
---
## Recommended migration order
1.**System status** — GET-only, zero writes; safe first to verify routing plumbing.
2.**Tour stops** — full parity in quinn-api. Wire `purgeEdge(['/www/tour', '/provider-api/tour-stops'])` into the handler and retire admin copy.
3.**Site text** — full parity. Wire purge for affected namespaces.
4.**Rates, Policies, Etiquette, Specialties, Roster content, Verified profiles, Hero strip** — all have quinn-api equivalents; retire admin copies in a batch. Wire purge per resource.
5.**Provider profile / About** — exists in quinn-api; confirm field parity first.
6.**Shop listings** (base CRUD only — skip photo sub-routes for now) — quinn-api surface exists.
7.**Cult of Lilith** — new quinn-api surface needed under `/admin/cult-of-lilith`; straightforward CRUD.
8.**Destinations** — verify vs pseo-destinations schema before migrating.
11.**Device link** — move to quinn-api SSO surface.
12.**DB Sync + Backup + Restore** — audit live usage; retire if no active caller.
13.**Page illustrations** — investigate disk vs DB backing before migrating.
14.**Gallery (photos)** — last. Three-way coupling: admin backend, image-protection service (port 3030 conflicts with quinn-api; must be resolved before migration), and photo origin on black (track B of the edge-cache plan). The upload flow — multipart → resize → WebP generation → manifest update → disk write → DB insert — is the highest-coupling path in the entire surface. Do not migrate until track B (dedicated photo server on black:8081) is complete and the image-protection port conflict is resolved.