diff --git a/internal/chat/service_template.go b/internal/chat/service_template.go new file mode 100644 index 0000000..9bfa40b --- /dev/null +++ b/internal/chat/service_template.go @@ -0,0 +1,109 @@ +package chat + +import ( + "context" + "log/slog" + "time" + + "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.ListTemplatesForUser(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") + } + t := &PromptTemplate{ + ID: uuid.Must(uuid.NewV7()), + Name: name, + Content: content, + Scope: scope, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if scope == TemplateScopeUser { + uid := userID + t.OwnerID = &uid + } + if err := s.repo.InsertTemplate(ctx, t); 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, id uuid.UUID, name, content string) error { + t, err := s.repo.GetTemplate(ctx, id) + if err != nil { + return err + } + if t == nil { + return errs.New(errs.CodeChatTemplateNotFound, "template not found") + } + if t.Scope == TemplateScopeUser && (t.OwnerID == nil || *t.OwnerID != userID) { + return errs.New(errs.CodeForbidden, "not template owner") + } + if err := s.repo.UpdateTemplate(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, id uuid.UUID) error { + t, err := s.repo.GetTemplate(ctx, id) + if err != nil { + return err + } + if t == nil { + return errs.New(errs.CodeChatTemplateNotFound, "template not found") + } + if t.Scope == TemplateScopeUser && (t.OwnerID == nil || *t.OwnerID != userID) { + return errs.New(errs.CodeForbidden, "not template owner") + } + if err := s.repo.DeleteTemplate(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 +} + +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) + } +}