CI / ci (push) Failing after 5s
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
88 lines
3.2 KiB
Go
88 lines
3.2 KiB
Go
package server
|
|
|
|
import (
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/plan"
|
|
)
|
|
|
|
// maxRotationReviewBody bounds a POST /dinners/{id}/rotation-review body.
|
|
const maxRotationReviewBody = 1 << 10
|
|
|
|
// rotationDecision is the POST /dinners/{id}/rotation-review body:
|
|
// keep, drop ("Not for us") or not_made ("We didn't make it").
|
|
type rotationDecision struct {
|
|
Decision *string `json:"decision"`
|
|
}
|
|
|
|
type rotationReviewList struct {
|
|
Reviews []plan.Review `json:"reviews"`
|
|
}
|
|
|
|
// getRotationReviews answers GET /rotation-reviews with the pending
|
|
// Rotation checks, oldest plan date first.
|
|
func getRotationReviews(logger *slog.Logger, svc *plan.Service) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
list, err := svc.PendingReviews(r.Context())
|
|
if err != nil {
|
|
writeRotationError(w, r, logger, "list Rotation checks", err)
|
|
return
|
|
}
|
|
writeJSON(w, r, logger, http.StatusOK, rotationReviewList{Reviews: list})
|
|
}
|
|
}
|
|
|
|
// postRotationReview applies the household's answer to "Keep it in
|
|
// rotation?" for a tried dinner, recording the session's actor as
|
|
// updated_by, and answers with the Rotation check as decided.
|
|
func postRotationReview(logger *slog.Logger, svc *plan.Service) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
actor, ok := actorOrFail(w, r, logger)
|
|
if !ok {
|
|
return
|
|
}
|
|
body, err := decodePatch[rotationDecision](http.MaxBytesReader(w, r.Body, maxRotationReviewBody))
|
|
if err != nil {
|
|
writeError(w, r, logger, http.StatusBadRequest, "invalid_request", err.Error())
|
|
return
|
|
}
|
|
if body.Decision == nil {
|
|
writeError(w, r, logger, http.StatusBadRequest, "invalid_request", "decision: required")
|
|
return
|
|
}
|
|
d, err := svc.DecideReview(r.Context(), chi.URLParam(r, "id"), *body.Decision, actor.Subject)
|
|
if err != nil {
|
|
writeRotationError(w, r, logger, "decide Rotation check", err)
|
|
return
|
|
}
|
|
writeJSON(w, r, logger, http.StatusOK, d)
|
|
}
|
|
}
|
|
|
|
// writeRotationError maps a Rotation check error to its API error: a
|
|
// decision other than keep, drop or not_made is 400, an unknown dinner or
|
|
// a dinner never checked 404, and a dinner whose checks are all decided
|
|
// (such as a second decision) or no longer tried 409.
|
|
func writeRotationError(w http.ResponseWriter, r *http.Request, logger *slog.Logger, action string, err error) {
|
|
var invalid *plan.InvalidDecisionError
|
|
switch {
|
|
case errors.As(err, &invalid):
|
|
writeError(w, r, logger, http.StatusBadRequest, "invalid_decision", invalid.Error())
|
|
case errors.Is(err, plan.ErrDinnerNotFound):
|
|
writeError(w, r, logger, http.StatusNotFound, "not_found", "dinner not found")
|
|
case errors.Is(err, plan.ErrReviewNotFound):
|
|
writeError(w, r, logger, http.StatusNotFound, "not_found", "rotation check not found")
|
|
case errors.Is(err, plan.ErrNoPendingReview):
|
|
writeError(w, r, logger, http.StatusConflict, "no_pending_review", err.Error())
|
|
case errors.Is(err, plan.ErrDinnerNotTried):
|
|
writeError(w, r, logger, http.StatusConflict, "dinner_not_tried", err.Error())
|
|
default:
|
|
logger.ErrorContext(r.Context(), action, "error", err)
|
|
writeError(w, r, logger, http.StatusInternalServerError, "internal", "internal error")
|
|
}
|
|
}
|