feat(PROJ-8): Automatischer IMAP-Sync (Cron-Scheduler)

Backend:
- internal/imap/store.go: 7 neue Felder (sync_interval_min, last_sync_at,
  last_sync_count, last_uid, sync_running, sync_status, sync_error_msg)
  DB-Migration via ALTER TABLE ADD COLUMN IF NOT EXISTS
  Neue Methoden: ListAll, UpdateSyncInterval, SetSyncRunning, UpdateSyncResult
- internal/imap/scheduler.go: Scheduler mit time.Ticker (1 min),
  inkrementeller Sync via UID SEARCH UID <lastUID+1>:*,
  exponential backoff (3 Versuche: 1s / 60s / 300s),
  sync_running-Flag verhindert parallele Syncs
- internal/api/server.go: POST /api/imap/{id}/sync (manueller Trigger),
  PATCH /api/imap/{id} (sync_interval_min setzen, 0 oder 5-1440 min)
- cmd/archivmail/main.go: Scheduler gestartet + via SetImap verdrahtet

Frontend:
- src/lib/api.ts: 6 neue ImapAccount-Felder, triggerImapSync, updateImapInterval
- src/app/imap/page.tsx: Intervall-Dropdown, "Sync jetzt"-Button,
  Letzter-Sync-Anzeige mit Status-Badge, Polling auch bei sync_running

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-17 02:17:44 +01:00
parent 9cc540a880
commit 988c37d85d
9 changed files with 762 additions and 52 deletions
+5 -2
View File
@@ -154,7 +154,7 @@ func main() {
// Wire SMTP daemon into API server for status endpoint
srv.SetSMTPDaemon(smtpDaemon)
// IMAP store + importer (wired to use async worker)
// IMAP store + importer + scheduler (wired to use async worker)
imapSt, err := imapstore.New(cfg.Database.DSN(), cfg.API.Secret)
if err != nil {
logger.Error("imap store init failed", "err", err)
@@ -162,7 +162,10 @@ func main() {
}
defer imapSt.Close()
imapImp := imapstore.NewImporter(imapSt, mailStore, idx, logger)
srv.SetImap(imapSt, imapImp)
imapSched := imapstore.NewScheduler(imapSt, imapImp, logger)
imapSched.Start()
defer imapSched.Stop()
srv.SetImap(imapSt, imapImp, imapSched)
// Backfill in background: migrate existing files into DB metadata + re-index
go runBackfill(context.Background(), mailStore, idx, worker, logger)