Code-review follow-ups: httputil, validation caps, a11y
Backend: - Extract shared internal/httputil (WriteJSON/ErrorJSON/BadRequest/ ServerError); drop the triple-duplicated helpers in docs, suggestions, vocab. ServerError now logs the real error and returns a generic 500 so raw DB/internal errors never reach the client. - vocab capture: validate doc_id ownership (blank -> none, unknown -> 400 instead of a leaked FK 500); rune-safe clamp word/gloss/definition/ phonetic/example. - vocab review(): wrap the read-modify-write in a transaction (TOCTOU). - /api request-size cap via MaxBytesReader middleware (2 MiB), exempting /api/images (own 10 MiB limit). Frontend: - StatusBar: drive the checking/voicing/collocating indicators from one array; llmDown uses !anyBusy. - Slide-overs: new useFocusTrap hook (focus-in, Tab trap, focus-restore) on GardenPanel + HistoryPanel, both role=dialog/aria-modal/aria-label. - speech.ts: export stopSpeech(); GardenPanel cancels audio on unmount. Tests: add doc_id-validation and field-clamp coverage; full suite green. Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"gitea.parodia.dev/drwily/petal/internal/db"
|
||||
"gitea.parodia.dev/drwily/petal/internal/httputil"
|
||||
)
|
||||
|
||||
// Version-history tuning.
|
||||
@@ -29,8 +30,8 @@ const (
|
||||
// resolve to /api/docs/{id}/versions...
|
||||
func (h *Handler) versionRoutes(r chi.Router) {
|
||||
r.Get("/{id}/versions", h.listVersions)
|
||||
r.Post("/{id}/versions", h.createVersion) // explicit "save a restore point"
|
||||
r.Get("/{id}/versions/{vid}", h.getVersion) // full body for preview
|
||||
r.Post("/{id}/versions", h.createVersion) // explicit "save a restore point"
|
||||
r.Get("/{id}/versions/{vid}", h.getVersion) // full body for preview
|
||||
r.Post("/{id}/versions/{vid}/restore", h.restoreVersion)
|
||||
}
|
||||
|
||||
@@ -50,7 +51,7 @@ func (h *Handler) listVersions(w http.ResponseWriter, r *http.Request) {
|
||||
docID, db.LocalUserID,
|
||||
)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
@@ -59,16 +60,16 @@ func (h *Handler) listVersions(w http.ResponseWriter, r *http.Request) {
|
||||
for rows.Next() {
|
||||
var v db.DocumentVersion
|
||||
if err := rows.Scan(&v.ID, &v.DocID, &v.Title, &v.WordCount, &v.Kind, &v.CreatedAt); err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
httputil.WriteJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
// getVersion returns one snapshot in full (including content) for preview.
|
||||
@@ -79,10 +80,10 @@ func (h *Handler) getVersion(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, v)
|
||||
httputil.WriteJSON(w, http.StatusOK, v)
|
||||
}
|
||||
|
||||
// createVersion takes an explicit, user-requested ('manual') restore point from
|
||||
@@ -96,16 +97,16 @@ func (h *Handler) createVersion(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
v, err := h.insertVersion(doc, db.VersionKindManual)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, v)
|
||||
httputil.WriteJSON(w, http.StatusCreated, v)
|
||||
}
|
||||
|
||||
// restoreVersion copies a snapshot back onto the live document. Before
|
||||
@@ -121,17 +122,17 @@ func (h *Handler) restoreVersion(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
current, err := h.fetch(docID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
if _, err := h.insertVersion(current, db.VersionKindPreRestore); err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -143,7 +144,7 @@ func (h *Handler) restoreVersion(w http.ResponseWriter, r *http.Request) {
|
||||
v.Title, v.Content, v.ContentText, v.WordCount, docID, db.LocalUserID,
|
||||
)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
if n, _ := res.RowsAffected(); n == 0 {
|
||||
@@ -153,10 +154,10 @@ func (h *Handler) restoreVersion(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
doc, err := h.fetch(docID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
httputil.ServerError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, doc)
|
||||
httputil.WriteJSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
// maybeAutoSnapshot records a throttled background snapshot of the just-saved
|
||||
|
||||
Reference in New Issue
Block a user