FDN-01: repository & projektgerüst
Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/<ticket>-<slug>-Branches, Ticket-Prefix in Commit-Nachricht) etabliert.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user