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)")
dryRun := fs.Bool("dry-run", false, "simulate import without saving")
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() {
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
skipped := 0
errors := 0
@@ -186,7 +193,7 @@ func runImport(args []string) {
for _, msg := range messages {
total++
result := importMessage(mailStore, idxMgr, msg, *dryRun)
result := importMessage(mailStore, idxMgr, msg, *dryRun, tenantID)
switch result {
case "imported":
imported++
@@ -226,7 +233,10 @@ func runImport(args []string) {
}
// 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)
if err != nil {
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"
}
id, err := mailStore.Save(context.Background(), raw, pm.Date, nil)
id, err := mailStore.Save(context.Background(), raw, pm.Date, tenantID)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: save failed: %v\n", err)
return "error"
@@ -259,9 +269,10 @@ func importMessage(mailStore *storage.Store, idxMgr index.TenantIndexer, raw []b
HasAttachment: len(pm.Attachments) > 0,
Date: pm.Date,
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)
return "error"
}
@@ -299,6 +310,7 @@ archivmail import [flags]
--file Einzelne EML- oder MBOX-Datei
--dir Verzeichnis mit EML/MBOX-Dateien
--recursive Unterverzeichnisse einschließen (mit --dir)
--tenant Mandanten-ID für importierte Mails (0 = keine/global)
--dry-run Simulation ohne Speichern
--json Maschinenlesbare JSON-Ausgabe
+2 -2
View File
@@ -242,7 +242,7 @@ func runPilerexportMethod(mailStore *storage.Store, idx index.TenantIndexer, bin
return nil
}
total++
result := importMessage(mailStore, idx, raw, dryRun)
result := importMessage(mailStore, idx, raw, dryRun, nil)
switch result {
case "imported":
imported++
@@ -312,7 +312,7 @@ func runDirectMethod(mailStore *storage.Store, idx index.TenantIndexer, storeDir
}
total++
result := importMessage(mailStore, idx, emailRaw, dryRun)
result := importMessage(mailStore, idx, emailRaw, dryRun, nil)
switch result {
case "imported":
imported++