Skip to content

02 — Code Updates / Migration Plan

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.

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.ts
  • 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-api Worker (API), admin-ui Pages (presentation), invoice-renderer Worker (billing — Stripe Billing Transition scope).
  • Auth: Cloudflare Access at the edge (JumpCloud or Google Workspace, deferred). Admin-api validates CF-Access-Jwt-Assertion on 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.
PhaseScopeGate to next
0Pre-work: Rotate and remove hardcoded secrets from adventive.php. Move to AWS Secrets Manager. This is a prerequisite.Secrets cleared from source history
1Scaffold 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
2Implement 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
3Cohort 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
4Implement 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
5Cohort 2 migration — billing-ops operators move to new admin.Cohort retention ≥ 2 weeks
6Implement campaigns, managed service jobs, override files (Cohort 3 workflows 26–31).Parity checklist complete
7Cohort 3 migration — remaining operators.All operators on new admin
8Phase 4 deferred items: tooling (deploy, Cognito sync, benchmarks), partner report evaluation.All workflows covered or explicitly deferred
9Freeze legacy admin (read-only mode). Start 30-day freeze window.30 days clean
10Decommission CodeIgniter admin. Archive repo.

Legacy files are not modified — they are inventoried so the replacement is feature-complete.

Legacy fileLinesReplaced byRiskNotes
application/controllers/Auth.php205Cloudflare Access (no code equivalent)LowAuth moved entirely to edge; no application auth controller
application/controllers/Dashboard.php42admin-api/src/routes/charts.ts + UI dashboard featureLowSimple aggregation
application/controllers/Account.php460admin-api/src/routes/customers.ts, accounts.ts, users.tsMediumCRUD + account-service API call replicated in Worker
application/controllers/Billing.php960admin-api/src/routes/invoices.ts, billing-profiles.ts (Stripe reads)HighInvoice generation retired; payment processing via Stripe; PDF stays in invoice-renderer Worker
application/controllers/Campaign.php111admin-api/src/routes/campaigns.tsMediumAd copy curl call → internal Worker call
application/controllers/Report.php445Phase 4 evaluation — analytics Worker or Stripe reportsHigh22 methods; do not port to admin-api
application/controllers/Reporting.php997Cloudflare Cron Trigger Workers (separate from admin-api)High25 scheduled-delivery methods → individual Cron Triggers
application/controllers/Tools.php451admin-api/src/routes/tools.ts (partial)MediumApp deployment tooling may be retired; Cognito sync stays
application/controllers/Override.php167admin-api/src/routes/overrides.ts + R2LowS3 → R2 migration; same versioning model
application/controllers/Chart.php98admin-api/src/routes/charts.tsLow4 JSON endpoints; straightforward port
application/controllers/ManagedServiceJob.php104admin-api/src/routes/managed-jobs.tsLowCRUD; clean port
application/controllers/Utility.php58Cloudflare Cron Trigger Workers or retiredLowCLI-only; resetApiRequestCounts, updateLiveCampaigns
application/controllers/api/Campaign_Controller.php117admin-api/src/routes/campaigns.tsLowDataTables pagination → standard pagination
application/controllers/api/Managedservices_Controller.php99admin-api/src/routes/managed-jobs.tsLowSame
application/models/Account_model.php1,321admin-api/src/db/queries/accounts.tsHighAll queries must be reproduced with full parity
application/models/Billing_model.php933admin-api/src/stripe/client.ts + residual DB queriesHighBilling queries largely retire; Stripe reads replace most
application/models/Campaigns_model.php665admin-api/src/db/queries/campaigns.tsMedium
application/models/Report_model.php5,333Phase 4 — analytics WorkerCriticalDo not attempt to port inline; 24 bespoke SQL functions
application/models/ManagedService_model.php505admin-api/src/db/queries/managed-jobs.tsMedium
application/models/Stats_model.php1,286Partially retire (billing stats → Stripe); remainder in analytics WorkerHigh
application/models/Override_model.php280admin-api/src/r2/overrides.tsLowS3 ops → R2 ops
application/models/Dashboard_model.php121admin-api/src/db/queries/charts.tsLow
application/config/adventive.phpCloudflare secrets + environment variablesCriticalSecrets must be rotated before this file is removed from history
application/libraries/Jc_auth.phpCloudflare Access (no application code)LowRetired entirely
application/libraries/Mailgun.phpRetained in invoice-renderer WorkerLowNot part of admin-api scope
application/libraries/AmazonS3.phpCloudflare R2 SDK in overrides WorkerLowS3 → R2
application/libraries/AwsCognito.phpCognito SDK in tools routeLowThin wrapper retained
application/libraries/AppDeploy.phpEvaluate retirement vs. thin portLowCodeDeploy tooling; may be superseded by wrangler/Actions
FilePurpose
admin-api/src/index.tsHono app entrypoint; CF Access middleware; global error handler
admin-api/src/auth/access.tsValidates CF-Access-Jwt-Assertion; extracts operator email
admin-api/src/auth/rbac.tsMaps operator email → RBAC role from config; enforces per-route
admin-api/src/db/client.tsHyperdrive MySQL client factory
admin-api/openapi.jsonGenerated from @hono/zod-openapi; consumed by UI type generation
admin-ui/src/api/generated.tsTypeScript types generated from OpenAPI spec
admin-ui/public/_redirectsCF Pages SPA fallback: /* /index.html 200
.github/workflows/deploy-api.ymlwrangler deploy on merge to main
.github/workflows/deploy-ui.ymlPages CI/CD on merge to main
admin-api/wrangler.tomlWorker config: name, routes, Hyperdrive binding, R2 binding, secrets
  • Entire ~/Repositories/BitBucket/adventive-admin repository — archived, not deleted.

Added:

PackagePurpose
honoHTTP framework for Workers
@hono/zod-openapiOpenAPI spec generation — single source of truth for types
zodSchema validation
stripeStripe SDK (Workers-compatible)
react, react-domUI framework
@tanstack/react-queryServer state management
@tanstack/react-routerType-safe routing
viteBuild tool
tailwindcssUtility CSS
shadcn/ui components + @radix-ui/*UI component primitives
vitestUnit testing
@playwright/testE2E testing

Removed (end of Phase 10): PHP 7.3 runtime, CodeIgniter 3.1, all composer dependencies.

  • 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_service internal API is retired as Stripe Billing Transition completes — any external callers must be identified.

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.

LayerToolScope
Unit — admin-apiVitestRoute handlers, Zod schemas, RBAC logic, DB query builders
Unit — admin-uiVitest + Testing LibraryComponents, hooks, form validation
ContractGenerated OpenAPI typesUI type generation catches API drift at build time
Integrationadmin-api against staging DB + Stripe test modeFull request/response parity with legacy behavior
E2EPlaywright against Pages preview deployCF Access service token for automation; golden-path flows per cohort
ParityPer-cohort checklistDogfooded alongside legacy admin before cohort migration
RollbackAny cohort can return to legacy URLNo 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”
LayerMechanismWhat it protects
EdgeCloudflare Access policy on admin-api.* and admin.* hostnames; JumpCloud SAML/SCIM IdP; WebAuthn second-factorAuthn — only members of JumpCloud Admin Dashboard group reach the Worker
Worker — authn verifyaccessAuth middleware verifies Cf-Access-Jwt-Assertion against the Access JWKS (10-min cache); reads email, groups, sub from claimsDefense-in-depth in case a route bypasses Access (misconfig)
Worker — RBACrequireRole(...) middleware factory; roles derived from JumpCloud groups (Adventive Engineering → super-admin, Adventive Billing → billing, Admin Dashboard → read-only)Authz per route
Worker — sessionensureSession middleware: random opaque token in Secure/HttpOnly/SameSite=Strict cookie; record (csrf, email, rotatedAt) in SESSION_KV; rotates every 8h or on email changeCSRF on mutations, idempotency on billing actions, audit-log seed
Worker — CSRFrequireCsrf middleware on POST/PUT/PATCH/DELETE; asserts X-Csrf-Token header equals the session record’s csrf tokenCSRF
Worker — idempotencyrequireIdempotency on billing/processing mutations; client provides Idempotency-Key header; 24h replay cache in IDEMPOTENCY_KVDouble-submit protection on billing actions
Worker — concurrencyrequireIfMatch on resource updates; client provides If-Match header with current ETagOptimistic 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.

The new admin-api ships these read-only billing endpoints in v1:

  • GET /api/billing/overview — KPIs (MTD/QTD/YTD/LTD), 12-month revenue trend
  • GET /api/billing/invoices — paginated invoice list with status filter
  • GET /api/billing/account-history — 12-month per-account revenue grid
  • GET /api/billing/aging — aging bucket summary + per-account breakdown
  • GET /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.

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.

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).

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.