02 — Code Updates / Migration Plan
Summary of changes
Section titled “Summary of changes”Greenfield replacement. Two new repositories (adventive-admin-api and adventive-admin-ui) ship in parallel with the existing CodeIgniter admin at ~/Repositories/BitBucket/adventive-admin. Both read/write the same backing database during the transition via Cloudflare Hyperdrive (MySQL connection pooling). Operators migrate cohort-by-cohort. The CodeIgniter admin is not modified — it is decommissioned once parity is reached and a 30-day freeze window completes.
Before beginning implementation: production secrets (application/config/adventive.php) must be rotated and moved to AWS Secrets Manager or Cloudflare secrets. This is a prerequisite, not a Phase 1 task.
Target repository layout
Section titled “Target repository layout”adventive-admin-api/ (TypeScript / Hono on Cloudflare Workers)├── src/│ ├── index.ts # Hono app entrypoint + CF Access middleware│ ├── auth/│ │ ├── access.ts # CF-Access-Jwt-Assertion validation│ │ └── rbac.ts # Role derivation from Access email → super-admin/billing/read-only│ ├── routes/│ │ ├── customers.ts # GET/PATCH /customers, /customers/:id│ │ ├── users.ts # GET /customers/:id/users, POST /users/:id/unlock│ │ ├── accounts.ts # Account settings, activation, cancellation│ │ ├── campaigns.ts # GET /campaigns, /ads (per account)│ │ ├── invoices.ts # GET /invoices → reads Stripe│ │ ├── billing-profiles.ts # GET/PUT /billing-profiles/:account_id│ │ ├── managed-jobs.ts # CRUD /managed-jobs│ │ ├── overrides.ts # CRUD /overrides (R2-backed)│ │ ├── service-levels.ts # Account plan/tier management│ │ ├── charts.ts # GET /charts/* (account reg, revenue, ads)│ │ ├── tools.ts # User lookup, preview lookup, ad types│ │ └── operators.ts # Operator RBAC management│ ├── db/│ │ ├── client.ts # Hyperdrive MySQL client│ │ ├── queries/│ │ │ ├── accounts.ts # Port of Account_model queries│ │ │ ├── campaigns.ts # Port of Campaigns_model queries│ │ │ ├── managed-jobs.ts # Port of ManagedService_model queries│ │ │ └── charts.ts # Port of Dashboard_model + Chart queries│ │ └── schema.ts # Table name constants│ ├── stripe/│ │ └── client.ts # Stripe SDK wrapper (invoice reads, customer reads)│ ├── r2/│ │ └── overrides.ts # R2 operations for override file versioning│ └── schemas/│ └── *.ts # Zod schemas → @hono/zod-openapi├── wrangler.toml├── vitest.config.ts└── test/ ├── routes/ └── auth/
adventive-admin-ui/ (React + Vite on Cloudflare Pages)├── src/│ ├── main.tsx│ ├── app/│ │ ├── router.tsx # TanStack Router config│ │ └── query-client.ts # TanStack Query config│ ├── features/│ │ ├── dashboard/ # Dashboard overview → replaces Dashboard.php view│ │ ├── customers/ # Account list, detail, create, activate, cancel│ │ ├── billing/ # Invoice list, payment, profiles, delinquent│ │ ├── managed-jobs/ # Managed service job CRUD│ │ ├── campaigns/ # Campaign + ad list, ad copy│ │ ├── overrides/ # Override file versioning│ │ └── tools/ # User lookup, ad types, benchmarks, deploy│ ├── api/│ │ └── generated.ts # Types generated from admin-api OpenAPI spec│ ├── components/│ │ ├── ui/ # shadcn/ui primitives│ │ └── brand/ # Adventive brand overrides (colors, typography)│ └── lib/│ └── access.ts # Read CF Access identity from /me endpoint├── public/│ └── _redirects # SPA fallback for CF Pages deep links├── vite.config.ts└── e2e/ # Playwright └── flows/ ├── account-management.spec.ts └── billing.spec.tsBefore / after
Section titled “Before / after”Before
Section titled “Before”- Monolithic CodeIgniter 3.1 app: PHP controllers render HTML views, same process handles DB access, billing rollup, invoice rendering, file I/O, and scheduled report delivery.
- Auth: JumpCloud LDAP → Duo 2FA → CI session. Currently: LDAP only (Duo disabled).
- Secrets: hardcoded in
application/config/adventive.php. - CSRF: globally disabled.
- Deploy: Bitbucket Pipelines → AWS CLI → CodeDeploy → EC2. Shared deploy-script repo (
shell). - Observability: EC2 server logs; no structured metrics or alerts.
- Three services:
admin-apiWorker (API),admin-uiPages (presentation),invoice-rendererWorker (billing — Stripe Billing Transition scope). - Auth: Cloudflare Access at the edge (JumpCloud or Google Workspace, deferred). Admin-api validates
CF-Access-Jwt-Assertionon every request. No application-managed sessions. - Secrets: Wrangler
secret/ Cloudflare environment variables. Nothing committed to source. - CSRF: eliminated — stateless JWT architecture has no session to forge.
- Deploy: GitHub Actions →
wrangler deploy(Worker) + Pages CI/CD (SPA). Single command per environment. - Observability: Workers Analytics Engine + Logpush → existing log sink.
Migration phases
Section titled “Migration phases”| Phase | Scope | Gate to next |
|---|---|---|
| 0 | Pre-work: Rotate and remove hardcoded secrets from adventive.php. Move to AWS Secrets Manager. This is a prerequisite. | Secrets cleared from source history |
| 1 | Scaffold admin-api Worker (Hono skeleton, CF Access middleware, RBAC, stub endpoints) + admin-ui Pages (Vite + shadcn/ui + TanStack Router skeleton). Deploy to preview URLs. Cloudflare Access policy provisioned for all ~10 operators. | Preview deploys green; all operators can authenticate |
| 2 | Implement customer + account management domain (Cohort 1 workflows 1–12): account list, detail, create, activate, cancel, user lookup, unlock, settings. RBAC enforcement. Full test coverage. | Feature parity checklist for cohort 1 complete |
| 3 | Cohort 1 migration — account-management operators move to new admin. Legacy stays live. Collect feedback, iterate UI. | Cohort retention at new admin ≥ 2 weeks with no rollback |
| 4 | Implement billing read surface (Cohort 2 workflows 13–25): invoice list/detail from Stripe, billing profiles, delinquent view, revenue history, managed services. Co-develops with Stripe Billing Transition Phase B. | Billing parity checklist complete; Stripe SoR stable |
| 5 | Cohort 2 migration — billing-ops operators move to new admin. | Cohort retention ≥ 2 weeks |
| 6 | Implement campaigns, managed service jobs, override files (Cohort 3 workflows 26–31). | Parity checklist complete |
| 7 | Cohort 3 migration — remaining operators. | All operators on new admin |
| 8 | Phase 4 deferred items: tooling (deploy, Cognito sync, benchmarks), partner report evaluation. | All workflows covered or explicitly deferred |
| 9 | Freeze legacy admin (read-only mode). Start 30-day freeze window. | 30 days clean |
| 10 | Decommission CodeIgniter admin. Archive repo. | — |
File-by-file change list
Section titled “File-by-file change list”Legacy files are not modified — they are inventoried so the replacement is feature-complete.
| Legacy file | Lines | Replaced by | Risk | Notes |
|---|---|---|---|---|
application/controllers/Auth.php | 205 | Cloudflare Access (no code equivalent) | Low | Auth moved entirely to edge; no application auth controller |
application/controllers/Dashboard.php | 42 | admin-api/src/routes/charts.ts + UI dashboard feature | Low | Simple aggregation |
application/controllers/Account.php | 460 | admin-api/src/routes/customers.ts, accounts.ts, users.ts | Medium | CRUD + account-service API call replicated in Worker |
application/controllers/Billing.php | 960 | admin-api/src/routes/invoices.ts, billing-profiles.ts (Stripe reads) | High | Invoice generation retired; payment processing via Stripe; PDF stays in invoice-renderer Worker |
application/controllers/Campaign.php | 111 | admin-api/src/routes/campaigns.ts | Medium | Ad copy curl call → internal Worker call |
application/controllers/Report.php | 445 | Phase 4 evaluation — analytics Worker or Stripe reports | High | 22 methods; do not port to admin-api |
application/controllers/Reporting.php | 997 | Cloudflare Cron Trigger Workers (separate from admin-api) | High | 25 scheduled-delivery methods → individual Cron Triggers |
application/controllers/Tools.php | 451 | admin-api/src/routes/tools.ts (partial) | Medium | App deployment tooling may be retired; Cognito sync stays |
application/controllers/Override.php | 167 | admin-api/src/routes/overrides.ts + R2 | Low | S3 → R2 migration; same versioning model |
application/controllers/Chart.php | 98 | admin-api/src/routes/charts.ts | Low | 4 JSON endpoints; straightforward port |
application/controllers/ManagedServiceJob.php | 104 | admin-api/src/routes/managed-jobs.ts | Low | CRUD; clean port |
application/controllers/Utility.php | 58 | Cloudflare Cron Trigger Workers or retired | Low | CLI-only; resetApiRequestCounts, updateLiveCampaigns |
application/controllers/api/Campaign_Controller.php | 117 | admin-api/src/routes/campaigns.ts | Low | DataTables pagination → standard pagination |
application/controllers/api/Managedservices_Controller.php | 99 | admin-api/src/routes/managed-jobs.ts | Low | Same |
application/models/Account_model.php | 1,321 | admin-api/src/db/queries/accounts.ts | High | All queries must be reproduced with full parity |
application/models/Billing_model.php | 933 | admin-api/src/stripe/client.ts + residual DB queries | High | Billing queries largely retire; Stripe reads replace most |
application/models/Campaigns_model.php | 665 | admin-api/src/db/queries/campaigns.ts | Medium | |
application/models/Report_model.php | 5,333 | Phase 4 — analytics Worker | Critical | Do not attempt to port inline; 24 bespoke SQL functions |
application/models/ManagedService_model.php | 505 | admin-api/src/db/queries/managed-jobs.ts | Medium | |
application/models/Stats_model.php | 1,286 | Partially retire (billing stats → Stripe); remainder in analytics Worker | High | |
application/models/Override_model.php | 280 | admin-api/src/r2/overrides.ts | Low | S3 ops → R2 ops |
application/models/Dashboard_model.php | 121 | admin-api/src/db/queries/charts.ts | Low | |
application/config/adventive.php | — | Cloudflare secrets + environment variables | Critical | Secrets must be rotated before this file is removed from history |
application/libraries/Jc_auth.php | — | Cloudflare Access (no application code) | Low | Retired entirely |
application/libraries/Mailgun.php | — | Retained in invoice-renderer Worker | Low | Not part of admin-api scope |
application/libraries/AmazonS3.php | — | Cloudflare R2 SDK in overrides Worker | Low | S3 → R2 |
application/libraries/AwsCognito.php | — | Cognito SDK in tools route | Low | Thin wrapper retained |
application/libraries/AppDeploy.php | — | Evaluate retirement vs. thin port | Low | CodeDeploy tooling; may be superseded by wrangler/Actions |
New files (indicative)
Section titled “New files (indicative)”| File | Purpose |
|---|---|
admin-api/src/index.ts | Hono app entrypoint; CF Access middleware; global error handler |
admin-api/src/auth/access.ts | Validates CF-Access-Jwt-Assertion; extracts operator email |
admin-api/src/auth/rbac.ts | Maps operator email → RBAC role from config; enforces per-route |
admin-api/src/db/client.ts | Hyperdrive MySQL client factory |
admin-api/openapi.json | Generated from @hono/zod-openapi; consumed by UI type generation |
admin-ui/src/api/generated.ts | TypeScript types generated from OpenAPI spec |
admin-ui/public/_redirects | CF Pages SPA fallback: /* /index.html 200 |
.github/workflows/deploy-api.yml | wrangler deploy on merge to main |
.github/workflows/deploy-ui.yml | Pages CI/CD on merge to main |
admin-api/wrangler.toml | Worker config: name, routes, Hyperdrive binding, R2 binding, secrets |
Removed files (end of Phase 10)
Section titled “Removed files (end of Phase 10)”- Entire
~/Repositories/BitBucket/adventive-adminrepository — archived, not deleted.
Dependencies
Section titled “Dependencies”Added:
| Package | Purpose |
|---|---|
hono | HTTP framework for Workers |
@hono/zod-openapi | OpenAPI spec generation — single source of truth for types |
zod | Schema validation |
stripe | Stripe SDK (Workers-compatible) |
react, react-dom | UI framework |
@tanstack/react-query | Server state management |
@tanstack/react-router | Type-safe routing |
vite | Build tool |
tailwindcss | Utility CSS |
shadcn/ui components + @radix-ui/* | UI component primitives |
vitest | Unit testing |
@playwright/test | E2E testing |
Removed (end of Phase 10): PHP 7.3 runtime, CodeIgniter 3.1, all composer dependencies.
Breaking changes
Section titled “Breaking changes”- Admin URL changes during transition (operators informed by cohort).
- Operator auth moves from JumpCloud LDAP → Cloudflare Access. All ~10 operators must be provisioned in the Access policy before Cohort 1 migrates.
- API consumers (any automation scripts pointing at legacy admin URLs) must update to new contract.
billing_serviceinternal API is retired as Stripe Billing Transition completes — any external callers must be identified.
Schema-freeze policy during transition
Section titled “Schema-freeze policy during transition”While both admins share the same database, no schema changes that break backward compatibility with the CodeIgniter admin may be deployed. New columns (nullable, with defaults) are acceptable. Column drops, renames, and type changes are blocked until the CodeIgniter admin is decommissioned. This policy is in effect from Phase 1 until Phase 10.
Testing strategy
Section titled “Testing strategy”| Layer | Tool | Scope |
|---|---|---|
| Unit — admin-api | Vitest | Route handlers, Zod schemas, RBAC logic, DB query builders |
| Unit — admin-ui | Vitest + Testing Library | Components, hooks, form validation |
| Contract | Generated OpenAPI types | UI type generation catches API drift at build time |
| Integration | admin-api against staging DB + Stripe test mode | Full request/response parity with legacy behavior |
| E2E | Playwright against Pages preview deploy | CF Access service token for automation; golden-path flows per cohort |
| Parity | Per-cohort checklist | Dogfooded alongside legacy admin before cohort migration |
| Rollback | Any cohort can return to legacy URL | No data migration risk — shared DB |
Update — 2026-04-30: locked API contract decisions
Section titled “Update — 2026-04-30: locked API contract decisions”API surface — hybrid REST + composite endpoints
Section titled “API surface — hybrid REST + composite endpoints”Strict REST per resource for the primary surface. A small allow-list of composite endpoints is permitted on heavy screens to avoid request waterfalls without coupling the API to UI shape. The current allow-list:
GET /api/accounts/:id?include=users,campaigns,adunits,advertisers,invoices,managedjobs,permissions,plan— Account Detail (replaces the 9-call waterfall a strict-REST design would force).
Future additions to the composite allow-list require an ADR. Default for all new endpoints is strict REST.
Auth — Cloudflare Access JWT + Worker session
Section titled “Auth — Cloudflare Access JWT + Worker session”| Layer | Mechanism | What it protects |
|---|---|---|
| Edge | Cloudflare Access policy on admin-api.* and admin.* hostnames; JumpCloud SAML/SCIM IdP; WebAuthn second-factor | Authn — only members of JumpCloud Admin Dashboard group reach the Worker |
| Worker — authn verify | accessAuth middleware verifies Cf-Access-Jwt-Assertion against the Access JWKS (10-min cache); reads email, groups, sub from claims | Defense-in-depth in case a route bypasses Access (misconfig) |
| Worker — RBAC | requireRole(...) middleware factory; roles derived from JumpCloud groups (Adventive Engineering → super-admin, Adventive Billing → billing, Admin Dashboard → read-only) | Authz per route |
| Worker — session | ensureSession middleware: random opaque token in Secure/HttpOnly/SameSite=Strict cookie; record (csrf, email, rotatedAt) in SESSION_KV; rotates every 8h or on email change | CSRF on mutations, idempotency on billing actions, audit-log seed |
| Worker — CSRF | requireCsrf middleware on POST/PUT/PATCH/DELETE; asserts X-Csrf-Token header equals the session record’s csrf token | CSRF |
| Worker — idempotency | requireIdempotency on billing/processing mutations; client provides Idempotency-Key header; 24h replay cache in IDEMPOTENCY_KV | Double-submit protection on billing actions |
| Worker — concurrency | requireIfMatch on resource updates; client provides If-Match header with current ETag | Optimistic concurrency |
Legacy auth components (Auth.php, Jc_auth.php LDAP library, Duo SDK, app-managed sessions, CSRF flag) have no equivalent in the new admin-api. They are removed entirely.
Billing scope — read-only in v1
Section titled “Billing scope — read-only in v1”The new admin-api ships these read-only billing endpoints in v1:
GET /api/billing/overview— KPIs (MTD/QTD/YTD/LTD), 12-month revenue trendGET /api/billing/invoices— paginated invoice list with status filterGET /api/billing/account-history— 12-month per-account revenue gridGET /api/billing/aging— aging bucket summary + per-account breakdownGET /api/billing/delinquent— open invoices > 15 days past due
All five depend on a DB_BILLING Hyperdrive binding that is not yet provisioned in dev. The endpoints exist as scaffolds and return 503 with a clear message until the Stripe Billing Transition project provisions Hyperdrives for billing and aggregate_ro databases.
The following mutating billing endpoints are deliberately not in admin-api v1:
- Test invoice generation
- Real invoice generation / billing cycle run
- QuickBooks export
- Commission report generation
- Dunning sweep (delinquent reminders)
- Year-end rollover
- Payment processing (
Billing/processPayment) - Account creation (
Account/validateNewAccount)
These are owned by the Stripe Billing Transition project. The UI keeps the Processing Controls page mocked (with a “Pending Stripe Billing Transition” indicator) until those endpoints are provided. Documented as a hand-off in §05.
Reports — not migrated
Section titled “Reports — not migrated”The legacy Report.php (~5200 LOC, 20+ customer-specific raw SQL queries) does not migrate to admin-api. The Reports surface is removed from the admin-ui in Phase 2. Customer-facing reports move to a future analytics-service evaluation (Phase 4) or to versioned BI templates.
Worker repository
Section titled “Worker repository”Scaffold lives at ~/Repositories/GitHub/Adventive/adventive-admin-api/. Initial commit established 2026-04-30. Layout:
src/ index.ts Hono app, middleware chain, error handler, OpenAPI doc types/env.ts Bindings + Variables + Operator types auth/ access.ts Cloudflare Access JWT verification (RSASSA-PKCS1-v1_5 + JWKS cache) session.ts Signed-cookie Worker session, CSRF, idempotency, If-Match lib/ db.ts mysql2 connect/query helpers (disableEval:true) logger.ts JSON log line emitter, level-gated errors.ts ApiError + named factories schemas/ common.ts shared zod schemas (status, pagination, errors) accounts.ts Account, AccountSummary, AccountUser, Campaign, includes routes/ health.ts /healthz me.ts /api/me accounts.ts /api/accounts (list + composite detail) billing.ts /api/billing/* (read-only; gated on DB_BILLING)Wrangler dry-run for dev env passes. Worker name: adv-svc-admin-api-dev. Custom hostname: admin-api.adventive.dev (AAAA record pre-creation required per standing pattern).
Phase 1 → Phase 2 handoff
Section titled “Phase 1 → Phase 2 handoff”The UI prototype’s src/lib/mock/* files are intentionally shaped to mirror the API contract. Replacing them with TanStack Query hooks against admin-api is the primary handoff. A typed API client generated from the Worker’s openapi.json lives at src/lib/api.ts and replaces the mock module pattern once the API endpoints land.