Appearance
Plans Catalog and Management
The Plans module exposes two route groups under /api/plans.
Public Catalog
GET /api/plansGET /api/plans/:id
These routes are marked with @PublicRoute() and only return plans that are:
isPublic = trueisActive = true- not soft deleted
Public and management list queries sort recommended plans first, then fall back to creation order.
The public response omits internal management-only fields such as internalAlias, isPublic, isActive, and timestamps.
Management Routes
GET /api/plans/manageGET /api/plans/manage/:idPOST /api/plans/managePUT /api/plans/manage/:idDELETE /api/plans/manage/:id
These routes currently require @AuthUser(). They do not yet enforce a more specific backoffice role because the current API does not have a dedicated shared authorization pattern for that boundary.
Validation Rules
internalAliasmust be lowercase and slug-like (letters,numbers,hyphens)colormust be a 6-digit hex string- prices must be non-negative numbers
- subscription plans require a
subscriptionConfig - one-time plans require
subscriptionConfig = null - each resource limit requires
quantity,limitType, andalertThreshold quantityis stored in the resource's canonical unit — bytes forSTORAGE, integer count for every other resource key;-1means "unlimited"alertThresholdis constrained to0..100
Soft Delete Lifecycle
DELETE /api/plans/manage/:id performs a soft delete by setting deleted_at on the main plans.plans row.
Consequences:
- deleted plans disappear from both public and management reads
- pricing and resource-limit rows remain attached for historical consistency
- future subscription records can still keep foreign-key references to retired plans without hard-deleting catalog history
Response Shape
The API response is section-oriented to match the product configuration UI:
- top-level identity fields
pricingobject for billing rulesresourcesarray for limit configuration
Each resources entry is shaped as { resourceKey, quantity, limitType, alertThreshold }, and resource keys must be unique inside the array.
This keeps the public catalog stable while still reflecting the underlying split-table persistence model.