247 lines
5.1 KiB
Go
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)
|
|
}
|