thread-master/old/backend/internal/model/knowledge_graph/usecase/usecase.go

211 lines
6.7 KiB
Go
Raw Normal View History

2026-06-26 08:37:04 +00:00
package usecase
import (
"context"
"strings"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
libkg "haixun-backend/internal/library/knowledge"
libmongo "haixun-backend/internal/library/mongo"
"haixun-backend/internal/model/knowledge_graph/domain/entity"
domrepo "haixun-backend/internal/model/knowledge_graph/domain/repository"
domusecase "haixun-backend/internal/model/knowledge_graph/domain/usecase"
)
type knowledgeGraphUseCase struct {
repo domrepo.Repository
}
func NewUseCase(repo domrepo.Repository) domusecase.UseCase {
return &knowledgeGraphUseCase{repo: repo}
}
func (u *knowledgeGraphUseCase) Get(ctx context.Context, tenantID, ownerUID, brandID string) (*domusecase.GraphSummary, error) {
2026-06-28 08:28:42 +00:00
if err := requireScope(tenantID, ownerUID, brandID, "", ""); err != nil {
2026-06-26 08:37:04 +00:00
return nil, err
}
item, err := u.repo.FindByBrand(ctx, tenantID, ownerUID, brandID)
if err != nil {
return nil, err
}
summary := toSummary(item)
return &summary, nil
}
2026-06-28 08:28:42 +00:00
func (u *knowledgeGraphUseCase) GetByCopyMission(ctx context.Context, tenantID, ownerUID, copyMissionID string) (*domusecase.GraphSummary, error) {
if err := requireScope(tenantID, ownerUID, "", "", copyMissionID); err != nil {
return nil, err
}
item, err := u.repo.FindByCopyMission(ctx, tenantID, ownerUID, copyMissionID)
if err != nil {
return nil, err
}
summary := toSummary(item)
return &summary, nil
}
2026-06-26 08:37:04 +00:00
func (u *knowledgeGraphUseCase) GetByTopic(ctx context.Context, tenantID, ownerUID, topicID, brandID string) (*domusecase.GraphSummary, error) {
2026-06-28 08:28:42 +00:00
if err := requireScope(tenantID, ownerUID, "", topicID, ""); err != nil {
2026-06-26 08:37:04 +00:00
return nil, err
}
item, err := u.loadTopicGraph(ctx, tenantID, ownerUID, topicID, brandID)
2026-06-26 08:37:04 +00:00
if err != nil {
return nil, err
}
summary := toSummary(item)
return &summary, nil
}
func (u *knowledgeGraphUseCase) Upsert(ctx context.Context, req domusecase.UpsertRequest) (*domusecase.GraphSummary, error) {
topicID := strings.TrimSpace(req.TopicID)
brandID := strings.TrimSpace(req.BrandID)
2026-06-28 08:28:42 +00:00
copyMissionID := strings.TrimSpace(req.CopyMissionID)
if err := requireScope(req.TenantID, req.OwnerUID, brandID, topicID, copyMissionID); err != nil {
2026-06-26 08:37:04 +00:00
return nil, err
}
seed := strings.TrimSpace(req.Seed)
if seed == "" {
return nil, app.For(code.Brand).InputMissingRequired("seed is required")
}
graph := &entity.Graph{
TenantID: req.TenantID,
OwnerUID: req.OwnerUID,
BrandID: brandID,
TopicID: topicID,
2026-06-28 08:28:42 +00:00
CopyMissionID: copyMissionID,
2026-06-26 08:37:04 +00:00
Seed: seed,
Nodes: req.Nodes,
Edges: req.Edges,
BraveSources: req.BraveSources,
ExpandStrategy: req.ExpandStrategy,
PainTagCount: req.PainTagCount,
GeneratedAt: req.GeneratedAt,
}
var (
item *entity.Graph
err error
)
2026-06-28 08:28:42 +00:00
switch {
case copyMissionID != "":
item, err = u.repo.UpsertByCopyMission(ctx, graph)
case topicID != "":
2026-06-26 08:37:04 +00:00
item, err = u.repo.UpsertByTopic(ctx, graph)
2026-06-28 08:28:42 +00:00
default:
2026-06-26 08:37:04 +00:00
item, err = u.repo.UpsertByBrand(ctx, graph)
}
if err != nil {
return nil, err
}
summary := toSummary(item)
return &summary, nil
}
func (u *knowledgeGraphUseCase) UpdateNodes(ctx context.Context, req domusecase.UpdateNodesRequest) (*domusecase.GraphSummary, error) {
topicID := strings.TrimSpace(req.TopicID)
brandID := strings.TrimSpace(req.BrandID)
2026-06-28 08:28:42 +00:00
copyMissionID := strings.TrimSpace(req.CopyMissionID)
if err := requireScope(req.TenantID, req.OwnerUID, brandID, topicID, copyMissionID); err != nil {
2026-06-26 08:37:04 +00:00
return nil, err
}
if len(req.Updates) == 0 {
return nil, app.For(code.Brand).InputMissingRequired("updates is required")
}
var (
current *entity.Graph
err error
)
2026-06-28 08:28:42 +00:00
switch {
case copyMissionID != "":
current, err = u.repo.FindByCopyMission(ctx, req.TenantID, req.OwnerUID, copyMissionID)
case topicID != "":
current, err = u.loadTopicGraph(ctx, req.TenantID, req.OwnerUID, topicID, brandID)
2026-06-28 08:28:42 +00:00
default:
2026-06-26 08:37:04 +00:00
current, err = u.repo.FindByBrand(ctx, req.TenantID, req.OwnerUID, brandID)
}
if err != nil {
return nil, err
}
changes := map[string]domusecase.NodeUpdate{}
for _, update := range req.Updates {
id := strings.TrimSpace(update.NodeID)
if id == "" {
continue
}
changes[id] = update
}
nodes := make([]libkg.Node, len(current.Nodes))
copy(nodes, current.Nodes)
for i := range nodes {
update, ok := changes[nodes[i].ID]
if !ok {
continue
}
if update.SelectedForScan != nil {
nodes[i].SelectedForScan = *update.SelectedForScan
}
if update.RelevanceTagsSet {
nodes[i].PatrolRelevance = libkg.SanitizePatrolKeywordList(update.RelevanceTags)
}
if update.RecencyTagsSet {
nodes[i].PatrolRecency = libkg.SanitizePatrolKeywordList(update.RecencyTags)
}
if update.RelevanceTagsSet || update.RecencyTagsSet {
nodes[i].DerivedTags = libkg.DerivePatrolTagsForNode(nodes[i], libkg.PatrolTagInput{})
}
}
painCount := libkg.CountPainTagCandidates(nodes)
var item *entity.Graph
2026-06-28 08:28:42 +00:00
switch {
case copyMissionID != "":
item, err = u.repo.UpdateNodesByCopyMission(ctx, req.TenantID, req.OwnerUID, copyMissionID, nodes, painCount)
case topicID != "":
2026-06-26 08:37:04 +00:00
item, err = u.repo.UpdateNodesByTopic(ctx, req.TenantID, req.OwnerUID, topicID, nodes, painCount)
2026-06-28 08:28:42 +00:00
default:
2026-06-26 08:37:04 +00:00
item, err = u.repo.UpdateNodes(ctx, req.TenantID, req.OwnerUID, brandID, nodes, painCount)
}
if err != nil {
return nil, err
}
summary := toSummary(item)
return &summary, nil
}
func (u *knowledgeGraphUseCase) AttachTopicID(ctx context.Context, tenantID, ownerUID, brandID, topicID string) error {
2026-06-28 08:28:42 +00:00
if err := requireScope(tenantID, ownerUID, brandID, topicID, ""); err != nil {
2026-06-26 08:37:04 +00:00
return err
}
return u.repo.AttachTopicID(ctx, tenantID, ownerUID, brandID, topicID)
}
2026-06-28 08:28:42 +00:00
func requireScope(tenantID, ownerUID, brandID, topicID, copyMissionID string) error {
2026-06-26 08:37:04 +00:00
if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
return app.For(code.Brand).InputMissingRequired("tenant_id and uid are required")
}
2026-06-28 08:28:42 +00:00
if strings.TrimSpace(topicID) == "" && strings.TrimSpace(brandID) == "" && strings.TrimSpace(copyMissionID) == "" {
return app.For(code.Brand).InputMissingRequired("brand id, topic id, or copy mission id is required")
2026-06-26 08:37:04 +00:00
}
return nil
}
func toSummary(item *entity.Graph) domusecase.GraphSummary {
if item == nil {
return domusecase.GraphSummary{}
}
return domusecase.GraphSummary{
ID: item.ID,
BrandID: libmongo.ResolveBrandID(item.BrandID, item.LegacyPersonaID),
TopicID: strings.TrimSpace(item.TopicID),
2026-06-28 08:28:42 +00:00
CopyMissionID: strings.TrimSpace(item.CopyMissionID),
2026-06-26 08:37:04 +00:00
Seed: item.Seed,
Nodes: item.Nodes,
Edges: item.Edges,
BraveSources: item.BraveSources,
ExpandStrategy: item.ExpandStrategy,
PainTagCount: item.PainTagCount,
GeneratedAt: item.GeneratedAt,
CreateAt: item.CreateAt,
UpdateAt: item.UpdateAt,
}
}