feat(PROJ-33): IMAP UID-Stabilität + Shared/Personal-Modus

Backend:
- storage: uid BIGSERIAL Migration, MailWithUID, GetMailsWithUID, GetMailsByRecipient
- tenantstore: imap_mode Spalte, GetIMAPMode, SetIMAPMode
- imapserver: stable UIDs aus DB, personal/shared Modus, userEmail in session
- api: GET/PUT /api/admin/settings/imap-mode (domain_admin only, double opt-in)

Frontend:
- IMAPSettingsTab: Modus-Anzeige + Toggle mit Double-Opt-In Dialog
- Admin-Panel: IMAP-Tab für domain_admin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-31 09:46:52 +02:00
parent b6856af2eb
commit 8d0f685fc9
9 changed files with 425 additions and 53 deletions
+22
View File
@@ -74,6 +74,7 @@ ALTER TABLE users ADD COLUMN IF NOT EXISTS tenant_id BIGINT REFERENCES tenants(i
ALTER TABLE audit_log ADD COLUMN IF NOT EXISTS tenant_id BIGINT REFERENCES tenants(id);
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS logo_data BYTEA;
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS logo_content_type VARCHAR(100) NOT NULL DEFAULT '';
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS imap_mode TEXT NOT NULL DEFAULT 'personal';
`
// New connects to PostgreSQL and initialises the tenant schema.
@@ -326,3 +327,24 @@ func (s *Store) getDomain(ctx context.Context, id int64) (*TenantDomain, error)
}
return &d, nil
}
// ── IMAP Mode ─────────────────────────────────────────────────────────────
// GetIMAPMode returns the imap_mode for a tenant ("personal" or "shared").
func (s *Store) GetIMAPMode(ctx context.Context, tenantID int64) (string, error) {
var mode string
err := s.pool.QueryRow(ctx, `SELECT imap_mode FROM tenants WHERE id = $1`, tenantID).Scan(&mode)
if err != nil {
return "personal", nil // safe default
}
return mode, nil
}
// SetIMAPMode updates the imap_mode for a tenant. Valid values: "personal", "shared".
func (s *Store) SetIMAPMode(ctx context.Context, tenantID int64, mode string) error {
if mode != "personal" && mode != "shared" {
return fmt.Errorf("tenantstore: invalid imap_mode %q", mode)
}
_, err := s.pool.Exec(ctx, `UPDATE tenants SET imap_mode = $1 WHERE id = $2`, mode, tenantID)
return err
}