feat(PROJ-30): Xapian → Manticore Search Migration

- internal/index/manticore.go: ManticoreTenantManager + manticoreIndex (RT-Indizes, CGO-frei)
- internal/index/index.go: TenantIndexer Interface (Xapian + Manticore)
- internal/index/tenant_worker.go: mgr-Typ auf TenantIndexer Interface
- internal/api/server.go: idxMgr auf TenantIndexer Interface
- config/config.go: IndexConfig.ManticoreDSN Feld
- cmd/archivmail/cmd_reindex.go: reindex Subkommando
- cmd/archivmail/main.go: Manticore-Branch + reindex Case
- go.mod: github.com/go-sql-driver/mysql v1.8.1
- update.sh: Manticore auto-install, CGO_ENABLED=0, config.yml migration, auto-reindex

fix(IMAP): TCP-Deadline-Wrapper für steckengebliebene Imports
fix(auth): Email-Claim in JWT für User-Isolation
fix(search): User-Isolation via sess.Email (fail-safe)
fix(ui): Admin-Login Auth-Cache, Logout-Redirect, IMAP-Polling-Resilienz

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-04-03 21:19:36 +02:00
parent e90d588e30
commit a93a843506
19 changed files with 742 additions and 65 deletions
+7 -3
View File
@@ -40,6 +40,9 @@ type Account struct {
SyncRunning bool `json:"sync_running"`
SyncStatus string `json:"sync_status"`
SyncErrorMsg string `json:"sync_error_msg"`
// Tenant assignment — mails imported from this account are tagged with this tenant.
TenantID *int64 `json:"tenant_id,omitempty"`
}
// Store manages IMAP account persistence in PostgreSQL.
@@ -71,7 +74,7 @@ CREATE TABLE IF NOT EXISTS imap_accounts (
CREATE INDEX IF NOT EXISTS idx_imap_accounts_owner ON imap_accounts (owner);
`
// migrationSQL adds the PROJ-8 sync columns if they do not yet exist.
// migrationSQL adds columns that may not exist in older installations.
const migrationSQL = `
ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS sync_interval_min INTEGER NOT NULL DEFAULT 0;
ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS last_sync_at TIMESTAMPTZ;
@@ -80,6 +83,7 @@ ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS last_uid BIGINT NOT NULL DEFA
ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS sync_running BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS sync_status TEXT NOT NULL DEFAULT '';
ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS sync_error_msg TEXT NOT NULL DEFAULT '';
ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS tenant_id INTEGER REFERENCES tenants(id);
`
// New creates a new Store, connects to PostgreSQL, and runs the migration.
@@ -138,7 +142,7 @@ const selectColumns = ` id, owner, name, host, port, tls, username, excluded_fol
status, error_msg, last_import_at, last_import_count,
progress_current, progress_total, created_at,
sync_interval_min, last_sync_at, last_sync_count, last_uid,
sync_running, sync_status, sync_error_msg `
sync_running, sync_status, sync_error_msg, tenant_id `
// scanner abstracts pgx.Row and pgx.Rows — both expose Scan(...any) error.
type scanner interface {
@@ -152,7 +156,7 @@ func scanRow(row scanner) (Account, error) {
&a.ExcludedFolders, &a.Status, &a.ErrorMsg, &a.LastImportAt,
&a.LastImportCount, &a.ProgressCurrent, &a.ProgressTotal, &a.CreatedAt,
&a.SyncIntervalMin, &a.LastSyncAt, &a.LastSyncCount, &a.LastUID,
&a.SyncRunning, &a.SyncStatus, &a.SyncErrorMsg,
&a.SyncRunning, &a.SyncStatus, &a.SyncErrorMsg, &a.TenantID,
)
return a, err
}