Files
archivmail/internal/imap/link_source.go
T
patrickandClaude Sonnet 5.5 4eb300c252 feat(PROJ-94): Postfach-Zuordnung pro IMAP-Konto, imap-link
Tabelle imap_account_mails, Importer schreibt die Zuordnung auch bei Duplikaten,
Zugriffsregel und Index-Tokens nutzen sie, archivmail imap-link befüllt den
Bestand (Trockenlauf Standard, --apply schreibt nur die Zuordnung).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 14:38:27 +01:00

49 lines
1.5 KiB
Go

package imap
import (
"context"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
)
// Same tenant rule as linkAccountMailSQL, evaluated read-only so the dry run
// reports exactly what --apply would write.
const linkLookupSQL = `
SELECT e.message_id, e.id,
(e.tenant_id IS NOT DISTINCT FROM a.tenant_id
OR (a.tenant_id IS NOT NULL AND EXISTS (
SELECT 1 FROM email_refs r WHERE r.email_id = e.id AND r.tenant_id = a.tenant_id))) AS tenant_ok,
EXISTS (SELECT 1 FROM imap_account_mails m WHERE m.account_id = a.id AND m.mail_id = e.id) AS linked
FROM emails e
CROSS JOIN imap_accounts a
WHERE a.id = $1 AND e.message_id = ANY($2)
`
func NewLinkLookup(pool *pgxpool.Pool) LinkLookup {
return func(ctx context.Context, accountID int64, ids []string) (map[string]LinkCandidate, error) {
out := make(map[string]LinkCandidate, len(ids))
for i := 0; i < len(ids); i += verifyLookupBatch {
end := min(i+verifyLookupBatch, len(ids))
rows, err := pool.Query(ctx, linkLookupSQL, accountID, ids[i:end])
if err != nil {
return nil, fmt.Errorf("query emails: %w", err)
}
for rows.Next() {
var msgID string
var c LinkCandidate
if err := rows.Scan(&msgID, &c.MailID, &c.TenantOK, &c.Linked); err != nil {
rows.Close()
return nil, fmt.Errorf("scan link candidate: %w", err)
}
out[msgID] = c
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("rows: %w", err)
}
}
return out, nil
}
}