feat(skills): 技能管理 - 支持后台维护一级分类 (#694)
Co-authored-by: ddcat666 <26564566+ddcat666@users.noreply.github.com>
This commit is contained in:
1 parent
fb4af9bd34
commit
fe2b892f1f
29 files changed
+579
-37
No files matched your search
@@ -28,6 +28,9 @@ func (r *Repository) DeleteBuiltinSkill(skillID string, actorID string) error {
|
||||
if err := tx.Delete(&model.SkillCurationAssignment{}, "skill_id = ?", skillID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&model.SkillCurationRootAssignment{}, "skill_id = ?", skillID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "skill_id"}}, DoUpdates: clause.AssignmentColumns([]string{"deleted_by", "deleted_at"})}).Create(&model.BuiltinSkillTombstone{SkillID: skillID, DeletedBy: actorID, DeletedAt: now}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
|
||||
type SkillCuration struct {
|
||||
model.SkillCurationSetting
|
||||
Categories []model.SkillCurationCategory `json:"categories"`
|
||||
Assignments []model.SkillCurationAssignment `json:"assignments"`
|
||||
Roots []model.SkillCurationRoot `json:"roots,omitempty"`
|
||||
RootAssignments []model.SkillCurationRootAssignment `json:"rootAssignments,omitempty"`
|
||||
Categories []model.SkillCurationCategory `json:"categories"`
|
||||
Assignments []model.SkillCurationAssignment `json:"assignments"`
|
||||
}
|
||||
|
||||
// Read the version before and after the snapshot so concurrent edits cannot
|
||||
@@ -25,6 +27,21 @@ func (r *Repository) SkillCuration(admin bool) (*SkillCuration, error) {
|
||||
if !admin && !out.Enabled {
|
||||
return out, nil
|
||||
}
|
||||
roots, err := r.SkillCurationRoots()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, root := range roots {
|
||||
if admin || root.Enabled {
|
||||
out.Roots = append(out.Roots, root)
|
||||
}
|
||||
}
|
||||
if !admin {
|
||||
out.Roots = append(out.Roots, model.SkillCurationRoot{ID: UnassignedSkillRoot, Name: "未归入启用分类", IconKey: "shapes", Enabled: true})
|
||||
}
|
||||
if err := r.db.Model(&model.SkillCurationRootAssignment{}).Select("skill_curation_root_assignments.*").Joins("JOIN skills ON skills.id = skill_curation_root_assignments.skill_id").Where("skills.status = 1 AND skills.is_private = false").Order("skill_id").Find(&out.RootAssignments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := r.db.Order("sort_order, id")
|
||||
if !admin {
|
||||
query = query.Where("enabled = ?", true)
|
||||
@@ -32,10 +49,22 @@ func (r *Repository) SkillCuration(admin bool) (*SkillCuration, error) {
|
||||
if err := query.Find(&out.Categories).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !admin {
|
||||
visible := []model.SkillCurationCategory{}
|
||||
for _, c := range out.Categories {
|
||||
for _, root := range out.Roots {
|
||||
if root.ID == c.RootTag {
|
||||
visible = append(visible, c)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
out.Categories = visible
|
||||
}
|
||||
query = r.db.Model(&model.SkillCurationAssignment{}).Select("skill_curation_assignments.*").
|
||||
Joins("JOIN skills ON skills.id = skill_curation_assignments.skill_id").Where("skills.status = 1 AND skills.is_private = false")
|
||||
if !admin {
|
||||
query = query.Joins("JOIN skill_curation_categories c ON c.id = skill_curation_assignments.category_id").Where("c.enabled = true AND c.root_tag = skills.tag")
|
||||
query = query.Joins("JOIN skill_curation_categories c ON c.id = skill_curation_assignments.category_id").Where("c.enabled = true AND c.root_tag = " + effectiveCurationRoot)
|
||||
}
|
||||
if err := query.Order("skill_id, category_id").Find(&out.Assignments).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -111,4 +140,4 @@ func (r *Repository) AssignSkillCuration(skillID string, ids []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
const effectiveCurationCategory = `SELECT 1 FROM skill_curation_assignments a JOIN skill_curation_categories c ON c.id = a.category_id WHERE a.skill_id = skills.id AND c.enabled = true AND c.root_tag = skills.tag AND skills.is_private = false`
|
||||
const effectiveCurationCategory = `SELECT 1 FROM skill_curation_assignments a JOIN skill_curation_categories c ON c.id = a.category_id WHERE a.skill_id = skills.id AND c.enabled = true AND c.root_tag = ` + effectiveCurationRoot + ` AND skills.is_private = false`
|
||||
@@ -0,0 +1,65 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"infinite-canvas/backend/internal/model"
|
||||
)
|
||||
|
||||
const UnassignedSkillRoot = "__unassigned__"
|
||||
|
||||
func (r *Repository) SkillCurationRoots() ([]model.SkillCurationRoot, error) {
|
||||
rows := []model.SkillCurationRoot{}
|
||||
if err := r.db.Order("sort_order, id").Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return model.DefaultSkillCurationRoots(), nil
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
func (r *Repository) SaveSkillCurationRoot(root *model.SkillCurationRoot) error {
|
||||
// Materialize the fallback before its first edit, keeping existing children
|
||||
// attached to their stable root IDs even after an empty-table fallback.
|
||||
var count int64
|
||||
if err := r.db.Model(&model.SkillCurationRoot{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
rows := model.DefaultSkillCurationRoots()
|
||||
if err := r.db.Create(&rows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return r.db.Save(root).Error
|
||||
}
|
||||
func (r *Repository) AssignSkillCurationRoot(skillID, rootID string) error {
|
||||
if err := r.db.Delete(&model.SkillCurationRootAssignment{}, "skill_id = ?", skillID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if rootID == "" {
|
||||
return nil
|
||||
}
|
||||
return r.db.Create(&model.SkillCurationRootAssignment{SkillID: skillID, RootID: rootID}).Error
|
||||
}
|
||||
|
||||
func (state *SkillCuration) EffectiveRoot(skill *model.Skill) string {
|
||||
id := skill.Tag
|
||||
if !skill.IsPrivate {
|
||||
for _, a := range state.RootAssignments {
|
||||
if a.SkillID == skill.ID {
|
||||
id = a.RootID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, root := range state.Roots {
|
||||
if root.ID == id && root.Enabled {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return UnassignedSkillRoot
|
||||
}
|
||||
|
||||
// Explicit assignments override the source tag. A disabled/missing target is
|
||||
// kept discoverable in the default bucket; it never falls into another root.
|
||||
const curationRootID = `COALESCE((SELECT ra.root_id FROM skill_curation_root_assignments ra WHERE ra.skill_id = skills.id AND skills.is_private = false),skills.tag)`
|
||||
const effectiveCurationRoot = `(CASE WHEN NOT EXISTS (SELECT 1 FROM skill_curation_roots) THEN CASE WHEN ` + curationRootID + ` IN ('drama','ecommerce','creative','social','others') THEN ` + curationRootID + ` ELSE '__unassigned__' END ELSE COALESCE((SELECT r.id FROM skill_curation_roots r WHERE r.enabled = true AND r.id = ` + curationRootID + `), '__unassigned__') END)`
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type SkillListFilter struct {
|
||||
PlatformRootID string
|
||||
PlatformCategoryID string
|
||||
PlatformUncategorized bool
|
||||
UserID string
|
||||
@@ -79,6 +80,9 @@ func (r *Repository) Skills(filter SkillListFilter) ([]model.Skill, int64, error
|
||||
if filter.Tag != "" {
|
||||
query = query.Where("skills.tag = ?", filter.Tag)
|
||||
}
|
||||
if filter.PlatformRootID != "" {
|
||||
query = query.Where(effectiveCurationRoot+" = ?", filter.PlatformRootID)
|
||||
}
|
||||
if filter.PlatformCategoryID != "" {
|
||||
query = query.Where("EXISTS ("+effectiveCurationCategory+" AND c.id = ?)", filter.PlatformCategoryID)
|
||||
}
|
||||
@@ -147,6 +151,9 @@ func (r *Repository) DeleteSkill(id string) error {
|
||||
if err := tx.Delete(&model.SkillCurationAssignment{}, "skill_id = ?", id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&model.SkillCurationRootAssignment{}, "skill_id = ?", id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&model.UserSkillState{}, "skill_id = ?", id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user