fix(PROJ-86): Manticore-Index-Drift (emails_global) + Datumssortierung bei fehlendem Date-Header

emails_global bekam nie Tenant-Mails gespiegelt, Superadmin-Suche sah nur
~30% aller Mails. Zusätzlich sanken Mails ohne gültigen Date-Header
(date_ts=0) beim datumssortierten Listing ans Ende aller Ergebnisse und
waren dadurch praktisch unauffindbar ("GUI zeigt neue Mails nicht") -
Import/Indexierung liefen technisch korrekt, nur Sortierung/Spiegelung
waren kaputt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFC6Jk2ke1Pq9XcuVGP1R
This commit is contained in:
sysops
2026-09-01 12:17:09 +02:00
co-authored by Claude Sonnet 5
parent 7727ad5cdf
commit 26c0e04c75
16 changed files with 109 additions and 18 deletions
+13 -4
View File
@@ -1079,8 +1079,9 @@ func (s *Store) IsIndexed(ctx context.Context, id string) (bool, error) {
// UnindexedMail describes one mail awaiting full-text indexing. TenantID is
// nil when the mail has no tenant assignment (system-level / global).
type UnindexedMail struct {
ID string
TenantID *int64
ID string
TenantID *int64
ReceivedAt time.Time
}
// GetUnindexedMails returns up to limit mails with indexed_at IS NULL, newest
@@ -1090,7 +1091,7 @@ func (s *Store) GetUnindexedMails(ctx context.Context, limit int) ([]UnindexedMa
if s.db == nil {
return nil, nil
}
q := `SELECT id, tenant_id FROM emails WHERE indexed_at IS NULL ORDER BY received_at DESC`
q := `SELECT id, tenant_id, received_at FROM emails WHERE indexed_at IS NULL ORDER BY received_at DESC`
args := []interface{}{}
if limit > 0 {
q += " LIMIT $1"
@@ -1104,7 +1105,7 @@ func (s *Store) GetUnindexedMails(ctx context.Context, limit int) ([]UnindexedMa
var out []UnindexedMail
for rows.Next() {
var m UnindexedMail
if err := rows.Scan(&m.ID, &m.TenantID); err != nil {
if err := rows.Scan(&m.ID, &m.TenantID, &m.ReceivedAt); err != nil {
continue
}
out = append(out, m)
@@ -1112,6 +1113,14 @@ func (s *Store) GetUnindexedMails(ctx context.Context, limit int) ([]UnindexedMa
return out, rows.Err()
}
// GetReceivedAt returns the received_at timestamp for a single mail. Used as
// the date-sort fallback when a mail's own Date: header is missing/unparseable.
func (s *Store) GetReceivedAt(ctx context.Context, id string) (time.Time, error) {
var t time.Time
err := s.db.QueryRow(ctx, `SELECT received_at FROM emails WHERE id = $1`, id).Scan(&t)
return t, err
}
// ── Backfill ──────────────────────────────────────────────────────────────
// Backfill walks the store directory, parses each email, inserts missing DB