feat(PROJ-56d): archivmail import um --tenant Flag erweitert

archivmail import --dir/--file ordnete importierte Mails bisher immer
ohne Mandant zu (Save(..., nil) fest verdrahtet). Neuer --tenant <id> Flag
übergibt die Tenant-ID an Store.Save() und an idxMgr.ForTenant(), sodass
Mail sowohl in der DB als auch im richtigen Tenant-Suchindex landet —
und automatisch die Retention-Regeln des Tenants greifen (applyRetention
nutzt bereits die übergebene tenantID). Relevant für den geplanten
Watch-Folder-Import pro Mandant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-06-24 17:03:15 +02:00
co-authored by Claude Sonnet 4.6
parent b3ff8e6cf9
commit 69c120a268
2 changed files with 18 additions and 6 deletions
+16 -4
View File
@@ -33,6 +33,7 @@ func runImport(args []string) {
recursive := fs.Bool("recursive", false, "recurse into subdirectories (with --dir)") recursive := fs.Bool("recursive", false, "recurse into subdirectories (with --dir)")
dryRun := fs.Bool("dry-run", false, "simulate import without saving") dryRun := fs.Bool("dry-run", false, "simulate import without saving")
jsonOut := fs.Bool("json", false, "machine-readable JSON output") jsonOut := fs.Bool("json", false, "machine-readable JSON output")
tenantFlag := fs.Int64("tenant", 0, "tenant ID to assign imported mails to (0 = none/global)")
fs.Usage = func() { fs.Usage = func() {
fmt.Fprintln(os.Stderr, "Usage: archivmail import [flags]") fmt.Fprintln(os.Stderr, "Usage: archivmail import [flags]")
@@ -161,6 +162,12 @@ func runImport(args []string) {
} }
} }
var tenantID *int64
if *tenantFlag > 0 {
tid := *tenantFlag
tenantID = &tid
}
imported := 0 imported := 0
skipped := 0 skipped := 0
errors := 0 errors := 0
@@ -186,7 +193,7 @@ func runImport(args []string) {
for _, msg := range messages { for _, msg := range messages {
total++ total++
result := importMessage(mailStore, idxMgr, msg, *dryRun) result := importMessage(mailStore, idxMgr, msg, *dryRun, tenantID)
switch result { switch result {
case "imported": case "imported":
imported++ imported++
@@ -226,7 +233,10 @@ func runImport(args []string) {
} }
// importMessage stores and indexes a single raw message. Returns "imported", "skipped", or "error". // importMessage stores and indexes a single raw message. Returns "imported", "skipped", or "error".
func importMessage(mailStore *storage.Store, idxMgr index.TenantIndexer, raw []byte, dryRun bool) string { // tenantID, when non-nil, assigns the imported mail to that tenant (both in
// storage and in the tenant-specific search index) instead of the
// global/tenant-less context.
func importMessage(mailStore *storage.Store, idxMgr index.TenantIndexer, raw []byte, dryRun bool, tenantID *int64) string {
pm, err := mailparser.Parse(raw) pm, err := mailparser.Parse(raw)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "warning: parse failed: %v\n", err) fmt.Fprintf(os.Stderr, "warning: parse failed: %v\n", err)
@@ -237,7 +247,7 @@ func importMessage(mailStore *storage.Store, idxMgr index.TenantIndexer, raw []b
return "imported" return "imported"
} }
id, err := mailStore.Save(context.Background(), raw, pm.Date, nil) id, err := mailStore.Save(context.Background(), raw, pm.Date, tenantID)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "warning: save failed: %v\n", err) fmt.Fprintf(os.Stderr, "warning: save failed: %v\n", err)
return "error" return "error"
@@ -259,9 +269,10 @@ func importMessage(mailStore *storage.Store, idxMgr index.TenantIndexer, raw []b
HasAttachment: len(pm.Attachments) > 0, HasAttachment: len(pm.Attachments) > 0,
Date: pm.Date, Date: pm.Date,
Size: int64(len(raw)), Size: int64(len(raw)),
TenantID: tenantID,
} }
if err := idxMgr.ForTenant(nil).IndexSync(doc); err != nil { if err := idxMgr.ForTenant(tenantID).IndexSync(doc); err != nil {
fmt.Fprintf(os.Stderr, "warning: index failed for %s: %v\n", id, err) fmt.Fprintf(os.Stderr, "warning: index failed for %s: %v\n", id, err)
return "error" return "error"
} }
@@ -299,6 +310,7 @@ archivmail import [flags]
--file Einzelne EML- oder MBOX-Datei --file Einzelne EML- oder MBOX-Datei
--dir Verzeichnis mit EML/MBOX-Dateien --dir Verzeichnis mit EML/MBOX-Dateien
--recursive Unterverzeichnisse einschließen (mit --dir) --recursive Unterverzeichnisse einschließen (mit --dir)
--tenant Mandanten-ID für importierte Mails (0 = keine/global)
--dry-run Simulation ohne Speichern --dry-run Simulation ohne Speichern
--json Maschinenlesbare JSON-Ausgabe --json Maschinenlesbare JSON-Ausgabe
+2 -2
View File
@@ -242,7 +242,7 @@ func runPilerexportMethod(mailStore *storage.Store, idx index.TenantIndexer, bin
return nil return nil
} }
total++ total++
result := importMessage(mailStore, idx, raw, dryRun) result := importMessage(mailStore, idx, raw, dryRun, nil)
switch result { switch result {
case "imported": case "imported":
imported++ imported++
@@ -312,7 +312,7 @@ func runDirectMethod(mailStore *storage.Store, idx index.TenantIndexer, storeDir
} }
total++ total++
result := importMessage(mailStore, idx, emailRaw, dryRun) result := importMessage(mailStore, idx, emailRaw, dryRun, nil)
switch result { switch result {
case "imported": case "imported":
imported++ imported++