mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-04 20:46:37 +08:00
fix: improve play profile input validation, error responses, and HTTP status codes
- createPlayProfileHandler: return 201 Created (not 200) on success - create/update handlers: return 500 for infra errors, 400 only for validation errors (using new ErrPlayProfileValidation sentinel) - deletePlayProfileHandler: validate JSON body (was silently ignored) - verifyPlayProfilePINHandler: validate JSON body (was silently ignored) - verify handler catch-all: return 500 (not 400) for unexpected errors - Service layer: wrap validation errors with ErrPlayProfileValidation so handlers can distinguish client vs server errors Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -51,11 +51,15 @@ func createPlayProfileHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "每个用户最多只能创建 3 个观影 Profile"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "每个用户最多只能创建 3 个观影 Profile"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if errors.Is(err, service.ErrPlayProfileValidation) {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, row)
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusCreated, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,10 +80,14 @@ func updatePlayProfileHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
c.JSON(http.StatusForbidden, gin.H{"error": "profile forbidden"})
|
c.JSON(http.StatusForbidden, gin.H{"error": "profile forbidden"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if errors.Is(err, service.ErrPlayProfileValidation) {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
c.JSON(http.StatusOK, row)
|
c.JSON(http.StatusOK, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +97,10 @@ func deletePlayProfileHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
uid, _ := c.Get(middleware.CtxUserID)
|
uid, _ := c.Get(middleware.CtxUserID)
|
||||||
userID := toString(uid)
|
userID := toString(uid)
|
||||||
var req deletePlayProfileReq
|
var req deletePlayProfileReq
|
||||||
_ = c.ShouldBindJSON(&req)
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
profile, err := svc.Repo.PlayProfile.FindByID(c.Request.Context(), c.Param("id"))
|
profile, err := svc.Repo.PlayProfile.FindByID(c.Request.Context(), c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -141,7 +152,10 @@ func deletePlayProfileHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
func verifyPlayProfilePINHandler(svc *service.Container) gin.HandlerFunc {
|
func verifyPlayProfilePINHandler(svc *service.Container) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
var req verifyPlayProfilePINReq
|
var req verifyPlayProfilePINReq
|
||||||
_ = c.ShouldBindJSON(&req)
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||||
|
return
|
||||||
|
}
|
||||||
uid, _ := c.Get(middleware.CtxUserID)
|
uid, _ := c.Get(middleware.CtxUserID)
|
||||||
profile, err := svc.PlayProfiles.VerifyPIN(c.Request.Context(), c.Param("id"), toString(uid), req.PIN)
|
profile, err := svc.PlayProfiles.VerifyPIN(c.Request.Context(), c.Param("id"), toString(uid), req.PIN)
|
||||||
if errors.Is(err, service.ErrPlayProfileNotFound) {
|
if errors.Is(err, service.ErrPlayProfileNotFound) {
|
||||||
@@ -157,7 +171,7 @@ func verifyPlayProfilePINHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expiresAt := time.Now().Add(12 * time.Hour)
|
expiresAt := time.Now().Add(12 * time.Hour)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ var (
|
|||||||
ErrPlayProfileForbidden = errors.New("profile forbidden")
|
ErrPlayProfileForbidden = errors.New("profile forbidden")
|
||||||
ErrPlayProfilePINInvalid = errors.New("pin invalid")
|
ErrPlayProfilePINInvalid = errors.New("pin invalid")
|
||||||
ErrPlayProfileLimit = errors.New("profile limit reached")
|
ErrPlayProfileLimit = errors.New("profile limit reached")
|
||||||
|
ErrPlayProfileValidation = errors.New("validation error")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPlayProfileService is the constructor.
|
// NewPlayProfileService is the constructor.
|
||||||
@@ -194,7 +196,7 @@ func (s *PlayProfileService) updateExisting(ctx context.Context, row *model.Play
|
|||||||
if in.RequirePIN && in.PIN != "" {
|
if in.RequirePIN && in.PIN != "" {
|
||||||
patch["pin_hash"] = hashPIN(in.PIN)
|
patch["pin_hash"] = hashPIN(in.PIN)
|
||||||
} else if in.RequirePIN && row.PINHash == "" {
|
} else if in.RequirePIN && row.PINHash == "" {
|
||||||
return nil, errors.New("pin required")
|
return nil, fmt.Errorf("%w: pin required", ErrPlayProfileValidation)
|
||||||
}
|
}
|
||||||
if !in.RequirePIN {
|
if !in.RequirePIN {
|
||||||
patch["pin_hash"] = ""
|
patch["pin_hash"] = ""
|
||||||
@@ -267,19 +269,21 @@ func (s *PlayProfileService) TouchActive(ctx context.Context, id string) error {
|
|||||||
|
|
||||||
// validateProfileInput rejects malformed payloads. On create we require
|
// validateProfileInput rejects malformed payloads. On create we require
|
||||||
// user_id; on update we allow it to be empty (caller supplies it via URL).
|
// user_id; on update we allow it to be empty (caller supplies it via URL).
|
||||||
|
// Errors wrap ErrPlayProfileValidation so handlers can distinguish
|
||||||
|
// validation failures (400) from infrastructure errors (500).
|
||||||
func validateProfileInput(in PlayProfileInput, requireUser bool) error {
|
func validateProfileInput(in PlayProfileInput, requireUser bool) error {
|
||||||
if strings.TrimSpace(in.Name) == "" {
|
if strings.TrimSpace(in.Name) == "" {
|
||||||
return errors.New("name required")
|
return fmt.Errorf("%w: name required", ErrPlayProfileValidation)
|
||||||
}
|
}
|
||||||
if requireUser && strings.TrimSpace(in.UserID) == "" {
|
if requireUser && strings.TrimSpace(in.UserID) == "" {
|
||||||
return errors.New("user_id required")
|
return fmt.Errorf("%w: user_id required", ErrPlayProfileValidation)
|
||||||
}
|
}
|
||||||
if requireUser && in.RequirePIN && strings.TrimSpace(in.PIN) == "" {
|
if requireUser && in.RequirePIN && strings.TrimSpace(in.PIN) == "" {
|
||||||
return errors.New("pin required")
|
return fmt.Errorf("%w: pin required", ErrPlayProfileValidation)
|
||||||
}
|
}
|
||||||
if in.RequirePIN && in.PIN != "" {
|
if in.RequirePIN && in.PIN != "" {
|
||||||
if len(in.PIN) < 4 || len(in.PIN) > 8 {
|
if len(in.PIN) < 4 || len(in.PIN) > 8 {
|
||||||
return errors.New("pin must be 4-8 characters")
|
return fmt.Errorf("%w: pin must be 4-8 characters", ErrPlayProfileValidation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user