Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
4.9 KiB
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
{
"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
{
"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 viaplan_allowsonly (globals ignored)
GET|PUT /api/admin/feature-gates
{
"sections": { "shell": true, "marketing": false },
"features": { "capability.byok": false }
}
Missing gate rows default enabled (migration-safe).
PUT /api/admin/feature-gates/sections/{section}
{ "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
.\scripts\migrate.ps1(ormake migrate)- Platform admin:
GET /api/admin/feature-gates→ all sections true PUT /api/admin/feature-gates/sections/marketing{ "enabled": false }- Tenant session:
GET /api/billing/capabilities→ marketing keys indisabled_features POST /api/admin/plans/{id}/features/enable-allthenGET→ allresolved_featurestrue- Non-admin
PUT /api/admin/feature-gates→ 403