Projeto bot pontos
This commit is contained in:
124
backend/internal/app/maintenance.go
Normal file
124
backend/internal/app/maintenance.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"botpontos/internal/adapters/postgres"
|
||||
"botpontos/internal/domain"
|
||||
)
|
||||
|
||||
const stateRetentionLastRun = "retention_last_run"
|
||||
|
||||
// Maintenance é o ciclo de 5 min: falha os envios travados e, uma vez por dia
|
||||
// no horário configurado, aplica a retenção de dados (9.10).
|
||||
func (s *Service) Maintenance(ctx context.Context) error {
|
||||
stuck, err := s.store.FailStuckSending(ctx, s.now().Add(-s.cfg.Jobs.SendingTimeout))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range stuck {
|
||||
s.audit(ctx, WorkerActor, &n.UserID, domain.EventInterruptedSending, false,
|
||||
map[string]any{"notification_id": n.ID, "type": string(n.Type)})
|
||||
}
|
||||
return s.applyRetentionIfDue(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) applyRetentionIfDue(ctx context.Context) error {
|
||||
now := s.now().In(s.location)
|
||||
if now.Hour() != s.cfg.Jobs.RetentionHour {
|
||||
return nil
|
||||
}
|
||||
today := s.TodayDate()
|
||||
var last struct {
|
||||
Day string `json:"day"`
|
||||
}
|
||||
if found, err := s.store.GetState(ctx, stateRetentionLastRun, &last); err != nil {
|
||||
return err
|
||||
} else if found && last.Day == today {
|
||||
return nil
|
||||
}
|
||||
|
||||
audit, err := s.store.DeleteAuditBefore(ctx, now.Add(-s.cfg.Jobs.RetentionAudit))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
notificationsCutoff := domain.LocalDay(now.Add(-s.cfg.Jobs.RetentionNotifications), s.location)
|
||||
notifications, err := s.store.DeleteNotificationsBefore(ctx, notificationsCutoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
days, err := s.store.DeleteUserDaysBefore(ctx, notificationsCutoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sessions, err := s.store.DeleteSessionsEndedBefore(ctx, now.Add(-s.cfg.Jobs.RetentionSessions))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
last.Day = today
|
||||
if err := s.store.SetState(ctx, stateRetentionLastRun, last); err != nil {
|
||||
return err
|
||||
}
|
||||
s.audit(ctx, WorkerActor, nil, domain.EventRetentionApplied, true, map[string]any{
|
||||
"audit_logs": audit, "notifications": notifications, "user_days": days, "web_sessions": sessions,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RotateKey recifra com a chave atual as credenciais gravadas com outra versão.
|
||||
func (s *Service) RotateKey(ctx context.Context) (int, error) {
|
||||
creds, err := s.store.ListCredentials(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
current := s.cipher.CurrentVersion()
|
||||
rotated := 0
|
||||
for _, c := range creds {
|
||||
if c.KeyVersion == current {
|
||||
continue
|
||||
}
|
||||
plain, err := s.cipher.Decrypt(c.EncryptedPassword, c.KeyVersion)
|
||||
if err != nil {
|
||||
return rotated, err
|
||||
}
|
||||
encrypted, version, err := s.cipher.Encrypt(plain)
|
||||
if err != nil {
|
||||
return rotated, err
|
||||
}
|
||||
if err := s.store.UpdateCredentialCipher(ctx, c.UserID, encrypted, version); err != nil {
|
||||
return rotated, err
|
||||
}
|
||||
rotated++
|
||||
}
|
||||
s.audit(ctx, CLIActor, nil, domain.EventEncryptionKeyRotated, true, map[string]any{"credentials": rotated, "version": current})
|
||||
return rotated, nil
|
||||
}
|
||||
|
||||
// Overview reúne os dados do dashboard do painel.
|
||||
type Overview struct {
|
||||
Counts postgres.Dashboard
|
||||
OutageSince *string
|
||||
HolidayFallback *string
|
||||
}
|
||||
|
||||
func (s *Service) Overview(ctx context.Context) (Overview, error) {
|
||||
var o Overview
|
||||
counts, err := s.store.Dashboard(ctx, s.TodayDate())
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
o.Counts = counts
|
||||
var outage outageState
|
||||
if found, _ := s.store.GetState(ctx, stateOutage, &outage); found {
|
||||
since := outage.Since.Format("2006-01-02T15:04:05Z07:00")
|
||||
o.OutageSince = &since
|
||||
}
|
||||
var fallback struct {
|
||||
Day string `json:"day"`
|
||||
}
|
||||
if found, _ := s.store.GetState(ctx, stateHolidayFallbackNotified, &fallback); found && fallback.Day == s.TodayDate() {
|
||||
o.HolidayFallback = &fallback.Day
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
Reference in New Issue
Block a user