package chat import ( "context" "log/slog" "github.com/google/uuid" "github.com/yan1h/agent-coding-workflow/internal/audit" "github.com/yan1h/agent-coding-workflow/internal/infra/errs" ) type templateService struct { repo Repository auditor audit.Recorder log *slog.Logger } var _ TemplateService = (*templateService)(nil) // NewTemplateService 构造 prompt_templates CRUD 服务。 func NewTemplateService(repo Repository, a audit.Recorder, log *slog.Logger) TemplateService { return &templateService{repo: repo, auditor: a, log: log} } func (s *templateService) List(ctx context.Context, userID uuid.UUID) ([]PromptTemplate, error) { return s.repo.ListPromptTemplatesForUser(ctx, userID) } func (s *templateService) Create(ctx context.Context, userID uuid.UUID, name, content string, scope TemplateScope) (*PromptTemplate, error) { if scope != TemplateScopeSystem && scope != TemplateScopeUser { return nil, errs.New(errs.CodeInvalidInput, "invalid scope") } params := InsertTemplateParams{ ID: uuid.Must(uuid.NewV7()), Name: name, Content: content, Scope: scope, } if scope == TemplateScopeUser { uid := userID params.OwnerID = &uid } t, err := s.repo.InsertPromptTemplate(ctx, params) if err != nil { return nil, err } if scope == TemplateScopeSystem { s.tryAudit(ctx, audit.Entry{ UserID: &userID, Action: "prompt_template.created", TargetType: "prompt_template", TargetID: t.ID.String(), }) } return t, nil } func (s *templateService) Update(ctx context.Context, userID uuid.UUID, isAdmin bool, id uuid.UUID, name, content string) error { t, err := s.repo.GetPromptTemplate(ctx, id) if err != nil { return err } if t == nil { return errs.New(errs.CodeChatTemplateNotFound, "template not found") } if err := checkTemplateWriteAccess(t, userID, isAdmin); err != nil { return err } if err := s.repo.UpdatePromptTemplate(ctx, id, name, content); err != nil { return err } if t.Scope == TemplateScopeSystem { s.tryAudit(ctx, audit.Entry{ UserID: &userID, Action: "prompt_template.updated", TargetType: "prompt_template", TargetID: id.String(), }) } return nil } func (s *templateService) Delete(ctx context.Context, userID uuid.UUID, isAdmin bool, id uuid.UUID) error { t, err := s.repo.GetPromptTemplate(ctx, id) if err != nil { return err } if t == nil { return errs.New(errs.CodeChatTemplateNotFound, "template not found") } if err := checkTemplateWriteAccess(t, userID, isAdmin); err != nil { return err } if err := s.repo.DeletePromptTemplate(ctx, id); err != nil { return err } if t.Scope == TemplateScopeSystem { s.tryAudit(ctx, audit.Entry{ UserID: &userID, Action: "prompt_template.deleted", TargetType: "prompt_template", TargetID: id.String(), }) } return nil } // checkTemplateWriteAccess 是 Update/Delete 共用的写权限判定: // user 模板仅 owner;system 模板仅 admin。 func checkTemplateWriteAccess(t *PromptTemplate, userID uuid.UUID, isAdmin bool) error { if t.Scope == TemplateScopeUser && (t.OwnerID == nil || *t.OwnerID != userID) { return errs.New(errs.CodeForbidden, "not template owner") } if t.Scope == TemplateScopeSystem && !isAdmin { return errs.New(errs.CodeForbidden, "admin only: system-scoped template") } return nil } func (s *templateService) tryAudit(ctx context.Context, e audit.Entry) { if err := s.auditor.Record(ctx, e); err != nil { s.log.Warn("audit record failed", "action", e.Action, "err", err) } }