feat(PROJ-34): Retention-Tab + pro-Mandant Aufbewahrungsfristen

- tenantstore: retention_days Spalte, GetRetentionDays/SetRetentionDays
- storage.Save(): per-tenant retention überschreibt globale config
- API: GET /api/admin/retention, PUT /api/admin/tenant/{id}/retention
- Frontend: RetentionTab mit globaler Policy-Anzeige, Mandanten-Tabelle,
  Bearbeiten-Dialog und Purge-Button (superadmin only)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-31 10:37:15 +02:00
parent 5f0c7a7e6d
commit 5bbf6d0ff3
8 changed files with 399 additions and 16 deletions
+10 -3
View File
@@ -324,9 +324,16 @@ func (s *Store) Save(ctx context.Context, raw []byte, _ time.Time, tenantID *int
} else {
s.insertMetaMinimal(ctx, id, len(raw), tenantID)
}
// PROJ-34: Set retention lock if configured
if s.retentionDays > 0 {
until := time.Now().AddDate(0, 0, s.retentionDays)
// PROJ-34: Set retention lock — prefer per-tenant retention, fall back to global.
effectiveRetention := s.retentionDays
if tenantID != nil {
var tenantDays int
if err := s.db.QueryRow(ctx, `SELECT retention_days FROM tenants WHERE id=$1`, *tenantID).Scan(&tenantDays); err == nil && tenantDays > 0 {
effectiveRetention = tenantDays
}
}
if effectiveRetention > 0 {
until := time.Now().AddDate(0, 0, effectiveRetention)
_, _ = s.db.Exec(ctx, `UPDATE emails SET retain_until=$1 WHERE id=$2 AND retain_until IS NULL`, until, id)
}
}