feat(backend): record tag usage in filters to activity.tag_uses

Listing files with a tag filter now logs each referenced tag to
activity.tag_uses, flagging it included (positive) or excluded (negated
under an odd number of NOTs); the untagged pseudo-token is skipped. The
filter AST is reused to determine polarity, so grouped negations like
!(A|B) mark both tags excluded.

Recording happens only when a filter is first applied — not on cursor
pagination or an anchored return — so one browse counts once. The write
is best-effort and never fails the listing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 21:40:13 +03:00
parent 6a3bb9ff51
commit 73ae8a046f
6 changed files with 266 additions and 15 deletions
+14 -1
View File
@@ -461,7 +461,20 @@ func (s *FileService) Replace(ctx context.Context, id uuid.UUID, p UploadParams)
// files the caller may see (unless they are an admin).
func (s *FileService) List(ctx context.Context, params domain.FileListParams) (*domain.FilePage, error) {
params.ViewerID, params.ViewerIsAdmin, _ = domain.UserFromContext(ctx)
return s.files.List(ctx, params)
page, err := s.files.List(ctx, params)
if err != nil {
return nil, err
}
// Log tag usage when a filter is first applied — not on pagination (cursor)
// or an anchored return, so a single browse counts once. Best-effort
// analytics; a failed write never breaks the listing.
if params.Filter != "" && params.Cursor == "" && params.Anchor == nil && params.ViewerID != 0 {
_ = s.files.RecordTagUses(ctx, params.ViewerID, params.Filter)
}
return page, nil
}
// AuthorizeView ensures the caller may view the file. Returns ErrNotFound if the