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:
@@ -3,8 +3,10 @@ package cabana
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/bouncer"
|
||||
"github.com/yuin/goldmark"
|
||||
)
|
||||
|
||||
@@ -44,3 +46,44 @@ func rejectUnsafeMarkdownHTML(html string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// msgMarkdownPreviewRefused is the fixed 422 detail of a preview whose
|
||||
// rendered HTML the RenderMarkdown gate refuses. The renderer's error text is
|
||||
// never written.
|
||||
const msgMarkdownPreviewRefused = "The rendered HTML contains a script or iframe tag, an event handler, or a javascript, vbscript or data URL and cannot be previewed."
|
||||
|
||||
// AdminMarkdownPreviewRequest is the body of POST /markdown/preview: the
|
||||
// markdown source of a form field.
|
||||
type AdminMarkdownPreviewRequest struct {
|
||||
Markdown string `json:"markdown"`
|
||||
}
|
||||
|
||||
// AdminMarkdownPreviewResult is the data of a POST /markdown/preview answer:
|
||||
// the HTML RenderMarkdown produced for the source.
|
||||
type AdminMarkdownPreviewResult struct {
|
||||
HTML string `json:"html"`
|
||||
}
|
||||
|
||||
// markdownPreview serves POST /markdown/preview: it renders the source
|
||||
// through RenderMarkdown for the admin form preview. Any signed-in backend
|
||||
// administrator may call it; it reads and writes no records. Output the
|
||||
// RenderMarkdown gate refuses is a 422 on markdown with a fixed message.
|
||||
func (s *service) markdownPreview(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := bouncer.User(r.Context())
|
||||
if !ok || principal == nil || !principal.Backend {
|
||||
WriteError(w, http.StatusUnauthorized, "unauthenticated", msgUnauthenticated)
|
||||
return
|
||||
}
|
||||
var body AdminMarkdownPreviewRequest
|
||||
if err := s.decodeStrictBody(w, r, &body); err != nil {
|
||||
writeCRUDError(w, err)
|
||||
return
|
||||
}
|
||||
html, err := RenderMarkdown(body.Markdown)
|
||||
if err != nil {
|
||||
WriteErrorDetails(w, http.StatusUnprocessableEntity, "validation_failed", "Validation failed",
|
||||
map[string]any{"markdown": []string{msgMarkdownPreviewRefused}})
|
||||
return
|
||||
}
|
||||
WriteData(w, http.StatusOK, AdminMarkdownPreviewResult{HTML: html}, nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user