15 KiB
Admin-API Migration Audit
Purpose: Input to Stage 4 of the edge-cache plan (§5). No routes are migrated here.
Scope: codebase/@features/admin/backend-api/src/routes/ → codebase/@features/api/src/surfaces/admin/
Date: 2026-05-16
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 |
| Pure MIGRATE (no quinn-api equivalent) | 7 |
| Photo-upload coupling: HIGH | 1 resource (gallery — migrate last) |
Route inventory by resource
1. Gallery (photos)
| Method | Path | Writes |
|---|---|---|
| GET | /api/gallery |
— |
| POST | /api/gallery |
gallery_items + disk files + manifest |
| PUT | /api/gallery/:id |
gallery_items |
| DELETE | /api/gallery/:id |
gallery_items + disk files + manifest |
| 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.
Status: PARTIAL — base CRUD exists. Protection trigger + adversary-view proxy: MIGRATE.
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
2. Rates
| Method | Path | Writes |
|---|---|---|
| GET | /api/rates |
— |
| POST | /api/rates |
rate_sections |
| PUT | /api/rates/:id |
rate_sections |
| DELETE | /api/rates/:id |
rate_sections + cascade rate_entries |
| POST | /api/rates/:sectionId/entries |
rate_entries |
| PUT | /api/rates/entries/:entryId |
rate_entries |
| DELETE | /api/rates/entries/:entryId |
rate_entries |
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).
Purge paths: /www/rates, /provider-api/rates
3. Tour Stops
| Method | Path | Writes |
|---|---|---|
| GET | /api/tour-stops |
— |
| POST | /api/tour-stops |
tour_stops |
| PUT | /api/tour-stops/:id |
tour_stops |
| DELETE | /api/tour-stops/:id |
tour_stops |
Quinn-api equivalent: src/surfaces/admin/tour-stops.ts — full CRUD.
Status: EXISTS.
Purge paths: /www/tour, /provider-api/tour-stops
4. Policies
| Method | Path | Writes |
|---|---|---|
| GET | /api/policies |
— |
| POST | /api/policies |
policy_sections |
| PUT | /api/policies/:id |
policy_sections |
| DELETE | /api/policies/:id |
policy_sections + cascade policy_items |
| POST | /api/policies/:sectionId/entries |
policy_items |
| PUT | /api/policies/entries/:entryId |
policy_items |
| DELETE | /api/policies/entries/:entryId |
policy_items |
Quinn-api equivalent: src/surfaces/admin/policies.ts — full CRUD.
Status: EXISTS.
Purge paths: /www/screening (policies appear on screening/provider page)
5. Specialties
| Method | Path | Writes |
|---|---|---|
| GET | /api/specialties |
— |
| POST | /api/specialties |
specialty_categories + optional first entry |
| PUT | /api/specialties/:categorySlug |
specialty_categories |
| DELETE | /api/specialties/:categorySlug |
cascade all entries |
| POST | /api/specialties/:categorySlug/entries |
specialties |
| PUT | /api/specialties/entries/:itemId |
specialties |
| DELETE | /api/specialties/entries/:itemId |
specialties |
Quinn-api equivalent: src/surfaces/admin/specialties.ts — full CRUD for categories + entries.
Status: EXISTS.
Purge paths: /www/specialties, /provider-api/specialties
6. Site Text
| Method | Path | Writes |
|---|---|---|
| GET | /api/site-text |
— |
| POST | /api/site-text |
site_text |
| PUT | /api/site-text |
site_text (upsert) |
| PUT | /api/site-text/upsert |
site_text (upsert alias) |
| PUT | /api/site-text/:id |
site_text |
| DELETE | /api/site-text/:id |
site_text |
Quinn-api equivalent: src/surfaces/admin/site-text.ts — full CRUD + upsert.
Status: EXISTS.
Purge paths: varies by namespace — at minimum all public pages via /www/*
7. Profile (identity / physical / contact)
| Method | Path | Writes |
|---|---|---|
| GET/PUT | /api/identity |
provider identity fields |
| GET/PUT | /api/physical |
physical attribute fields |
| GET/PUT | /api/contact |
contact + social fields |
Quinn-api equivalent: src/surfaces/admin/provider-profile.ts — GET/PUT / + PATCH /:section.
Status: EXISTS (three discrete routes in admin vs section-patching in quinn-api; semantically equivalent).
Purge paths: /provider-api/profile, /www/about
8. About
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).
Purge paths: /www/about
9. Shop Listings
| Method | Path | Writes |
|---|---|---|
| GET | /api/shop |
— |
| POST | /api/shop |
shop_listings |
| GET/PUT/DELETE | /api/shop/:id |
shop_listings |
| POST | /api/shop/:id/photos |
shop_listing_photos + disk |
| DELETE | /api/shop/:id/photos/:photoId |
shop_listing_photos + disk |
| PUT | /api/shop/:id/photos/reorder |
shop_listing_photos.sort_order |
Quinn-api equivalent: src/surfaces/admin/shop-listings.ts — base CRUD. Photo sub-routes absent.
Status: PARTIAL — listing CRUD exists; photo upload/delete/reorder on shop items: MIGRATE.
Purge paths: /www/shop, /provider-api/shop
10. Roster Content
| Method | Path | Writes |
|---|---|---|
| GET | /api/roster-content |
— |
| PUT | /api/roster-content/:slug |
roster_content (upsert) |
Quinn-api equivalent: src/surfaces/admin/roster-content.ts — GET + PUT /:slug.
Status: EXISTS.
Purge paths: /provider-api/roster-content
11. Verified Profiles
| Method | Path | Writes |
|---|---|---|
| GET | /api/verified-profiles |
— |
| POST | /api/verified-profiles |
verified_profiles |
| PUT | /api/verified-profiles/:id |
verified_profiles |
| DELETE | /api/verified-profiles/:id |
verified_profiles |
Quinn-api equivalent: src/surfaces/admin/verified-profiles.ts — full CRUD.
Status: EXISTS.
Purge paths: /www/verified-profiles
12. Etiquette
| Method | Path | Writes |
|---|---|---|
| GET | /api/etiquette |
— |
| POST | /api/etiquette |
etiquette_sections |
| PUT | /api/etiquette/:id |
etiquette_sections |
| DELETE | /api/etiquette/:id |
cascade items |
| POST/PUT/DELETE | /api/etiquette/*/entries/* |
etiquette_items |
Quinn-api equivalent: src/surfaces/admin/etiquette.ts — full CRUD.
Status: EXISTS.
Purge paths: /www/etiquette
13. Hero Strip
| Method | Path | Writes |
|---|---|---|
| GET | /api/hero-strip |
— |
| POST | /api/hero-strip |
hero_strip_items |
| PUT | /api/hero-strip/:id |
hero_strip_items |
| DELETE | /api/hero-strip/:id |
hero_strip_items |
Quinn-api equivalent: src/surfaces/admin/hero-strip.ts — full CRUD.
Status: EXISTS.
Purge paths: /www/home, /provider-api/hero-strip
14. Mail Admin (mailserver accounts)
| Method | Path | Writes |
|---|---|---|
| GET | /api/mail-admin/accounts |
— |
| 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 |
| PATCH | /api/mail/threads/:uid/read |
IMAP flags |
| POST | /api/mail/threads/:uid/draft/approve |
draft_replies |
| POST | /api/mail/threads/:uid/draft/reject |
draft_replies |
| PATCH | /api/mail/draft/:id |
draft_replies |
Quinn-api equivalent: none (messaging is in quinn.m, not the quinn-api admin surface).
Status: MIGRATE. Consider whether this belongs in quinn.m's surface rather than /admin/*.
Purge paths: none (internal inbox)
21. MIGRATE — Destinations (tour city pages)
| Method | Path | Writes |
|---|---|---|
| GET | /api/destinations |
— |
| POST | /api/destinations |
destinations |
| PUT | /api/destinations/:id |
destinations |
| DELETE | /api/destinations/:id |
destinations |
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 (pending schema confirmation).
Purge paths: /www/destinations, /_/escorts/in-{city} pages
22. MIGRATE — DB Sync
GET /api/sync/info, GET /api/sync/export, POST /api/sync/import, POST /api/sync/push, POST /api/sync/pull
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.
23. MIGRATE — Backup / Export / Photo Export / Restore
GET /api/backup, GET /api/export, GET /api/export/stats, POST /api/export/photos/:size, POST /api/restore
Status: MIGRATE or RETIRE. These are SQLite-era backup/restore + photo resize triggers. Audit usage before migrating.
24. MIGRATE — Device Link (TOTP pairing)
POST /api/device-link/start, GET /api/device-link/poll/:token, GET /auth/device-link/callback
Status: MIGRATE. Belongs in quinn-api's SSO/auth surface.
Purge paths: none
Recommended migration order
- System status — GET-only, zero writes; safe first to verify routing plumbing.
- Tour stops — full parity in quinn-api. Wire
purgeEdge(['/www/tour', '/provider-api/tour-stops'])into the handler and retire admin copy. - Site text — full parity. Wire purge for affected namespaces.
- 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.
- Provider profile / About — exists in quinn-api; confirm field parity first.
- Shop listings (base CRUD only — skip photo sub-routes for now) — quinn-api surface exists.
- Cult of Lilith — new quinn-api surface needed under
/admin/cult-of-lilith; straightforward CRUD. - Destinations — verify vs pseo-destinations schema before migrating.
- Touring subscribe (public POST) + touring-subscribers (admin GET) — wire public surface endpoint.
- Mail threads — largest non-content feature; placement in quinn.m vs
/admin/*needs a decision. - Device link — move to quinn-api SSO surface.
- DB Sync + Backup + Restore — audit live usage; retire if no active caller.
- Page illustrations — investigate disk vs DB backing before migrating.
- 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.