# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

`io.openziti.zrok` (repo name `myzrok`) is the Node/Express backend for **my.zrok.io** — the billing and
account-management portal that sits in front of the [zrok](https://zrok.io) (OpenZiti) service. It is not zrok
itself; it's the customer-facing layer that handles signup, Auth0 login, Chargebee billing/subscriptions,
organizations, custom domains, and provisioning zrok accounts/frontends on behalf of myzrok users.

There is no build step and no frontend framework/bundler — pages are server-rendered by a hand-rolled template
engine (see Architecture below), and there are no automated tests (`npm test` is a stub).

## Commands

```bash
npm install         # install dependencies
npm start            # runs `node server.js` (no explicit "start" script; npm's default)
```

Local Postgres for development (see `myzrokdb/README.md`):

```bash
docker run -d --name myzrokdb -p 5432:5432/tcp \
  -e POSTGRES_PASSWORD=mysecretpassword -e PGDATA=/var/lib/postgresql/data/pgdata \
  -e POSTGRES_DB=myzrokdb -v ./myzrokdb:/var/lib/postgresql/data postgres
```

The app requires a populated `.env` (see keys used throughout `assets/scripts/`, e.g. `DB_*`, `AUTH_SECRET`,
`CLIENT_ID`, `CHARGEBEE_*`, `ZROK_*`, `ZROK_DB_*`, `INFLUX_*`, `JWK_URI`). There's no `.env.example` — cross-check
an existing `.env` or a teammate for values. The `Dockerfile` deletes `.env` at build time; in deployed
environments all config comes from real environment variables.

There is no lint or test command configured — `npm test` is a placeholder that always exits 1.

## Architecture

### Two Postgres databases, two Sequelize/pg clients

- `assets/scripts/db/pgdb.js` — myzrok's own database (`DB_*` env vars). Owns `usermeta` (per-user billing/zrok
  linkage state, stored as JSONB), the session table (via `connect-pg-simple`), and everything under
  `migrations/` at the repo root. On module load it runs `umzug.up()` automatically against `migrations/*.js`, so
  **every process boot applies any pending migration** — be careful running multiple instances against a shared DB
  during development.
- `assets/scripts/db/zrokpgdb.js` — zrok's own database (`ZROK_DB_*` env vars), queried directly for zrok-side
  account data as a read path independent of the zrok REST API.

Both modules self-initialize on `require()` (top-level `init().then(...)`) and export raw query helpers
(`getClient`, `query`-style functions) rather than a class/singleton you construct — just `require` and call.

### External integrations

- **Auth0** (`express-openid-connect`, `assets/scripts/auth/`) — handles login/session; `auth0Config.afterCallback`
  is wired to `myzrokBackend.afterAuthCallback` in `server.js`, which is where new users get provisioned.
- **Chargebee** (`assets/scripts/payment/`, `assets/scripts/subscription/`, `assets/scripts/plans/`) — billing,
  subscriptions, plans, invoices. `CHARGEBEE_SERVICE`/`CHARGEBEE_APIKEY` are used directly via `axios` in places
  (e.g. `assets/scripts/routes/api.routes.js`) in addition to the Chargebee-specific service modules.
- **zrok admin API** (`assets/scripts/zrok/zrok-rest-client.js`) — calls the zrok service itself (`ZROK_URL`,
  `ZROK_ADMIN_SECRET`) to create/delete accounts, ziti identities, and frontends.
- **zrok orchestration API** (`assets/scripts/zrok/zrok-orchestration/`) — a separate service
  (`ZROK_ORCHESTRATION_URL`, `ZROK_ORCHESTRATION_ADMIN_SECRET`) used for custom-domain "stack" provisioning
  (`assets/scripts/customdomains/`).
- **InfluxDB** (`assets/scripts/metrics/UtilizationService.js`) — usage/utilization metrics shown on the
  dashboard (`/api/usage`, `/api/totals`).

Central orchestration logic that ties Auth0/Chargebee/zrok together (new user provisioning, linking an existing
zrok account, etc.) lives in `assets/scripts/myzrok-backend.js`, which `server.js` and most routes call into.

### Routing

`server.js` mounts one router per concern under `/api/*` (users, payment-sources, subscriptions,
payment-invoices, auth, subscription-events, feedback, plans, domains, organization-management), plus a generic
`apiRouter` at `/api` and `router` at `/` (`assets/scripts/routes/base.routes.js`). All API handlers check
`req.oidc.isAuthenticated()` manually per-route (no shared auth middleware) and return `401` inline.

### Server-rendered pages: the template system

There is **no** frontend framework. `assets/scripts/template-handler.js` reads `assets/html/template.htm` plus
every file in `assets/html/*.htm` (pages) and `assets/components/*.htm` (partials) into memory, watches them with
`chokidar` for hot-reload, and does simple `{{placeholder}}` string substitution:

- `{{contents}}` in `template.htm` is replaced by the matching page from `assets/html/`.
- `{{componentId}}` inside a page is replaced by the matching file in `assets/components/`.
- New pages **must** live in `assets/html/` and end in `.htm` or they won't be served.

`base.routes.js`'s catch-all `GET /:url` resolves `assets/html/<url>.htm` this way, and additionally:
1. Requires auth (redirects to `/logout` otherwise).
2. Loads the user's `usermeta` + Chargebee billing details every navigation and injects them into the page as an
   inline `<script>const userMeta = {...}</script>` (built via raw string concatenation in `buildPage()` in
   `base.routes.js` — not templated/escaped, treat with care if touching it).
3. Applies redirect rules: unlinked zrok account → `/settings`, org member who isn't an org admin → `/organization`.

Pages live in `assets/html/` (dashboard, billing, billingDetails, billingHistory, domains, organization, pricing,
settings, users, zroklogin). Client-side JS/CSS for them is under `assets/scripts/frontend/` and `assets/styles/`.

### Data model notes

- `usermeta` (myzrok DB) is a single JSONB blob per row, not a normalized table — fields like `zrokId`,
  `subscriptionId`, `planId`, `organizationToken`, `isOrgAdmin` are read/written as ad hoc JSON properties across
  the codebase rather than a fixed schema.
- Sequelize models (`assets/scripts/**/Model.js`) back the normalized tables created by `migrations/*.js`
  (custom domains, front ends, organization API clients, plans, and their variants). Migrations run via
  `umzug` against `pgdb.js`'s Sequelize instance and are tracked in a `SequelizeMeta` table — add new ones as
  `migrations/NN_description.js` following the existing `up`/`down` pattern and numeric prefix ordering.

### Deployment

- `Dockerfile` builds a plain `node:24-slim` image running `npm start`; it deletes any local `.env` so production
  config must come from real environment variables.
- `jenkinsfiles/deploy-serverless.jenkinsfile` drives CI/CD (staging/production) via NetFoundry's shared Jenkins
  library.
- `terraform/` provisions the AWS infrastructure (ALB, RDS/Postgres, Cognito, ACM, Route53, S3, IAM) for the
  `zrok-billing` service.
