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) }