feat: feedback submissions with screenshots, the hide-widget preference and its user payload key
- POST /_feedback/api/v1/{key}/submit validates as PHP, refuses non-image screenshots (ImageContentGuard copy) and stores the submission, its public screenshot and the G15Office job in one transaction
- PUT /_feedback/api/v1/me/hidden (jwt.auth) stores the caller's preference; feedback_widget_hidden joins every golem15.user payload
- preflights stay with the framework CORS layer
This commit is contained in:
66
controllers/api/me_hidden_controller.go
Normal file
66
controllers/api/me_hidden_controller.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.golem15.com/golem15/sm-feedback-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/bouncer"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
"git.golem15.com/golem15/summercms/modules/phrasebook"
|
||||
)
|
||||
|
||||
// MeHiddenRules are MeHiddenController::update's validation rules.
|
||||
var MeHiddenRules = []lagoon.RequestRule{
|
||||
{Field: "hidden", Rules: lagoon.ParseRules("required|boolean")},
|
||||
}
|
||||
|
||||
type meHiddenErrors struct {
|
||||
Error string `json:"error"`
|
||||
Errors any `json:"errors"`
|
||||
}
|
||||
|
||||
type meHiddenBody struct {
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
// MeHidden handles PUT /_feedback/api/v1/me/hidden (jwt.auth): the signed-in
|
||||
// user's "hide the widget" preference. hidden must be a Laravel boolean
|
||||
// (true, false, 1, 0, "1", "0"); anything else is 422 {"error":"Validation
|
||||
// failed","errors":...}. Only the JWT principal's row is written, and the
|
||||
// answer is {"hidden":bool}. The value also rides on every user payload
|
||||
// (feedback_widget_hidden), so there is no read route.
|
||||
func MeHidden(app *backpack.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
gdb := database(app)
|
||||
user, ok := bouncer.User(r.Context())
|
||||
if gdb == nil || !ok || user == nil || user.ID == 0 {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
}
|
||||
input, ok := readInput(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
tr, _ := app.Lookup[*phrasebook.Translator]()
|
||||
errs, err := lagoon.ValidateRequest(ctx, gdb.WithContext(ctx), input, MeHiddenRules, tr)
|
||||
if err != nil {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
writeJSON(w, http.StatusUnprocessableEntity, meHiddenErrors{
|
||||
Error: "Validation failed",
|
||||
Errors: orderedErrors{errs: errs, order: lagoon.ErrorKeys(errs, MeHiddenRules)},
|
||||
})
|
||||
return
|
||||
}
|
||||
hidden := laravelTrue(input["hidden"])
|
||||
if err := models.SetWidgetHidden(ctx, gdb, user.ID, hidden); err != nil {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, meHiddenBody{Hidden: hidden})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user