Files
goclaw/internal/store/pg/team_task_siblings.go
viettranx 3b4004946e chore: apply go fix syntax upgrades (min/max, switch patterns)
Apply Go 1.21+ min/max builtins and improved switch pattern matching
across test files and utilities. No functional changes.
2026-04-15 11:24:57 +07:00

104 lines
2.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package pg
import (
"context"
"fmt"
"strings"
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
// BatchGetTaskSiblingsByBasenames returns vault docs attached to the same
// team task as each input basename. Result is keyed by source basename.
//
// Caps at `limit` per (source_base × task_id) pair — a file attached to two
// different tasks gets up to `limit` siblings from each task (not shared).
//
// Tenant isolation via tta.tenant_id filter; team isolation via matching
// vd.team_id = tta.team_id in the join.
//
// Single SQL per chunk — `ANY($2)` lets us avoid 1 query per basename.
func (s *PGTeamStore) BatchGetTaskSiblingsByBasenames(
ctx context.Context,
tenantID uuid.UUID,
basenames []string,
limit int,
) (map[string][]store.TaskSibling, error) {
if len(basenames) == 0 {
return nil, nil
}
if limit <= 0 {
limit = 9
}
// Lowercase + dedup defensively.
seen := make(map[string]bool, len(basenames))
clean := make([]string, 0, len(basenames))
for _, b := range basenames {
lb := strings.ToLower(b)
if lb == "" || seen[lb] {
continue
}
seen[lb] = true
clean = append(clean, lb)
}
out := make(map[string][]store.TaskSibling, len(clean))
const chunkSize = 500
for start := 0; start < len(clean); start += chunkSize {
end := min(start+chunkSize, len(clean))
chunk := clean[start:end]
const q = `
WITH target_tasks AS (
SELECT DISTINCT tta.base_name AS source_base, tta.task_id
FROM team_task_attachments tta
WHERE tta.tenant_id = $1 AND tta.base_name = ANY($2)
),
ranked AS (
SELECT
tt.source_base,
tta2.task_id,
vd.id AS doc_id,
tta2.base_name,
tta2.created_at,
ROW_NUMBER() OVER (
PARTITION BY tt.source_base, tta2.task_id
ORDER BY tta2.created_at DESC, vd.id DESC
) AS rn
FROM target_tasks tt
JOIN team_task_attachments tta2 ON tta2.task_id = tt.task_id
JOIN vault_documents vd
ON vd.tenant_id = tta2.tenant_id
AND vd.team_id = tta2.team_id
AND vd.path_basename = tta2.base_name
WHERE tta2.base_name != tt.source_base
)
SELECT source_base, task_id, doc_id, base_name, created_at
FROM ranked
WHERE rn <= $3
ORDER BY source_base, task_id, created_at DESC
`
rows, err := s.db.QueryContext(ctx, q, tenantID, pqStringArray(chunk), limit)
if err != nil {
return nil, fmt.Errorf("batch task siblings: %w", err)
}
for rows.Next() {
var sourceBase string
var sib store.TaskSibling
if err := rows.Scan(&sourceBase, &sib.TaskID, &sib.DocID, &sib.BaseName, &sib.AttachmentTime); err != nil {
rows.Close()
return nil, err
}
out[sourceBase] = append(out[sourceBase], sib)
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
}
return out, nil
}