frm/handlers/collector.go

132 lines
3.5 KiB
Go
Raw Normal View History

2025-01-10 18:52:12 +00:00
package handlers
import (
"log/slog"
"maps"
"net/http"
"net/url"
"slices"
"github.com/acaloiaro/frm"
"github.com/acaloiaro/frm/internal"
"github.com/acaloiaro/frm/types"
"github.com/acaloiaro/frm/ui"
2025-01-11 00:11:33 +00:00
"github.com/google/uuid"
2025-01-10 18:52:12 +00:00
)
// View renders the form viewer for the collector
func View(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
i, err := frm.Instance(ctx)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
formID, err := formID(ctx)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
f, err := internal.Q(ctx, i.DBArgs).GetForm(ctx, internal.GetFormParams{
WorkspaceID: i.WorkspaceID,
ID: *formID,
})
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
// Render the form collector
err = ui.Viewer((frm.Form)(f)).Render(ctx, w)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
// Collect handles collector form submissions
func Collect(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
i, err := frm.Instance(ctx)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
formID, err := formID(ctx)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
f, err := internal.Q(ctx, i.DBArgs).GetForm(ctx, internal.GetFormParams{
WorkspaceID: i.WorkspaceID,
ID: *formID,
})
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
err = r.ParseForm()
if err != nil {
slog.Error("[collector] unable to parse form", "error", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
2025-01-11 00:11:33 +00:00
submission := r.Form
errs := validate(f, submission)
2025-01-10 18:52:12 +00:00
if errs.Any() {
slog.Info("[collector] failed validation", "errors", errs)
w.WriteHeader(http.StatusBadRequest)
} else {
2025-01-11 00:11:33 +00:00
// TODO Redirect to a thank-you page
2025-01-10 18:52:12 +00:00
w.Header().Add("hx-redirect", frm.CollectorPathForm(ctx, *formID))
2025-01-11 00:11:33 +00:00
2025-01-10 18:52:12 +00:00
w.WriteHeader(http.StatusOK)
}
2025-01-11 00:11:33 +00:00
// Validation renders whether there are errors or not errors, so that non-erroneous fields can be cleared of error messages
// as the user corrects validation errors
2025-01-10 18:52:12 +00:00
allFields := slices.Collect(maps.Keys(f.Fields))
err = ui.Validation(allFields, errs).Render(ctx, w)
if err != nil {
slog.Error("[collector] error while reporting validation error", "error", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
2025-01-11 00:11:33 +00:00
formFieldValues := types.FormFieldValues{}
for fieldID, fieldValue := range submission {
formFieldValues[fieldID] = types.FormFieldSubmission{
ID: uuid.New(),
FormFieldID: uuid.MustParse(fieldID), // TODO do not use MustParse
Order: f.Fields[fieldID].Order,
Required: f.Fields[fieldID].Required,
Hidden: f.Fields[fieldID].Hidden,
Type: f.Fields[fieldID].Type,
DataType: f.Fields[fieldID].DataType,
Value: fieldValue,
}
}
_, err = internal.Q(ctx, i.DBArgs).SaveSubmission(ctx, internal.SaveSubmissionParams{
FormID: formID,
WorkspaceID: i.WorkspaceID,
Status: internal.SubmissionStatusPartial,
Fields: formFieldValues,
})
if err != nil {
slog.Error("[collector] unable to save submission")
w.WriteHeader(http.StatusInternalServerError)
return
}
2025-01-10 18:52:12 +00:00
}
// validate validates forms
func validate(f internal.Form, submission url.Values) (errs types.ValidationErrors) {
errs = types.ValidationErrors{}
for submittedFieldID := range maps.Keys(submission) {
field := f.Fields[submittedFieldID]
formFieldValue := submission[submittedFieldID]
if err := field.Validate(formFieldValue); err != nil {
errs[submittedFieldID] = err
}
}
return errs
}