feat(cabana): add markdown preview admin route

- POST {prefix}/api/v1/markdown/preview renders {markdown} through
  cabana.RenderMarkdown in the backend-guarded group behind requireAjax
- refused output is a 422 validation_failed on markdown with a fixed message
- swag annotation, regenerated admin.json and schema.d.ts
- route inventories, CSRF walk (26) and OpenAPI conformance learn the route
- README and docs/backend/forms.md document the route
This commit is contained in:
Jakub Zych
2026-10-06 20:53:07 +02:00
parent 0ff928d6cf
commit b492e79f2b
13 changed files with 517 additions and 6 deletions

View File

@@ -105,6 +105,28 @@
],
"type": "object"
},
"cabana.AdminMarkdownPreviewRequest": {
"properties": {
"markdown": {
"type": "string"
}
},
"required": [
"markdown"
],
"type": "object"
},
"cabana.AdminMarkdownPreviewResult": {
"properties": {
"html": {
"type": "string"
}
},
"required": [
"html"
],
"type": "object"
},
"cabana.AdminProfile": {
"properties": {
"email": {
@@ -361,6 +383,21 @@
],
"type": "object"
},
"cabana.Envelope-cabana_AdminMarkdownPreviewResult": {
"properties": {
"data": {
"$ref": "#/components/schemas/cabana.AdminMarkdownPreviewResult"
},
"meta": {
"$ref": "#/components/schemas/cabana.SuccessMeta"
}
},
"required": [
"data",
"meta"
],
"type": "object"
},
"cabana.Envelope-cabana_AdminProfile": {
"properties": {
"data": {
@@ -2211,6 +2248,93 @@
]
}
},
"/markdown/preview": {
"post": {
"description": "Renders the markdown source through cabana.RenderMarkdown (goldmark without unsafe HTML) and answers the sanitized HTML. Output the renderer's gate refuses is a 422 on markdown. Any backend session may call it.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.AdminMarkdownPreviewRequest"
}
}
},
"description": "Markdown source",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.Envelope-cabana_AdminMarkdownPreviewResult"
}
}
},
"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"
},
"413": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Request Entity Too Large"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Unprocessable Entity"
}
},
"security": [
{
"BackendBearer": []
}
],
"summary": "Render a markdown preview",
"tags": [
"admin"
]
}
},
"/navigation": {
"get": {
"responses": {

View File

@@ -279,6 +279,95 @@ export interface paths {
patch?: never;
trace?: never;
};
"/markdown/preview": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Render a markdown preview
* @description Renders the markdown source through cabana.RenderMarkdown (goldmark without unsafe HTML) and answers the sanitized HTML. Output the renderer's gate refuses is a 422 on markdown. Any backend session may call it.
*/
post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** @description Markdown source */
requestBody: {
content: {
"application/json": components["schemas"]["cabana.AdminMarkdownPreviewRequest"];
};
};
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.Envelope-cabana_AdminMarkdownPreviewResult"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
/** @description Forbidden */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
/** @description Not Found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
/** @description Request Entity Too Large */
413: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
/** @description Unprocessable Entity */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["cabana.ErrorEnvelope"];
};
};
};
};
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/navigation": {
parameters: {
query?: never;
@@ -4240,6 +4329,12 @@ export interface components {
"cabana.AdminLogoutData": {
status: string;
};
"cabana.AdminMarkdownPreviewRequest": {
markdown: string;
};
"cabana.AdminMarkdownPreviewResult": {
html: string;
};
"cabana.AdminProfile": {
email: string;
first_name: string;
@@ -4307,6 +4402,10 @@ export interface components {
data: components["schemas"]["cabana.AdminLogoutData"];
meta: components["schemas"]["cabana.SuccessMeta"];
};
"cabana.Envelope-cabana_AdminMarkdownPreviewResult": {
data: components["schemas"]["cabana.AdminMarkdownPreviewResult"];
meta: components["schemas"]["cabana.SuccessMeta"];
};
"cabana.Envelope-cabana_AdminProfile": {
data: components["schemas"]["cabana.AdminProfile"];
meta: components["schemas"]["cabana.SuccessMeta"];