verstak-sync-server/internal/server/retention.go

46 lines
1.4 KiB
Go

package server
import (
"os"
"path/filepath"
"strings"
"time"
)
// CleanupRetention only removes independently expiring data. It intentionally
// never prunes server_ops or content-addressed blobs: the operation log remains
// required for a newly paired device until a future checkpoint protocol exists.
func (s *Server) CleanupRetention(now time.Time) error {
if err := s.cleanupExpiredSessions(); err != nil {
return err
}
if _, err := s.db.Exec("DELETE FROM server_email_tokens WHERE expires_at <= ?", now.UTC().Format(time.RFC3339)); err != nil {
return err
}
if _, err := s.db.Exec("DELETE FROM server_idempotency_keys WHERE created_at < ?", now.Add(-time.Duration(s.cfg.Retention.IdempotencyHours)*time.Hour).UTC().Format(time.RFC3339)); err != nil {
return err
}
if _, err := s.db.Exec("DELETE FROM server_audit_log WHERE created_at < ?", now.AddDate(0, 0, -s.cfg.Retention.AuditDays).UTC().Format(time.RFC3339)); err != nil {
return err
}
entries, err := os.ReadDir(s.blobsDir)
if err != nil && !os.IsNotExist(err) {
return err
}
for _, entry := range entries {
if !strings.HasPrefix(entry.Name(), ".upload-") {
continue
}
info, err := entry.Info()
if err != nil {
return err
}
if info.ModTime().Before(now.Add(-time.Duration(s.cfg.Retention.TempUploadHours) * time.Hour)) {
if err := os.Remove(filepath.Join(s.blobsDir, entry.Name())); err != nil && !os.IsNotExist(err) {
return err
}
}
}
return nil
}