feat(19.7-01): account contributions, prompted record actions and backend subject events

- pact.HasAccountContributions compiled at Activate; GET /account/contributions
  and POST /account/contributions/{name}/actions/{action} behind interactiveOnly
  (AccountContributionView, AccountContributionActionView, AccountActionResultView)
- RecordAction.prompt (RecordActionPrompt) for a pact.AdminRecordAction with a
  Prompt; the record action route accepts payload only for such an action
  (trimmed JSON string, non-empty, capped at max_length) and hands it to Run as
  Value through CRUDService.RecordActionWithValue; a prompt without a label
  fails boot
- fireSubjectChanged: pact.MCPSubjectChanged for revoked tokens, Administrators
  saves that changed role, permissions or superuser, Roles saves that changed
  grants, deactivation and deletion
- phrases backend::lang.form.action_prompt_required/_too_long (en, pl)
- smoke tests TestAccountContributionsSmoke and TestRecordActionPromptSmoke;
  TestRecordActionPromptRun on Postgres; route inventories, CSRF walk and
  OpenAPI conformance cases extended for the two routes
- regenerated admin/openapi/admin.json and admin/src/api/schema.d.ts; cabana
  README, docs/backend/{admin-controllers,users-and-permissions}.md
This commit is contained in:
Jakub Zych
2026-10-22 16:31:29 +02:00
parent 6887abf575
commit 9f14cf603c
28 changed files with 1431 additions and 20 deletions

View File

@@ -1,6 +1,75 @@
{
"components": {
"schemas": {
"cabana.AccountActionResultView": {
"properties": {
"message": {
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"message",
"url"
],
"type": "object"
},
"cabana.AccountContributionActionView": {
"properties": {
"confirm": {
"type": "string"
},
"danger": {
"type": "boolean"
},
"label": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"confirm",
"danger",
"label",
"name"
],
"type": "object"
},
"cabana.AccountContributionView": {
"properties": {
"actions": {
"items": {
"$ref": "#/components/schemas/cabana.AccountContributionActionView"
},
"type": "array"
},
"description": {
"type": "string"
},
"label": {
"type": "string"
},
"name": {
"type": "string"
},
"status": {
"description": "Status is the section's line for the signed-in admin; \"\" when the\nsection has none.",
"type": "string"
}
},
"required": [
"actions",
"description",
"label",
"name",
"status"
],
"type": "object"
},
"cabana.AdminAPIToken": {
"properties": {
"created_at": {
@@ -677,6 +746,24 @@
],
"type": "object"
},
"cabana.Envelope-array_cabana_AccountContributionView": {
"properties": {
"data": {
"items": {
"$ref": "#/components/schemas/cabana.AccountContributionView"
},
"type": "array"
},
"meta": {
"$ref": "#/components/schemas/cabana.SuccessMeta"
}
},
"required": [
"data",
"meta"
],
"type": "object"
},
"cabana.Envelope-array_cabana_AdminAPIToken": {
"properties": {
"data": {
@@ -785,6 +872,21 @@
],
"type": "object"
},
"cabana.Envelope-cabana_AccountActionResultView": {
"properties": {
"data": {
"$ref": "#/components/schemas/cabana.AccountActionResultView"
},
"meta": {
"$ref": "#/components/schemas/cabana.SuccessMeta"
}
},
"required": [
"data",
"meta"
],
"type": "object"
},
"cabana.Envelope-cabana_AdminAPIToken": {
"properties": {
"data": {
@@ -2361,6 +2463,14 @@
},
"name": {
"type": "string"
},
"prompt": {
"allOf": [
{
"$ref": "#/components/schemas/cabana.RecordActionPrompt"
}
],
"description": "Prompt is present only for an action that asks for one text value in\nits confirm dialog (pact.AdminRecordAction.Prompt); the admin posts the\nvalue as the body key payload, a JSON string."
}
},
"required": [
@@ -2369,6 +2479,27 @@
],
"type": "object"
},
"cabana.RecordActionPrompt": {
"properties": {
"comment": {
"type": "string"
},
"label": {
"type": "string"
},
"max_length": {
"type": "integer"
},
"placeholder": {
"type": "string"
}
},
"required": [
"label",
"max_length"
],
"type": "object"
},
"cabana.RecordEnvelope": {
"properties": {
"data": {
@@ -3097,6 +3228,148 @@
]
}
},
"/account/contributions": {
"get": {
"description": "Lists the sections plugins contribute to the signed-in admin's My account page: label, description, the status for this admin and the actions they may run that apply to them. Empty when no plugin contributes. Personal API tokens are refused (403 interactive_only).",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.Envelope-array_cabana_AccountContributionView"
}
}
},
"description": "OK"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Unauthorized"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Forbidden"
}
},
"security": [
{
"BackendBearer": []
}
],
"summary": "List My account contributions",
"tags": [
"admin"
]
}
},
"/account/contributions/{name}/actions/{action}": {
"post": {
"description": "Runs one action of a contributed section for the signed-in admin. The action's permissions are checked (403) and its Applies rule again (409 when it no longer applies); the answer is the action's message and an optional URL to open. Personal API tokens are refused (403 interactive_only).",
"parameters": [
{
"description": "Section name",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "Action name",
"in": "path",
"name": "action",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.Envelope-cabana_AccountActionResultView"
}
}
},
"description": "OK"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Unauthorized"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Forbidden"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Not Found"
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Conflict"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Unprocessable Entity"
}
},
"security": [
{
"BackendBearer": []
}
],
"summary": "Run a My account contribution action",
"tags": [
"admin"
]
}
},
"/account/preferences": {
"get": {
"description": "The signed-in admin's own UI preferences: theme (system, light or dark; system when not set) and locale (an admin locale code, or \"\" to follow the request locale). Interactive session only (403 interactive_only for personal API tokens).",