55 lines
1.7 KiB
Markdown
55 lines
1.7 KiB
Markdown
# PieCed Portal — Billing Phase 1 patch (suspend-via-admin fix)
|
|
|
|
Single-file fix on top of the Phase 1 v2 drop.
|
|
|
|
## What it fixes
|
|
|
|
The admin panel's suspend/resume button hits
|
|
`/api/admin/tenants/[name]/suspend` (a different route from the
|
|
customer-side `/api/tenants/[name]/suspend`). The v2 drop only
|
|
hooked the customer route — admin suspends were going to K8s
|
|
without producing a row in `tenant_suspension_events`.
|
|
|
|
This patch adds the same `recordSuspensionEvent` hook to the
|
|
admin route. No other code paths affected; no schema changes.
|
|
|
|
## Files
|
|
|
|
```
|
|
src/app/api/admin/tenants/[name]/suspend/route.ts MODIFIED
|
|
```
|
|
|
|
## Deploy
|
|
|
|
Extract over your `pieced-portal/` tree, rebuild, redeploy as
|
|
usual. After the new image is running, verify:
|
|
|
|
1. Suspend any test tenant from the `/admin` panel.
|
|
2. Check the events table:
|
|
|
|
```bash
|
|
kubectl -n pieced-system exec -it portal-db-1 -- psql -U postgres -d portal -c \
|
|
"SELECT * FROM tenant_suspension_events ORDER BY id DESC LIMIT 5;"
|
|
```
|
|
|
|
Expect a fresh `suspended` row for the tenant you just toggled.
|
|
|
|
3. Resume → expect a `resumed` row.
|
|
|
|
## Why I missed this
|
|
|
|
Both routes share the same shape (PATCH/POST that sets
|
|
`spec.suspend`), but they differ on:
|
|
|
|
- URL path (`/api/admin/tenants/...` vs `/api/tenants/...`)
|
|
- Method (POST vs PATCH)
|
|
- Authorization (platform-only vs owner+platform)
|
|
- Caller (admin panel vs customer cancel button)
|
|
|
|
When I grepped for the suspend hook target I matched on the
|
|
customer endpoint and didn't audit cross-cutting admin
|
|
duplicates. I've since checked every site that calls
|
|
`patchTenantSpec`, `createTenant`, or `deleteTenant` — this was
|
|
the only missed billing-relevant one. Other `patchTenantSpec`
|
|
sites are confirmed non-billing (openClawImage, channelUsers).
|