backend: GDPR export + retention cleanups + worker metrics (BE-1/2/3)
BE-3 observability: expose the worker's Prometheus metrics on :6060/metrics (apns/fcm/asynq histograms + a new cache_ops_total counter were recorded all along but never scraped — which is why those dashboard panels read empty); add the worker containerPort, the vmagent worker scrape job, and two additive NetworkPolicies. Instrument cache Get/Set hit/miss. BE-2 retention: three periodic Asynq cleanup crons mirroring the reminder-log cleanup — notifications (90d), webhook dedup log (180d), audit_log (365d). BE-1 GDPR data export: POST /api/auth/export/ enqueues a low-priority Asynq job that gathers all of the user's data (owned residences + their tasks/contractors/ documents/share-codes, plus profile/notifications/prefs/push-tokens/subscription/ audit log), zips one JSON file per category, and emails it as an attachment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/treytartt/honeydue-api/internal/models"
|
||||
"github.com/treytartt/honeydue-api/internal/repositories"
|
||||
)
|
||||
|
||||
// Data-retention cleanup job types. Registered as periodic crons in
|
||||
// cmd/worker/main.go. These keep transient/log tables from growing unbounded;
|
||||
// none touch user-facing data that the app reads back.
|
||||
const (
|
||||
TypeNotificationCleanup = "maintenance:notification_cleanup"
|
||||
TypeWebhookLogCleanup = "maintenance:webhook_log_cleanup"
|
||||
TypeAuditLogCleanup = "maintenance:audit_log_cleanup"
|
||||
)
|
||||
|
||||
// Retention windows (days).
|
||||
const (
|
||||
notificationRetentionDays = 90
|
||||
webhookLogRetentionDays = 180
|
||||
auditLogRetentionDays = 365 // keep 1 year of security events
|
||||
)
|
||||
|
||||
// HandleNotificationCleanup deletes notification rows older than the retention
|
||||
// window. Notifications are delivery records (push/digest history); 90 days is
|
||||
// ample for any in-app history a client might show.
|
||||
func (h *Handler) HandleNotificationCleanup(ctx context.Context, _ *asynq.Task) error {
|
||||
cutoff := time.Now().UTC().AddDate(0, 0, -notificationRetentionDays)
|
||||
res := h.db.WithContext(ctx).Where("created_at < ?", cutoff).Delete(&models.Notification{})
|
||||
if res.Error != nil {
|
||||
log.Error().Err(res.Error).Msg("notification cleanup failed")
|
||||
return res.Error
|
||||
}
|
||||
log.Info().Int64("deleted", res.RowsAffected).Int("retention_days", notificationRetentionDays).Msg("notification cleanup completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleWebhookLogCleanup prunes the webhook dedup log. Rows only matter for the
|
||||
// window in which a provider (Apple/Google) might redeliver an event; 180 days
|
||||
// is a generous safety margin past any real redelivery.
|
||||
func (h *Handler) HandleWebhookLogCleanup(ctx context.Context, _ *asynq.Task) error {
|
||||
cutoff := time.Now().UTC().AddDate(0, 0, -webhookLogRetentionDays)
|
||||
res := h.db.WithContext(ctx).Where("processed_at < ?", cutoff).Delete(&repositories.WebhookEvent{})
|
||||
if res.Error != nil {
|
||||
log.Error().Err(res.Error).Msg("webhook log cleanup failed")
|
||||
return res.Error
|
||||
}
|
||||
log.Info().Int64("deleted", res.RowsAffected).Int("retention_days", webhookLogRetentionDays).Msg("webhook log cleanup completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleAuditLogCleanup prunes audit events older than the retention window.
|
||||
// One year of security events is retained for compliance/forensics.
|
||||
func (h *Handler) HandleAuditLogCleanup(ctx context.Context, _ *asynq.Task) error {
|
||||
cutoff := time.Now().UTC().AddDate(0, 0, -auditLogRetentionDays)
|
||||
res := h.db.WithContext(ctx).Where("created_at < ?", cutoff).Delete(&models.AuditLog{})
|
||||
if res.Error != nil {
|
||||
log.Error().Err(res.Error).Msg("audit log cleanup failed")
|
||||
return res.Error
|
||||
}
|
||||
log.Info().Int64("deleted", res.RowsAffected).Int("retention_days", auditLogRetentionDays).Msg("audit log cleanup completed")
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user