Initial import of zappier-edge from zapier monorepo

This commit is contained in:
George Lambert 2026-09-11 13:50:46 -04:00
commit f0d193b221
120 changed files with 19867 additions and 0 deletions

BIN
docs/superpowers/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,163 @@
# Accounting, User Management & Customer Portal — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** Extend Zappier with three surfaces: (a) company accounting & system management, (b) internal user management, and (c) an end-user customer portal — with full documentation and screenshot walkthroughs for each.
**Architecture:** Same ports-and-adapters style as the existing codebase. New domain modules (`src/invoicing.ts`, `src/reports.ts`, `src/accounts.ts`) expose pure logic + repo interfaces; SQLite adapters live in `src/db/`; HTTP wiring goes into `src/admin.ts` (company side) and a new `src/portal.ts` (customer side). The admin SPA gains tabs; the portal is a second dependency-free SPA under `portal/`. No new runtime deps in Phase 12; Phase 3 adds `otplib`-style TOTP only if unavoidable — prefer `node:crypto` scrypt for passwords and a minimal TOTP implementation per RFC 6238.
**Tech Stack:** Node 20 · TS strict · Express 4 · better-sqlite3 · jest/supertest.
## Global Constraints
- Money is integer cents everywhere; multipliers applied once, `Math.round`.
- Money/date logic must be pure functions, unit-tested without HTTP or SQLite.
- All SQL in `src/db/` repos only; all tables `CREATE TABLE IF NOT EXISTS`; schema changes for existing tables use idempotent `ALTER TABLE ... ADD COLUMN` guarded by `PRAGMA table_info` checks.
- Existing 87 jest tests + 4 mocha tests stay green; `npx tsc --noEmit` clean after every task.
- No servers left running after any verification; walkthrough screenshots use a throwaway `ZAPPIER_DB`.
- PDFs: Phase 12 deliver **print-ready HTML report/invoice pages** (browser Print → PDF, zero deps) plus CSV; binary PDF generation is deferred unless the owner asks.
- The demo password `$$$Adm1n###` and `admin-dev-key` remain dev defaults, overridable by env.
## Surface map
| Surface | URL | Auth | Audience |
|---|---|---|---|
| Admin SPA (extended) | `/admin` | login token / `x-admin-key` | Company ops & accounting |
| Customer portal SPA (new) | `/portal` | customer session token (+TOTP 2FA) | End-user customers |
| Public API | `/v1` | `x-api-key` | Unchanged |
---
## Phase 1 — Accounting backend
### Task 1: Schema + customer billing identity
**Files:**
- Modify: `src/auth.ts``Customer` gains `billingType: 'stripe' | 'purchase_order'` (default `'stripe'`), `email?: string`
- Modify: `src/db/customer-repo.ts` — idempotent `ALTER TABLE customers ADD COLUMN billing_type TEXT NOT NULL DEFAULT 'stripe'`; same for `email TEXT`; map in `toCustomer`
- Modify: `src/admin.ts``PUT /customers/:id` accepts `billingType` (validated) and `email`
- Test: `tests/db-customer.test.ts`, `tests/admin.test.ts` (extend)
**Interfaces:**
- Consumes: existing `CustomerRepo`
- Produces: `Customer.billingType`, `Customer.email` used by all later tasks
- [x] Extend `tests/db-customer.test.ts`: migration on existing DB adds columns with `'stripe'` default; round-trip `billingType`/`email`
- [x] Extend `tests/admin.test.ts`: `PUT /customers/:id` sets `billingType: 'purchase_order'`; invalid value → 400
- [x] Implement; run suites; commit `feat(accounting): customer billing types and email`
### Task 2: Invoice engine
**Files:**
- Create: `src/invoicing.ts` — types + pure generation logic + repo interface
- Create: `src/db/invoice-repo.ts` — SQLite adapter
- Test: `tests/invoicing.test.ts`, `tests/db-invoice.test.ts`
**Interfaces:**
```ts
export interface Invoice {
id: string; // INV-2026-07-<customerSeq>
customerId: string;
period: string; // YYYY-MM
status: 'draft' | 'issued' | 'paid';
lines: InvoiceLine[]; // { endpointId, calls, cents }
totalCents: number; // sum of lines (gross usage)
creditCents: number; // monthly credit applied
billableCents: number; // totalCents - creditCents, floored at 0
billingType: 'stripe' | 'purchase_order';
poNumber?: string; // PO billing only
issuedAtMs?: number; dueAtMs?: number; paidAtMs?: number;
}
export interface InvoiceRepo {
save(invoice: Invoice): void;
get(id: string): Invoice | undefined;
list(filter: { customerId?: string; period?: string; status?: Invoice['status'] }): Invoice[];
nextSequence(period: string): number;
}
export function buildInvoice(args: {
customer: Customer; period: string; sequence: number;
entries: UsageEntry[]; tier: TierConfig; poNumber?: string;
}): Invoice; // groups entries by endpoint; status 'draft'
```
- [x] Tests: line grouping, credit math (partial/zero/excess), id format, PO fields
- [x] Repo tests: save/get/list filters, sequence increments per period
- [x] Implement; commit `feat(accounting): invoice engine`
### Task 3: Reports service (billing + trends, CSV/JSON)
**Files:**
- Create: `src/reports.ts``billingRows(entries, customers, tiers, range)` → rows `{customerId, name, billingType, calls, totalCents, creditCents, billableCents}`; `usageTrend(entries, bucket: 'day'|'week')``[{bucket, calls, cents}]`; `toCsv(rows)` with RFC-4180 escaping
- Test: `tests/reports.test.ts`
- [x] Tests: date-range filtering (inclusive from, exclusive to), per-customer vs all, per-billingType filter, trend bucketing across month boundary, CSV quoting of commas/quotes/newlines
- [x] Implement; commit `feat(accounting): reports service`
### Task 4: Admin accounting API
**Files:**
- Modify: `src/admin.ts` — new routes (all behind existing admin auth):
- `POST /invoices/generate { period, customerId?, poNumber? }` → builds draft invoices for the period (all customers or one; idempotent per customer+period — regenerating replaces the draft)
- `POST /invoices/:id/issue`, `POST /invoices/:id/paid`
- `GET /invoices?customerId&period&status`
- `GET /invoices/:id` (+ `?format=html` print-ready invoice page)
- `GET /reports/billing?from&to&customerId&billingType&format=json|csv`
- `GET /reports/usage-trend?from&to&bucket&customerId`
- `GET /zapier/status``{ published: boolean, triggerCount, actionCount, baseUrl }` read from `zapier-app/` files (static inspection, no network)
- Test: `tests/admin-accounting.test.ts`
- [x] Tests per route incl. CSV content-type, filter combos, invoice lifecycle transitions (draft→issued→paid; illegal transitions → 409)
- [x] Implement; commit `feat(accounting): admin accounting API`
---
## Phase 2 — Admin UI: Accounting, Reports, Users tabs
### Task 5: Invoices tab (generate, filter, lifecycle, print page)
### Task 6: Reports tab (date-range pickers, customer + billing-type filters, CSV download, trend charts as inline SVG)
### Task 7: Users tab (admin account management: list/create/deactivate admin users backed by a new `admin_users` table replacing the static two-account map; login endpoint reads the table; env seed preserved)
### Task 8: System tab (Zapier connection status, billing job last-run info from `billing_reports`/`job_locks`)
Each: admin UI section + `tests/` coverage for any new API + screenshot verification. Commit per task.
---
## Phase 3 — Customer portal (`/portal`)
### Task 9: Customer identity
- `customers` += `password_hash`, `totp_secret`, `totp_enabled`, `email_verified`
- `src/accounts.ts`: scrypt hash/verify (`node:crypto`), session tokens (new `portal_sessions` table), signup `POST /portal/api/signup` (creates customer on `free` tier + issues API key), login `POST /portal/api/login`
- Tests: hash round-trip, signup/login flows, session expiry
### Task 10: TOTP 2FA
- RFC 6238 TOTP (HMAC-SHA1, 30 s step, 6 digits) implemented in `src/accounts.ts` (no dep): `generateTotpSecret`, `totpUri(secret, email)`, `verifyTotp(secret, code, window=1)`
- Routes: `POST /portal/api/2fa/setup` (returns secret + otpauth URI; QR rendered client-side via a tiny inline QR lib or Google-Charts-free canvas QR — decision: render otpauth URI as text + QR via `qrcode` npm dep, portal-side only), `POST /portal/api/2fa/enable`, `POST /portal/api/2fa/verify` (login second step), `POST /portal/api/2fa/disable`
- Tests: known RFC vectors, window tolerance, login requires second factor when enabled
### Task 11: Portal dashboard API
- `GET /portal/api/me` (profile, tier, apiKey, regenerate key `POST /portal/api/api-key`)
- `GET /portal/api/usage` (month-to-date + credit)
- `GET /portal/api/invoices` (own invoices only, scoped by session customer)
- `POST /portal/api/reload { amountCents }` — prepaid balance: `customers` += `balance_cents`; Stripe PaymentIntent via existing SDK (test mode); balance drawn down at invoice issue before metered reporting
- `PUT /portal/api/email-invoicing { enabled, email }` — stored prefs; billing job emails PO invoices (send via SMTP env config; dev: log-only transport)
- Tests: session scoping (cannot read other customers' invoices), reload math, key regeneration invalidates old key
### Task 12: Portal SPA
- `portal/index.html` + `portal/app.js` in the same design language as the new admin: login/signup/2FA screens, dashboard (usage + balance), invoices (history + print), billing (reload, email invoicing), API docs + pricing info pages (rendered from `openapi.yaml` + live rate card)
- Screenshot verification of every screen
---
## Phase 4 — Documentation
### Task 13: `docs/ACCOUNTING.md` + PDF — company accounting walkthrough (invoices, PO billing, reports, trends, CSV export) with screenshots
### Task 14: `docs/USER-MANAGEMENT.md` + PDF — admin users walkthrough
### Task 15: `docs/CUSTOMER-PORTAL.md` + PDF — end-user walkthrough (signup, 2FA, reload, invoices, API docs)
### Task 16: Refresh `USER-MANUAL.md`, `DEVELOPER.md`, `README.md`; re-export all PDFs
---
## Self-review notes
- Spec coverage: Stripe **and** PO billing (Tasks 12, 11), invoicing histories (2, 4, 5), CSV/PDF + date range + all/individual/by-type (3, 4, 6), usage/billing trends (3, 6), management reports (4, 6, 8), Zapier connections (4, 8), user management (7), customer login/setup/2FA/invoices/reload/email invoicing/API docs/pricing (912), docs for all three surfaces (1316).
- Type consistency: `Invoice`, `InvoiceRepo`, `billingRows`, `usageTrend`, `toCsv` signatures are defined once above and reused in every later task.

File diff suppressed because it is too large Load diff