Files
nexarch/mail/internal/search/fields.go
T
sysopsandClaude Sonnet 5 db73aab0de SRC-05: facetten-filter-api
Facetten- und Filter-API für Suche (Absender, Zeitraum, Postfach,
Anhangstyp, Tag), mandantengetrennt, mit UND-Verknüpfung mehrerer Filter.

- migrations/0002..0005: vier nummerierte ALTER-Migrationen für die neuen
  Facettenfelder (Manticore erlaubt nur eine Spalte je ALTER-Anweisung),
  EnsureSchema wendet sie idempotent nach.
- fields.go: FacetFields-Whitelist, einzige zulässige Facettendimensionen.
- facets.go: Client.Facets nutzt Manticores strukturierte aggs.terms/
  aggs.range-API, kein dynamischer SQL-Klauselbau. Filter kombinieren als
  zusätzliche equals-Klauseln in derselben bool.must-Liste wie der
  Tenant-Filter. Zeitraum-Facette über feste Buckets via aggs.range.

Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-05-PRUEFPROTOKOLL.md):
1. TestFacets_CountsMatchActualHits: Facettenzahlen stimmen real mit der
   tatsächlichen Treffermenge überein.
2. TestFacets_ThreeFiltersCombineWithAND: 3 kombinierte Filter schränken
   4 Dokumente real auf genau 1 verbleibenden Treffer ein.
3. TestFacets_TenantSeparation: Facetten eines Mandanten enthalten real
   keine Werte eines anderen.

Kein Umbau: Search/Delete/Index-Verhalten aus SRC-01/SRC-03 unverändert,
dedup/indexworker/storage/crypto/encstorage unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 10:49:52 +02:00

54 lines
2.4 KiB
Go

// Package search implementiert SRC-01: den Manticore-RT-Suchindex für
// Mail-Inhalte. Bekannter Fehler vermeiden (siehe known-issues-archivmail.md
// #11/#12): archivmail baute WHERE-Klauseln und teils Spalten-/Tabellennamen
// dynamisch über fmt.Sprintf/strings.Join zusammen. Für dieses Paket gilt
// verbindlich: Spalten- und Tabellennamen kommen AUSSCHLIESSLICH aus den
// Konstanten dieser Datei, nirgendwo sonst im Paket wird ein Feld- oder
// Tabellenname zur Laufzeit zusammengesetzt. Suchanfragen laufen über die
// strukturierte Manticore-HTTP-JSON-API (Query/Insert-Sub, kein
// String-Zusammenbau von SQL), nicht über die SQL-Schnittstelle.
package search
import "hash/fnv"
// IndexName ist der einzige Ort, an dem der Manticore-Indexname als
// Literal steht.
const IndexName = "mail_documents"
// Statische Feld-Whitelist des mail_documents-Index (muss deckungsgleich
// mit migrations/0001_mail_documents.sql bleiben).
const (
FieldTenantSlug = "tenant_slug"
FieldMessageID = "message_id"
FieldSubject = "subject"
FieldBody = "body"
FieldAttachmentText = "attachment_text"
FieldSentAt = "sent_at"
// Facettenfelder (SRC-05), nachgezogen über migrations/0002..0005.
FieldSender = "sender"
FieldMailbox = "mailbox"
FieldAttachmentType = "attachment_type"
FieldTag = "tag"
)
// FacetFields sind die je Kachel unterstützten Filterdimensionen
// (Akzeptanzkriterium 1: Absender, Postfach, Anhangstyp, Tag — Zeitraum
// läuft separat über FieldSentAt als Bereichsfacette, siehe facets.go).
// Statische Liste — Aufrufer können ausschließlich diese Feldnamen als
// Facetten-/Filterdimension angeben, kein beliebiger Client-Feldname.
var FacetFields = []string{FieldSender, FieldMailbox, FieldAttachmentType, FieldTag}
// DocumentID berechnet deterministisch die Manticore-Dokument-ID aus
// Mandant und Message-ID (FNV-1a, 64 Bit). Deterministisch statt einer
// separat vergebenen ID, damit Re-Indexierung (Index) und Löschung
// (Delete) für dieselbe Mail immer dieselbe Dokument-ID referenzieren,
// ohne dass der Aufrufer sie zwischenspeichern muss (SRC-02: Löschungen
// müssen ohne zusätzlichen Zustand nachgezogen werden können).
func DocumentID(tenantSlug, messageID string) uint64 {
h := fnv.New64a()
_, _ = h.Write([]byte(tenantSlug))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(messageID))
return h.Sum64()
}