agent-orchestrator/backend/internal/notify/manager.go

84 lines
2.2 KiB
Go

// Package notify owns notification write-side production and live dashboard fan-out.
package notify
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
)
// Store is the write-side notification persistence boundary.
type Store interface {
CreateNotification(ctx context.Context, rec domain.NotificationRecord) (domain.NotificationRecord, bool, error)
}
// Publisher pushes newly persisted notifications to live dashboard subscribers.
type Publisher interface {
Publish(ctx context.Context, rec domain.NotificationRecord) error
}
// Intent is the lifecycle-to-notification producer contract.
type Intent = ports.NotificationIntent
// Manager validates lifecycle intents, enriches them into stored rows, persists
// unread notifications, and publishes newly inserted rows to live subscribers.
type Manager struct {
store Store
publisher Publisher
clock func() time.Time
newID func() string
}
// Deps configures a Manager.
type Deps struct {
Store Store
Publisher Publisher
Clock func() time.Time
NewID func() string
}
// New constructs a write-side notification manager.
func New(d Deps) *Manager {
m := &Manager{store: d.Store, publisher: d.Publisher, clock: d.Clock, newID: d.NewID}
if m.clock == nil {
m.clock = time.Now
}
if m.newID == nil {
m.newID = func() string { return "ntf_" + uuid.NewString() }
}
return m
}
// Notify stores one notification intent and publishes it after persistence.
// Duplicate unread rows are treated as a clean no-op.
func (m *Manager) Notify(ctx context.Context, intent Intent) error {
if m == nil || m.store == nil {
return errors.New("notify: store is required")
}
if intent.CreatedAt.IsZero() {
intent.CreatedAt = m.clock().UTC()
}
rec, err := enrich(intent)
if err != nil {
return fmt.Errorf("notify enrich: %w", err)
}
rec.ID = m.newID()
created, inserted, err := m.store.CreateNotification(ctx, rec)
if err != nil {
return fmt.Errorf("notify store: %w", err)
}
if !inserted || m.publisher == nil {
return nil
}
if err := m.publisher.Publish(ctx, created); err != nil {
return fmt.Errorf("notify publish: %w", err)
}
return nil
}