fix(backend): apply auto-tag rule to existing files on creation

CreateRule accepted apply_to_existing but ignored it, so enabling the
checkbox while creating a rule never retroactively tagged files already
carrying the when-tag — only activating an existing rule did. Extract the
retroactive expansion into TagRuleRepo.ApplyToExisting (reused by SetActive)
and call it from CreateRule when the rule is active, inside one transaction
so a file is never left half-tagged. Mirrors SetRuleActive semantics,
including following only active downstream rules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 18:09:31 +03:00
parent 9937984a5a
commit 57192a49f9
4 changed files with 126 additions and 13 deletions
+25 -9
View File
@@ -192,16 +192,32 @@ func (s *TagService) ListRules(ctx context.Context, tagID uuid.UUID) ([]domain.T
return s.rules.ListByTag(ctx, tagID)
}
// CreateRule adds a tag rule. If applyToExisting is true, the then_tag is
// retroactively applied to all files that already carry the when_tag.
// Retroactive application requires a FileRepo; it is deferred until wired
// in a future iteration (see port.FileRepo.ListByTag).
func (s *TagService) CreateRule(ctx context.Context, whenTagID, thenTagID uuid.UUID, isActive, _ bool) (*domain.TagRule, error) {
return s.rules.Create(ctx, domain.TagRule{
WhenTagID: whenTagID,
ThenTagID: thenTagID,
IsActive: isActive,
// CreateRule adds a tag rule. When the rule is active and applyToExisting is
// true, the full transitive expansion of thenTagID is retroactively applied to
// every file already carrying whenTagID — same semantics as activating an
// existing rule via SetRuleActive. The insert and retroactive apply run in one
// transaction so a file is never left half-tagged.
func (s *TagService) CreateRule(ctx context.Context, whenTagID, thenTagID uuid.UUID, isActive, applyToExisting bool) (*domain.TagRule, error) {
var created *domain.TagRule
err := s.tx.WithTx(ctx, func(ctx context.Context) error {
rule, err := s.rules.Create(ctx, domain.TagRule{
WhenTagID: whenTagID,
ThenTagID: thenTagID,
IsActive: isActive,
})
if err != nil {
return err
}
created = rule
if isActive && applyToExisting {
return s.rules.ApplyToExisting(ctx, whenTagID, thenTagID)
}
return nil
})
if err != nil {
return nil, err
}
return created, nil
}
// SetRuleActive toggles a rule's is_active flag and returns the updated rule.