382 lines
15 KiB
Markdown
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.
|