lilith-platform.live/docs/admin-api-migration.md

382 lines
15 KiB
Markdown

# 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.messenger / quinn.m surface, 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
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.
9. **Touring subscribe** (public POST) + touring-subscribers (admin GET) — wire public surface endpoint.
10. **Mail threads** — largest non-content feature; placement in quinn.messenger (quinn.m) vs `/admin/*` needs a decision.
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.