Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
129 lines
4.9 KiB
Markdown
129 lines
4.9 KiB
Markdown
# 05 — Plan permissions API (agent 5/10)
|
|
|
|
Session/dashboard HTTP surface for plan feature permissions. Aligns with
|
|
`03-permission-contract.md`. Public `/api/v1` OpenAPI is unchanged (API-key product
|
|
API); schemas below are the session contract for admin UI + dashboard gating.
|
|
|
|
Backend storage + resolve live in `billing` per `04-backend-model.md` /
|
|
contract §2 / §6 (`plans.features` JSONB + `platform_feature_gates`). Migration:
|
|
`apps/api/sql/schema/026_plan_features.sql`. Apply with `make migrate` /
|
|
`.\scripts\migrate.ps1` before relying on writes.
|
|
|
|
Handlers call: `CapabilitiesForCompany`, `GetPlanFeatures`, `SetPlanFeatures`,
|
|
`EnableAllPlanFeatures`, `GetFeatureGates`, `SetFeatureGates`, `SetSectionGate`
|
|
(and agent-4 aliases `EnableAllForPlan` / `SetGlobalFeature` / `AssertFeature`).
|
|
|
|
---
|
|
|
|
## Endpoint list + auth
|
|
|
|
| Method | Path | Auth | Purpose |
|
|
|--------|------|------|---------|
|
|
| `GET` | `/api/billing/capabilities` | Session + company | Effective features = plan ∩ global |
|
|
| `GET` | `/api/billing/credits` | Session + company | Existing credits overview **plus** `features`, `disabled_features`, `feature_etag` |
|
|
| `GET` | `/api/auth/me` | Session | Embeds credits overview (same feature fields when credits load) |
|
|
| `GET` | `/api/admin/plans` | Session + **platform admin** | Plans include `features` (overrides) + `resolved_features` (plan_allows) |
|
|
| `POST` | `/api/admin/plans` | Session + **platform admin** | Upsert plan; optional `features` replaces overrides when present |
|
|
| `GET` | `/api/admin/plans/{planID}/features` | Session + **platform admin** | Plan feature editor payload |
|
|
| `PUT` | `/api/admin/plans/{planID}/features` | Session + **platform admin** | Replace overrides `{ "features": { "key": true } }` |
|
|
| `POST` | `/api/admin/plans/{planID}/features/enable-all` | Session + **platform admin** | Set every registry key `true` (custom packages) |
|
|
| `GET` | `/api/admin/feature-gates` | Session + **platform admin** | Global section + feature master switches |
|
|
| `PUT` | `/api/admin/feature-gates` | Session + **platform admin** | Partial upsert `{ "sections": {...}, "features": {...} }` |
|
|
| `PUT` | `/api/admin/feature-gates/sections/{section}` | Session + **platform admin** | Enable/disable one section for **ALL** plans |
|
|
|
|
Mutations require CSRF (dashboard session stack) + platform admin for `/api/admin/*`.
|
|
|
|
Unknown feature keys / sections → `400` `{ "error": "…" }` via `billing.ClientError`.
|
|
|
|
---
|
|
|
|
## Response shapes
|
|
|
|
### `GET /api/billing/capabilities`
|
|
|
|
```json
|
|
{
|
|
"plan_id": 3,
|
|
"plan_name": "Starter",
|
|
"is_custom": false,
|
|
"has_active_plan": true,
|
|
"features": { "catalog.products": true, "settings.api_keys": true },
|
|
"sections": { "catalog": true, "marketing": true },
|
|
"disabled_features": ["integrations.ai.byok"],
|
|
"feature_etag": "sha256:…",
|
|
"entitlements": {
|
|
"plan_name": "Starter",
|
|
"is_free_plan": false,
|
|
"is_paid_plan": true,
|
|
"can_use_ai": true,
|
|
"can_use_eprel": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### `GET|PUT /api/admin/plans/{planID}/features`
|
|
|
|
```json
|
|
{
|
|
"plan_id": 3,
|
|
"plan_name": "Starter",
|
|
"is_custom": false,
|
|
"features": { "settings.api_keys": true },
|
|
"resolved_features": { "catalog.products": true, "integrations.ai.byok": false }
|
|
}
|
|
```
|
|
|
|
- `features` = stored sparse overrides (`{}` if none)
|
|
- `resolved_features` = full registry via `plan_allows` only (globals ignored)
|
|
|
|
### `GET|PUT /api/admin/feature-gates`
|
|
|
|
```json
|
|
{
|
|
"sections": { "shell": true, "marketing": false },
|
|
"features": { "capability.byok": false }
|
|
}
|
|
```
|
|
|
|
Missing gate rows default **enabled** (migration-safe).
|
|
|
|
### `PUT /api/admin/feature-gates/sections/{section}`
|
|
|
|
```json
|
|
{ "enabled": false }
|
|
```
|
|
|
|
Returns full `FeatureGatesView`.
|
|
|
|
---
|
|
|
|
## OpenAPI (session fragment)
|
|
|
|
See `docs/plan-permissions/05-openapi-fragment.yaml` for OpenAPI 3.1 path/components
|
|
suitable for admin/dashboard clients. Not mounted on `/api/v1/openapi.yaml`.
|
|
|
|
---
|
|
|
|
## Files touched (agent 5)
|
|
|
|
| File | Intent |
|
|
|------|--------|
|
|
| `apps/api/internal/httpapi/plan_features_handlers.go` | Handlers |
|
|
| `apps/api/internal/httpapi/server.go` | Route mount |
|
|
| `apps/api/internal/billing/plan_features.go` | Service + DTOs (shared with agent 4) |
|
|
| `apps/api/internal/billing/feature_catalog.go` | Registry from `01-feature-keys.json` |
|
|
| `apps/api/internal/billing/service.go` | `Plan`/`CreditsOverview`/`ListPlans`/`UpsertPlan` |
|
|
| `apps/api/internal/billing/client_errors.go` | Client-facing feature errors |
|
|
| `apps/api/sql/schema/026_plan_features.sql` | Additive migration |
|
|
|
|
---
|
|
|
|
## Manual verify
|
|
|
|
1. `.\scripts\migrate.ps1` (or `make migrate`)
|
|
2. Platform admin: `GET /api/admin/feature-gates` → all sections true
|
|
3. `PUT /api/admin/feature-gates/sections/marketing` `{ "enabled": false }`
|
|
4. Tenant session: `GET /api/billing/capabilities` → marketing keys in `disabled_features`
|
|
5. `POST /api/admin/plans/{id}/features/enable-all` then `GET` → all `resolved_features` true
|
|
6. Non-admin `PUT /api/admin/feature-gates` → 403
|