feat(PROJ-51): Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien)
Fuehrt archiving_rules ein (PROJ-43-Basis: Tabelle + CRUD-API + Admin-UI) und erweitert die Retention-Logik (PROJ-34) um Regel-basierte Fristen, eine globale Mindestfrist (min_retention_days) sowie Nachvollziehbarkeit der Frist-Quelle (retain_until_source) in API und Mail-Detailansicht.
This commit is contained in:
+11
-16
@@ -32,6 +32,7 @@ type Config struct {
|
||||
Keyfile string // path to 32-byte AES key file; empty = no encryption
|
||||
DSN string // PostgreSQL DSN; empty = no DB
|
||||
RetentionDays int // 0 = no lock; >0 = GoBD retention period in days
|
||||
MinRetentionDays int // PROJ-51: global minimum retention floor (0 = none)
|
||||
CompressEnabled bool // gzip-compress emails and attachments before encryption
|
||||
}
|
||||
|
||||
@@ -42,6 +43,7 @@ type Store struct {
|
||||
key []byte // nil = no encryption
|
||||
db *pgxpool.Pool // nil = no DB
|
||||
retentionDays int // 0 = no lock
|
||||
minRetentionDays int // PROJ-51: global minimum retention floor (0 = none)
|
||||
compressEnabled bool // gzip before encryption
|
||||
}
|
||||
|
||||
@@ -72,7 +74,7 @@ func New(cfg Config) (*Store, error) {
|
||||
}
|
||||
}
|
||||
|
||||
s := &Store{dir: cfg.Dir, retentionDays: cfg.RetentionDays, compressEnabled: cfg.CompressEnabled}
|
||||
s := &Store{dir: cfg.Dir, retentionDays: cfg.RetentionDays, minRetentionDays: cfg.MinRetentionDays, compressEnabled: cfg.CompressEnabled}
|
||||
|
||||
// Load encryption key
|
||||
if err := s.loadKey(cfg.Keyfile); err != nil {
|
||||
@@ -103,6 +105,8 @@ func New(cfg Config) (*Store, error) {
|
||||
_, _ = s.db.Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_emails_uid ON emails (uid)`)
|
||||
// 2.0: storage_objects FK on emails
|
||||
_, _ = s.db.Exec(ctx, `ALTER TABLE emails ADD COLUMN IF NOT EXISTS storage_id BIGINT REFERENCES storage_objects(id)`)
|
||||
// PROJ-51: archiving_rules table + retain_until_source column
|
||||
s.initRetentionRulesSchema(ctx)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@@ -470,21 +474,12 @@ func (s *Store) Save(ctx context.Context, raw []byte, _ time.Time, tenantID *int
|
||||
if parseErr == nil {
|
||||
_ = s.saveAttachments(ctx, id, pm)
|
||||
}
|
||||
// PROJ-34: Set retention lock.
|
||||
// Mandanten-Mails: nur wenn der Mandant explizit retention_days > 0 gesetzt hat.
|
||||
// Globale config greift NICHT automatisch — jeder Mandant muss selbst opt-in.
|
||||
// Mails ohne Mandant (tenantID == nil): globale config als Fallback.
|
||||
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 {
|
||||
until := time.Now().AddDate(0, 0, tenantDays)
|
||||
_, _ = s.db.Exec(ctx, `UPDATE emails SET retain_until=$1 WHERE id=$2 AND retain_until IS NULL`, until, id)
|
||||
}
|
||||
// else: tenant hat retention_days=0 → kein Lock gesetzt → keine automatische Löschung
|
||||
} else if s.retentionDays > 0 {
|
||||
until := time.Now().AddDate(0, 0, s.retentionDays)
|
||||
_, _ = s.db.Exec(ctx, `UPDATE emails SET retain_until=$1 WHERE id=$2 AND retain_until IS NULL`, until, id)
|
||||
}
|
||||
// PROJ-34 + PROJ-51: Set retention lock.
|
||||
// Priority: matching archiving_rule with retention_days > tenant
|
||||
// default > global default, then raised by the global minimum floor.
|
||||
// Behaviour without rules/min stays identical to PROJ-34 (tenant
|
||||
// opt-in; global default only for tenant-less mails).
|
||||
s.applyRetention(ctx, id, pm, tenantID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user