Files
story/internal/onboarding/maintenance.go
T

66 lines
2.2 KiB
Go
Raw Normal View History

package onboarding
import (
"context"
"data-terminal-cloud/internal/outbox"
"encoding/json"
"github.com/google/uuid"
"time"
)
// Recover durable jobs after an outbox retry budget was exhausted during a
// pause. ProcessActivation owns the device lock and the no-repeat fence.
func (s *Service) RunMaintenance(ctx context.Context) {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
if err := s.MaintainOnce(ctx); err != nil && ctx.Err() == nil {
s.options.Logger.Error("onboarding recovery scan failed")
}
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}
func (s *Service) MaintainOnce(ctx context.Context) error {
rows, err := s.pool.Query(ctx, `SELECT j.device_id::text,j.id::text,j.binding_generation FROM device_activation_job j JOIN device d ON d.id=j.device_id JOIN device_binding b ON b.device_id=d.id AND b.unbound_at IS NULL AND b.generation=j.binding_generation WHERE d.status='registered' AND d.activation_state NOT IN ('LOST','REVOKED') AND j.binding_generation=d.binding_generation AND j.updated_at<now()-interval '3 minutes' AND (j.status IN ('NOT_STARTED','RESERVED','CONTACT_POSSIBLE') OR (j.status='REGISTERED_OBSERVED' AND j.config_ciphertext IS NULL)) ORDER BY j.updated_at LIMIT 10`)
if err != nil {
return err
}
type item struct {
DeviceID string `json:"device_id"`
ActivationID string `json:"activation_id"`
Generation int64 `json:"binding_generation"`
}
var items []item
for rows.Next() {
var v item
if err = rows.Scan(&v.DeviceID, &v.ActivationID, &v.Generation); err != nil {
rows.Close()
return err
}
items = append(items, v)
}
rows.Close()
if err = rows.Err(); err != nil {
return err
}
if s.options.ActivationEnabled {
for _, v := range items {
raw, _ := json.Marshal(v)
if err = s.ProcessActivation(ctx, outbox.Event{ID: uuid.NewString(), Payload: raw}); err != nil {
return err
}
}
}
// Retain business attempts and idempotency tombstones for replay protection.
_, err = s.pool.Exec(ctx, `DELETE FROM device_handoff WHERE expires_at<now()-interval '1 day'`)
if err != nil {
return err
}
_, err = s.pool.Exec(ctx, `DELETE FROM device_time_challenge WHERE expires_at<now()-interval '1 day'`)
return err
}