Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/<ticket>-<slug>-Branches, Ticket-Prefix in Commit-Nachricht) etabliert.
95 lines
3.7 KiB
Go
95 lines
3.7 KiB
Go
// Package index is the (Phase 1) full-text search sync layer for archivdms.
|
|
//
|
|
// PostgreSQL remains the single source of truth; this package keeps a
|
|
// secondary, per-tenant Manticore Search index (Hybrid BM25+Vektor is a later
|
|
// phase) in sync with the documents table. Only the write/sync half is
|
|
// implemented here — there is deliberately NO search endpoint yet (Phase 2/3).
|
|
//
|
|
// Design guarantees:
|
|
// - The index is best-effort. When Manticore is not configured (empty DSN)
|
|
// the whole thing degrades to a no-op: the Indexer is nil and every caller
|
|
// skips silently.
|
|
// - An index error must NEVER be propagated to the originating HTTP request.
|
|
// Callers log and move on. Postgres stays authoritative, so a stale index
|
|
// is a recoverable, non-fatal condition (a later reindex CLI, Phase 3,
|
|
// rebuilds it).
|
|
//
|
|
// This package intentionally has NO dependency on internal/storage to avoid an
|
|
// import cycle: storage builds DocumentDoc values and calls into here.
|
|
package index
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// DocumentDoc is the index representation of a stored document. It is the
|
|
// projection of a documents row plus its resolved taxonomy (tags) and ACL
|
|
// (visibility group IDs) that the search index needs.
|
|
type DocumentDoc struct {
|
|
ID int64
|
|
TenantID int64
|
|
Title string
|
|
DocType string // deprecated free-text doc_type
|
|
Correspondent string // deprecated free-text correspondent
|
|
OCRText string
|
|
Tags []string
|
|
TagIDs []int64
|
|
DocTypeID *int64
|
|
CorrespondentID *int64
|
|
ACLGroupIDs []int64
|
|
RetainUntil *time.Time
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// SearchQuery is the (Phase 3) full-text + attribute query against a single
|
|
// tenant's index. It intentionally carries only what the index needs to return
|
|
// a ranked list of documents.id values; the caller re-hydrates the full
|
|
// document rows from Postgres (the source of truth) afterwards.
|
|
type SearchQuery struct {
|
|
// Query is the raw user full-text term. It is escaped before it ever
|
|
// reaches a MATCH() expression — callers pass it verbatim.
|
|
Query string
|
|
// TagIDs, when non-empty, restricts hits to documents carrying ANY of
|
|
// these tag ids (MVA filter).
|
|
TagIDs []int64
|
|
// DocTypeID, when non-nil, restricts hits to that document type.
|
|
DocTypeID *int64
|
|
// ACLGroupIDs applies the group-resolved document ACL: when non-nil, only
|
|
// documents visible to ANY of these permission groups are returned. A nil
|
|
// slice means "no ACL filter" (domain_admin/superadmin). An explicitly
|
|
// empty (non-nil) slice would match nothing — callers must short-circuit
|
|
// that case before querying.
|
|
ACLGroupIDs []int64
|
|
// Page is 1-based; PageSize caps hits per page.
|
|
Page int
|
|
PageSize int
|
|
}
|
|
|
|
// SearchHit is a single ranked result: a documents.id plus its BM25 score.
|
|
type SearchHit struct {
|
|
ID int64
|
|
Score float64
|
|
}
|
|
|
|
// Indexer syncs a single (tenant-scoped) document index. Implementations must
|
|
// never block or fail the calling request on transient backend errors beyond
|
|
// returning the error for the caller to log.
|
|
type Indexer interface {
|
|
// IndexSync inserts or replaces the document (id-based upsert).
|
|
IndexSync(ctx context.Context, doc DocumentDoc) error
|
|
// Delete removes the document from the index by its documents.id.
|
|
Delete(ctx context.Context, id int64) error
|
|
// Search runs a full-text + attribute query and returns the ranked hits
|
|
// for the requested page plus the total match count (across all pages).
|
|
Search(ctx context.Context, q SearchQuery) (hits []SearchHit, total int, err error)
|
|
}
|
|
|
|
// TenantIndexer hands out per-tenant Indexer instances, each backed by its own
|
|
// RT table (documents_tenant_<id>).
|
|
type TenantIndexer interface {
|
|
ForTenant(tenantID int64) Indexer
|
|
Close() error
|
|
}
|