mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-28 01:03:50 +08:00
173 lines
4.4 KiB
Go
173 lines
4.4 KiB
Go
package server
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/Autumn-27/artex/db"
|
|
)
|
|
|
|
const maxTaskTemplateRequestBytes = 512 << 10
|
|
|
|
// taskTemplateRequest uses pointers so PATCH can distinguish omitted fields from
|
|
// explicit empty values. Empty values are still rejected by the DB validator.
|
|
type taskTemplateRequest struct {
|
|
Name *string `json:"name"`
|
|
Description *string `json:"description"`
|
|
Goal *string `json:"goal"`
|
|
}
|
|
|
|
func decodeTaskTemplateRequest(w http.ResponseWriter, r *http.Request, req *taskTemplateRequest) bool {
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxTaskTemplateRequestBytes)
|
|
if err := decode(r, req); err != nil {
|
|
var tooLarge *http.MaxBytesError
|
|
if errors.As(err, &tooLarge) {
|
|
writeErr(w, http.StatusRequestEntityTooLarge, "请求正文过大")
|
|
} else {
|
|
writeErr(w, http.StatusBadRequest, err.Error())
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func validateTaskTemplateRequest(req taskTemplateRequest) error {
|
|
checks := []struct {
|
|
name string
|
|
value *string
|
|
limit int
|
|
}{
|
|
{name: "name", value: req.Name, limit: db.MaxTaskTemplateNameRunes},
|
|
{name: "description", value: req.Description, limit: db.MaxTaskTemplateTextRunes},
|
|
{name: "goal", value: req.Goal, limit: db.MaxTaskTemplateTextRunes},
|
|
}
|
|
for _, check := range checks {
|
|
if check.value == nil {
|
|
continue
|
|
}
|
|
value := strings.TrimSpace(*check.value)
|
|
if check.name == "name" {
|
|
value = strings.Join(strings.Fields(value), " ")
|
|
}
|
|
if utf8.RuneCountInString(value) > check.limit {
|
|
return fmt.Errorf("%s 最多 %d 个字符", check.name, check.limit)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeTaskTemplateErr(w http.ResponseWriter, err error) {
|
|
switch {
|
|
case errors.Is(err, db.ErrTaskTemplateInvalid):
|
|
writeErr(w, http.StatusBadRequest, err.Error())
|
|
case errors.Is(err, db.ErrTaskTemplateNameConflict):
|
|
writeErr(w, http.StatusConflict, "模板名称已存在")
|
|
case errors.Is(err, db.ErrTaskTemplateNotFound):
|
|
writeErr(w, http.StatusNotFound, "task template not found")
|
|
default:
|
|
writeErr(w, http.StatusInternalServerError, err.Error())
|
|
}
|
|
}
|
|
|
|
func (s *Server) pgListTaskTemplates(w http.ResponseWriter, _ *http.Request) {
|
|
pg := s.pg(w)
|
|
if pg == nil {
|
|
return
|
|
}
|
|
templates, err := pg.ListTaskTemplates()
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"templates": templates})
|
|
}
|
|
|
|
func (s *Server) pgCreateTaskTemplate(w http.ResponseWriter, r *http.Request) {
|
|
pg := s.pg(w)
|
|
if pg == nil {
|
|
return
|
|
}
|
|
var req taskTemplateRequest
|
|
if !decodeTaskTemplateRequest(w, r, &req) {
|
|
return
|
|
}
|
|
if err := validateTaskTemplateRequest(req); err != nil {
|
|
writeErr(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
template, err := pg.CreateTaskTemplate(db.TaskTemplateInput{
|
|
Name: stringValue(req.Name),
|
|
Description: stringValue(req.Description),
|
|
Goal: stringValue(req.Goal),
|
|
})
|
|
if err != nil {
|
|
writeTaskTemplateErr(w, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, template)
|
|
}
|
|
|
|
func (s *Server) pgUpdateTaskTemplate(w http.ResponseWriter, r *http.Request) {
|
|
pg := s.pg(w)
|
|
if pg == nil {
|
|
return
|
|
}
|
|
id, ok := pathInt(r, "id")
|
|
if !ok {
|
|
writeErr(w, http.StatusBadRequest, "bad task template id")
|
|
return
|
|
}
|
|
var req taskTemplateRequest
|
|
if !decodeTaskTemplateRequest(w, r, &req) {
|
|
return
|
|
}
|
|
if err := validateTaskTemplateRequest(req); err != nil {
|
|
writeErr(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if req.Name == nil && req.Description == nil && req.Goal == nil {
|
|
writeErr(w, http.StatusBadRequest, "至少需要提供 name、description 或 goal")
|
|
return
|
|
}
|
|
template, err := pg.PatchTaskTemplate(id, db.TaskTemplatePatch{
|
|
Name: req.Name, Description: req.Description, Goal: req.Goal,
|
|
})
|
|
if err != nil {
|
|
writeTaskTemplateErr(w, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, template)
|
|
}
|
|
|
|
func (s *Server) pgDeleteTaskTemplate(w http.ResponseWriter, r *http.Request) {
|
|
pg := s.pg(w)
|
|
if pg == nil {
|
|
return
|
|
}
|
|
id, ok := pathInt(r, "id")
|
|
if !ok {
|
|
writeErr(w, http.StatusBadRequest, "bad task template id")
|
|
return
|
|
}
|
|
deleted, err := pg.DeleteTaskTemplate(id)
|
|
if err != nil {
|
|
writeErr(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if !deleted {
|
|
writeErr(w, http.StatusNotFound, "task template not found")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"deleted": id})
|
|
}
|
|
|
|
func stringValue(value *string) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
return *value
|
|
}
|