Files
ttstd-infinite-canvas/backend/internal/skills/skill_curation.go

175 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package skills
import (
"strings"
"unicode/utf8"
"yingce/backend/internal/kernel"
"yingce/backend/internal/model"
"yingce/backend/internal/repository"
)
type SkillCuration = repository.SkillCuration
type SkillCurationAssignmentInput struct {
RootID *string `json:"rootId,omitempty"`
SkillID string `json:"skillId"`
CategoryIDs []string `json:"categoryIds"`
}
type SkillCurationUpdate struct {
Root *model.SkillCurationRoot `json:"root,omitempty"`
ExpectedRevision *int64 `json:"expectedRevision"`
Enabled *bool `json:"enabled,omitempty"`
Category *model.SkillCurationCategory `json:"category,omitempty"`
Assignment *SkillCurationAssignmentInput `json:"assignment,omitempty"`
}
func (s *Service) SkillCuration(actor *model.User, admin bool) (*SkillCuration, error) {
if err := s.curationActor(actor, admin); err != nil {
return nil, err
}
return s.repo.SkillCuration(admin)
}
func (s *Service) curationActor(actor *model.User, admin bool) error {
if actor == nil || actor.ID == "" {
return kernel.Unauthorized("请先登录")
}
if admin {
current, err := s.repo.User(actor.ID)
if err != nil {
return err
}
if current.Role != model.UserRoleAdmin || current.Status != model.UserStatusActive {
return kernel.Forbidden("需要管理员权限")
}
}
return nil
}
func (s *Service) UpdateSkillCuration(actor *model.User, req SkillCurationUpdate, requestID string) (*SkillCuration, error) {
if err := s.curationActor(actor, true); err != nil {
return nil, err
}
count := 0
if req.Root != nil {
count++
}
if req.Enabled != nil {
count++
}
if req.Category != nil {
count++
}
if req.Assignment != nil {
count++
}
if req.ExpectedRevision == nil || *req.ExpectedRevision < 0 || count != 1 {
return nil, kernel.BadAuthRequest("需要版本号和一项修改")
}
err := s.repo.UpdateSkillCuration(actor.ID, *req.ExpectedRevision, requestID, func(repo *repository.Repository) error {
if req.Enabled != nil {
return repo.SetSkillCurationEnabled(*req.Enabled)
}
state, err := repo.SkillCuration(true)
if err != nil {
return err
}
if req.Root != nil {
return saveCurationRoot(repo, state, *req.Root)
}
if req.Category != nil {
c := *req.Category
c.Name = strings.Join(strings.Fields(c.Name), " ")
c.NormalizedName = strings.ToLower(c.Name)
if utf8.RuneCountInString(c.Name) < 1 || utf8.RuneCountInString(c.Name) > 64 || !activeCurationRoot(state, c.RootTag) {
return kernel.BadAuthRequest("分类名称或一级分类无效")
}
exists := c.ID == ""
for _, old := range state.Categories {
if old.ID == c.ID {
exists = true
if old.RootTag != c.RootTag {
return kernel.BadAuthRequest("不能移动子分类")
}
}
if old.ID != c.ID && old.RootTag == c.RootTag && old.NormalizedName == c.NormalizedName {
return kernel.BadAuthRequest("同一级分类下名称不能重复")
}
}
if !exists {
return kernel.NotFound("分类不存在")
}
if c.ID == "" {
c.ID = kernel.NewID()
}
return repo.SaveSkillCurationCategory(&c)
}
a := req.Assignment
if a.CategoryIDs == nil || len(a.CategoryIDs) > 32 {
return kernel.BadAuthRequest("需要完整分类集合,最多32项")
}
skill, err := repo.CurationSkill(a.SkillID)
if err != nil {
return kernel.NotFound("公开技能不存在")
}
rootID := state.EffectiveRoot(skill)
if a.RootID != nil {
if *a.RootID != "" && !activeCurationRoot(state, *a.RootID) {
return kernel.BadAuthRequest("一级分类必须存在且启用")
}
rootID = *a.RootID
if rootID == "" {
rootID = skill.Tag
if !activeCurationRoot(state, rootID) {
rootID = repository.UnassignedSkillRoot
}
}
}
seen := map[string]bool{}
for _, id := range a.CategoryIDs {
valid := false
for _, c := range state.Categories {
if c.ID == id && c.Enabled && c.RootTag == rootID {
valid = true
}
}
if !valid || seen[id] {
return kernel.BadAuthRequest("分类必须启用、同根且不能重复")
}
seen[id] = true
}
if a.RootID != nil {
if err := repo.AssignSkillCurationRoot(a.SkillID, *a.RootID); err != nil {
return err
}
}
return repo.AssignSkillCuration(a.SkillID, a.CategoryIDs)
})
if err != nil {
return nil, err
}
return s.repo.SkillCuration(true)
}
func (s *Service) curationFilter(req SkillListRequest) (string, bool, error) {
if req.PlatformCategoryID == "" && !req.PlatformUncategorized {
return "", false, nil
}
state, err := s.repo.SkillCuration(false)
if err != nil {
return "", false, err
}
if !state.Enabled {
return "", false, nil
}
if req.PlatformCategoryID != "" && req.PlatformUncategorized {
return "", false, kernel.BadAuthRequest("分类和未细分不能同时筛选")
}
if req.PlatformCategoryID != "" {
for _, c := range state.Categories {
if c.ID == req.PlatformCategoryID {
return c.ID, false, nil
}
}
return "", false, kernel.BadAuthRequest("分类已停用或不存在,请重新选择")
}
return "", req.PlatformUncategorized, nil
}