This is an inventory and disposition audit, not an implementation PR. The recommendation follows the repository's accepted architecture ADR: one Next.js App Router application, protected admin route groups, and shared pure route logic rather than another deployable service.
How to read this
Migrate Preserve the user-visible behavior or data contract while moving ownership into the target app. Replace Rebuild the capability around the target architecture because the Flask implementation is the wrong boundary. Archive Keep a historical copy or export for reference, but remove it from the running product. Retire Delete the route or asset after callers are removed.
Evidence links point to the repository or the issue. Line anchors are tied to the audited commit where possible. The source inventory came from the route decorators in src/app.py, the browser callers in src/templates and src/static, and the route/data utilities under src/utils.
Public pages and compatibility routes
| Route | Current behavior | Disposition | Rationale |
|---|---|---|---|
GET / | Renders home.html. | Migrate | Keep the public landing experience and its tracker entry point. |
GET /tracker | Renders the live map template. | Migrate | Core public product surface. Rebuild the page and its data reads in the target app. |
GET /advent | Renders the Advent page only when ADVENT_ENABLED is true. | Migrate | Seasonal feature is part of the launch scope. Preserve the server-side gate in the new app. |
GET /admin | Renders the admin dashboard without checking auth in Flask. | Replace | Move the page behind the target app's protected admin route group. API auth alone is not a sufficient page boundary. |
GET /admin/route-simulator | Renders the current simulator; JavaScript performs auth/API work. | Replace | Make simulation a target-app admin tool backed by shared route logic. |
GET /admin/route-simulator-legacy | Renders the explicitly deprecated simulator. | Retire | It is a compatibility page with no stated future role. Remove after the new simulator is accepted. |
GET /index | Docstring says redirect, implementation renders tracker.html. | Archive | Record it as a legacy alias. Preserve temporarily only if external links require it, then issue a real redirect or remove it. |
Public APIs
| Endpoint | Current behavior | Disposition | Rationale |
|---|---|---|---|
GET /api/advent/manifest | Returns day metadata and unlock state when Advent is enabled. | Migrate | Client contract is useful. Reissue it from the target app with explicit schema tests. |
GET /api/advent/day/<day> | Returns unlocked content, or 403 metadata without the payload while locked. | Migrate | Security behavior is intentional and should remain server-authoritative. |
/api/santa/* documented in docs/API.md | No matching Flask routes exist. Browser code reads static route JSON instead. | Retire | Do not port an API that is not implemented. Decide separately whether the target app needs a public read API. |
Admin API inventory
All rows below are behind require_admin_auth, except the login endpoint. The current decorator accepts a 24-hour signed token and also accepts the raw ADMIN_PASSWORD as a backward-compatible bearer value.
| Surface | Endpoints | Disposition | Rationale |
|---|---|---|---|
| Authentication | POST /api/admin/login | Replace | Keep password login as a migration input, but replace raw-password bearer fallback with target-app sessions or short-lived credentials, revocation, and rate limits. |
| Location CRUD | GET/POST /api/admin/locationsPUT/DELETE /api/admin/locations/<id> | Migrate | These are the core route editing operations. Preserve validation and response behavior while changing the storage owner. |
| Location validation/import | POST /locations/validatePOST /locations/import | Migrate | Useful authoring workflows. The target implementation needs transaction or atomic-write semantics for bulk replace. |
| Route status and validation | GET /route/statusPOST /route/precompute | Replace | The current precompute handler validates rather than computing, which is a misleading contract. Expose a named validation action and a separate compute/publish action if needed. |
| Route simulation | POST /route/simulate | Replace | Move deterministic simulation into shared domain logic so editor, admin UI, and tests use one implementation. |
| Trial route lifecycle | GET/POST/DELETE /route/trialPOST /route/trial/applyPOST /route/trial/simulate | Replace | Retain preview and apply behavior, but give the target app explicit draft/version semantics instead of a mutable sidecar JSON file. |
| Route backup | GET /backup/export | Migrate | Keep export before the storage move and make restore/import a tested, explicit operation. |
| Advent editor | GET /advent/days, GET/PUT /advent/day/<day>POST /advent/day/<day>/toggle-unlockPOST /advent/validateGET /advent/exportPOST /advent/import | Migrate | Preserve the seasonal editorial workflow, validation, export, and unlock override. Put it behind the target admin boundary. |
Route tools and data files
| Asset | Current role | Disposition | Rationale |
|---|---|---|---|
tools/route-editor | Standalone React/Vite editor with its own package boundary and JSON export. | Replace | Absorb the useful editor flows into the target admin surface. Keep its algorithm ideas as migration evidence, not a second deployable frontend. |
src/static/data/santa_route.json | Primary route data. Flask admin writes it; public browser code reads it directly. | Migrate | Move to the target app's explicit route/content store. Preserve a versioned export and add atomic publish/rollback. |
src/static/data/advent_calendar.json | Advent content, unlock timestamps, and admin overrides. | Migrate | Import into the target content model. Validate year-sensitive unlock timestamps before the 2026 season. |
Route Data/** | Legacy text, candidate route lists, trial routes, and map visualization inputs. | Archive | Keep a dated snapshot for provenance. Do not let the new runtime read multiple competing route sources. |
src/utils/locations.py and src/utils/advent.py | Parsing, normalization, validation, simulation support, caching, and file persistence. | Replace | Port the domain rules deliberately. The late module-level rebinding of load_santa_route_from_json is a migration smell, not a contract to preserve. |
PWA and delivery behavior
| Surface | Current behavior | Disposition | Rationale |
|---|---|---|---|
/sw.js and src/static/sw-register.js | Service worker uses a cache-first strategy, caches a fixed list of paths, and serves /offline.html for failed navigations. | Replace | Rebuild against the target app's generated asset paths and cache policy. Current list includes /index.html, while Flask's /index is a route, so the cache contract is already split. |
| Third-party CDN assets | Templates load Tailwind and Leaflet from CDNs. The service worker avoids caching external origins. | Replace | Make production asset ownership and CSP explicit in the target build. Offline behavior should be tested from a clean browser cache. |
offline.html | Static offline fallback exists at repository root and under templates. | Migrate | Keep the user-facing fallback, consolidate to the target app's public asset path, and verify the service worker scope. |
Deployment and operations
| Asset | Current role | Disposition | Rationale |
|---|---|---|---|
.github/workflows/deploy-on-release.ymlfirst-deploy.yml | Release-based VPS deployment and post-deploy checks. | Migrate | Keep release ownership, permissions, restart, health, and rollback checks. Change the process entrypoint and artifact contents. |
docs/DEPLOY.md | Detailed systemd/VPS ownership and release guidance. | Migrate | Retain the operational lessons. Update service commands, health checks, and data paths for the target app. |
docs/DEPLOYMENT.md | Broad Heroku, Vercel, Netlify, Docker, AWS, VPS, Supervisor, and example guidance. | Archive | It contains mutually inconsistent deployment options and placeholder configurations. Keep as historical reference, then write one supported production path. |
src/app.py process entrypoint | Can run Flask directly or under Gunicorn; dotenv path is computed from the source tree. | Retire | Remove after the target app owns production startup. Keep environment names only where the new deployment contract still uses them. |
Findings that affect migration
Admin page boundary is weaker than the API boundary
/admin and both simulator pages render without Flask auth. The dashboard's JavaScript then calls protected APIs. A user can load the page shell without credentials, and the actual access model is split between browser code and the API decorator. The target app should protect the route group before rendering the shell.
Password fallback weakens token intent
The auth decorator accepts a signed 24-hour token, then falls through to direct comparison with ADMIN_PASSWORD. That means the password itself can be replayed as a bearer token. The target should preserve the login input only long enough to migrate clients, then remove the fallback and add rate limiting and revocation or short expiry.
Mutable JSON is the write authority
Location and Advent mutations load a whole JSON document, modify it in memory, and write it back. Bulk replace, trial apply, and Advent import can overwrite the only current copy. The migration needs atomic writes or a transactional store, versioned snapshots, and a tested restore path before moving admin write traffic.
Documentation describes a different API
docs/API.md documents /api/santa/location, route, distance, and stats endpoints, plus non-prefixed admin URLs. None are registered in Flask. The browser instead reads static route data. Treat the document as stale evidence and do not use it as a migration contract without product confirmation.
Configuration has conflicting defaults
Config.SECRET_KEY has a placeholder default, while src/app.py uses a different default string. The app logs whether ADMIN_PASSWORD is configured, and the docs describe several configuration classes that are not used by the current app. The target should fail closed in production and have one typed configuration source.
There is no executable test result in this audit environment
The repository contains broad pytest coverage and CI workflows, but this checkout has no installed pytest module. Source-level evidence is complete; runtime behavior remains unverified here. The migration PRs should run the existing suite in CI and add browser smoke tests for every accepted public and admin flow.
Recommended migration sequence
- Freeze the contract. Accept this inventory as the baseline. Mark the undocumented
/api/santa/*family as either intentionally retired or separately specified. - Move pure behavior. Port location normalization, validation, route simulation, Advent unlock logic, and schemas into the target domain package. Test them without a web server.
- Import and version data. Convert both JSON stores, archive the legacy Route Data inputs, and prove export, restore, atomic publish, and rollback.
- Build protected admin flows. Recreate location editing, route preview/apply, backup, and Advent editing behind one server-owned admin boundary.
- Cut public pages and PWA. Migrate tracker, Advent, offline behavior, and route-data reads. Retire Flask only after route-by-route smoke checks pass.
Migration issue linkage
This audit is the accepted inventory candidate for issue #217, under parent tracker #199. Future implementation issues should link back here and name the rows they consume. No implementation issue was created by this audit.
Review checklist
- Accept or edit the disposition for each row before porting code.
- Decide whether any external consumer depends on
/indexor the undocumented APIs. - Confirm the target data store and rollback mechanism before enabling admin writes.
- Require evidence for public pages, protected admin pages, Advent lock behavior, PWA offline behavior, and deployment health checks.