Files

247 lines
5.1 KiB
Go

package server
import (
"encoding/json"
"errors"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"perler-beads-server/internal/domain"
"strings"
"time"
)
func (s *Server) patternList(c *gin.Context) {
admin := strings.HasPrefix(c.FullPath(), "/api/admin/")
mine := c.FullPath() == "/api/my/patterns"
if admin && !need(c, "admin") || mine && !need(c, "") {
return
}
raw, e := s.list(c, "pattern")
if e != nil {
fail(c, e)
return
}
out := []domain.Pattern{}
u := user(c)
for _, b := range raw {
var p domain.Pattern
if json.Unmarshal(b, &p) != nil {
continue
}
if mine {
if p.OwnerID != u.ID {
continue
}
} else if admin {
if p.StoreID != u.StoreID {
continue
}
} else {
if p.Visibility != "public" || p.StoreID != storeID(c) {
continue
}
}
if q := strings.ToLower(c.Query("q")); q != "" && !strings.Contains(strings.ToLower(p.Title+" "+p.Category), q) {
continue
}
out = append(out, p)
}
ok(c, out)
}
func (s *Server) readPattern(c *gin.Context) (domain.Pattern, bool) {
var p domain.Pattern
if e := s.get(c, "pattern", c.Param("id"), &p); e != nil {
reply(c, 404, "图纸不存在", nil)
return p, false
}
if !domain.CanRead(p, user(c)) {
if user(c).ID == "" {
reply(c, 403, "请先登录", nil)
} else {
reply(c, 401, "无权访问图纸", nil)
}
return p, false
}
return p, true
}
func (s *Server) patternGet(c *gin.Context) {
if p, yes := s.readPattern(c); yes {
ok(c, p)
}
}
func (s *Server) patternSave(c *gin.Context) {
admin := strings.HasPrefix(c.FullPath(), "/api/admin/")
role := ""
if admin {
role = "admin"
}
if !need(c, role) {
return
}
var p domain.Pattern
if !bind(c, &p) {
return
}
u := user(c)
if p.StoreID == "" {
p.StoreID = u.StoreID
}
if p.StoreID != u.StoreID {
reply(c, 401, "无权写入其他门店", nil)
return
}
var store domain.Store
if s.get(c, "store", p.StoreID, &store) != nil {
fail(c, errors.New("门店不存在"))
return
}
if !admin {
p.Visibility = "private"
}
if e := p.Validate(); e != nil {
fail(c, e)
return
}
// 持久图纸只能引用当前门店已维护的色号,禁止客户端伪造物料位置。
var palette []domain.Color
if s.get(c, "palette", p.StoreID, &palette) != nil {
fail(c, errors.New("门店色板不存在"))
return
}
authoritative := map[string]domain.Color{}
for _, color := range palette {
authoritative[color.Code] = color
}
for i, color := range p.Colors {
saved, exists := authoritative[color.Code]
if !exists || !strings.EqualFold(saved.Hex, color.Hex) {
fail(c, errors.New("图纸色号与门店色板不符"))
return
}
p.Colors[i] = saved
}
p.ID = uuid.NewString()
p.OwnerID = u.ID
p.CreatedAt = time.Now().UTC().Format(time.RFC3339)
p.Demo = false
if e := s.put(c, "pattern", p.ID, p.StoreID, p.OwnerID, p); e != nil {
fail(c, e)
return
}
ok(c, p)
}
func (s *Server) patternDelete(c *gin.Context) {
if !need(c, "") {
return
}
p, yes := s.readPattern(c)
if !yes {
return
}
if p.OwnerID != user(c).ID {
reply(c, 401, "只能删除自己的图纸", nil)
return
}
if _, e := s.DB.Exec(c, `DELETE FROM documents WHERE kind='pattern' AND id=$1 AND owner_id=$2`, p.ID, user(c).ID); e != nil {
fail(c, e)
return
}
ok(c, nil)
}
func (s *Server) patternPatch(c *gin.Context) {
if !need(c, "admin") {
return
}
p, yes := s.readPattern(c)
if !yes {
return
}
if p.StoreID != user(c).StoreID {
reply(c, 401, "门店无权限", nil)
return
}
var in struct {
Title *string `json:"title"`
Category *string `json:"category"`
Visibility *string `json:"visibility"`
}
if !bind(c, &in) {
return
}
if in.Title != nil {
p.Title = *in.Title
}
if in.Category != nil {
p.Category = *in.Category
}
if in.Visibility != nil {
p.Visibility = *in.Visibility
}
if e := p.Validate(); e != nil {
fail(c, e)
return
}
if e := s.put(c, "pattern", p.ID, p.StoreID, p.OwnerID, p); e != nil {
fail(c, e)
return
}
ok(c, p)
}
func (s *Server) favoriteList(c *gin.Context) {
if !need(c, "") {
return
}
rows, e := s.DB.Query(c, `SELECT d.body FROM favorites f JOIN documents d ON d.kind='pattern' AND d.id=f.pattern_id WHERE f.user_id=$1 ORDER BY f.pattern_id`, user(c).ID)
if e != nil {
fail(c, e)
return
}
defer rows.Close()
out := []string{}
for rows.Next() {
var b []byte
if e = rows.Scan(&b); e != nil {
fail(c, e)
return
}
var p domain.Pattern
if e = json.Unmarshal(b, &p); e != nil {
fail(c, e)
return
}
if domain.CanRead(p, user(c)) {
out = append(out, p.ID)
}
}
if e = rows.Err(); e != nil {
fail(c, e)
return
}
ok(c, out)
}
func (s *Server) favoritePut(c *gin.Context) {
if !need(c, "") {
return
}
if _, yes := s.readPattern(c); !yes {
return
}
var in struct {
Active bool `json:"active"`
}
if !bind(c, &in) {
return
}
var e error
if in.Active {
_, e = s.DB.Exec(c, `INSERT INTO favorites VALUES($1,$2) ON CONFLICT DO NOTHING`, user(c).ID, c.Param("id"))
} else {
_, e = s.DB.Exec(c, `DELETE FROM favorites WHERE user_id=$1 AND pattern_id=$2`, user(c).ID, c.Param("id"))
}
if e != nil {
fail(c, e)
return
}
ok(c, nil)
}