136 lines
4.5 KiB
Go
136 lines
4.5 KiB
Go
// Package racenameintents adapts the per-game capability evaluator's
|
|
// RaceNameIntents interface to the shared galaxy/notificationintent
|
|
// publisher. introduced a NoopRaceNameIntents shim while the
|
|
// notification catalog lacked the lobby.race_name.* types; lands
|
|
// those types and this adapter replaces the shim in production wiring.
|
|
package racenameintents
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"galaxy/lobby/internal/ports"
|
|
"galaxy/lobby/internal/service/capabilityevaluation"
|
|
"galaxy/notificationintent"
|
|
)
|
|
|
|
// Publisher implements capabilityevaluation.RaceNameIntents by composing
|
|
// the type-specific notificationintent constructors with the shared
|
|
// IntentPublisher port.
|
|
type Publisher struct {
|
|
publisher ports.IntentPublisher
|
|
clock func() time.Time
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// Config groups the dependencies required to construct a Publisher.
|
|
type Config struct {
|
|
// Publisher receives every constructed notification intent. The
|
|
// adapter never falls back to a noop; transport errors are wrapped
|
|
// and returned so the evaluator's logging path can record them.
|
|
Publisher ports.IntentPublisher
|
|
|
|
// Clock supplies the wall-clock used for log timestamps. The
|
|
// adapter copies FinishedAt from the inbound event into the intent
|
|
// metadata, so the clock is currently unused inside Publish*; it is
|
|
// retained on the struct for parity with other lobby adapters and
|
|
// for forthcoming tracing hooks.
|
|
Clock func() time.Time
|
|
|
|
// Logger receives optional adapter-level structured logs. Defaults
|
|
// to slog.Default() if nil.
|
|
Logger *slog.Logger
|
|
}
|
|
|
|
// NewPublisher constructs one Publisher.
|
|
func NewPublisher(cfg Config) (*Publisher, error) {
|
|
if cfg.Publisher == nil {
|
|
return nil, errors.New("new race name intents publisher: nil intent publisher")
|
|
}
|
|
clock := cfg.Clock
|
|
if clock == nil {
|
|
clock = time.Now
|
|
}
|
|
logger := cfg.Logger
|
|
if logger == nil {
|
|
logger = slog.Default()
|
|
}
|
|
return &Publisher{
|
|
publisher: cfg.Publisher,
|
|
clock: clock,
|
|
logger: logger.With("adapter", "lobby.racenameintents"),
|
|
}, nil
|
|
}
|
|
|
|
// PublishEligible builds a lobby.race_name.registration_eligible intent
|
|
// from ev and forwards it to the underlying intent publisher. Idempotency
|
|
// is scoped by (game_id, user_id) so retries of the same evaluator pass
|
|
// collapse to a single notification at the consumer.
|
|
func (publisher *Publisher) PublishEligible(ctx context.Context, ev capabilityevaluation.EligibleEvent) error {
|
|
if publisher == nil {
|
|
return errors.New("publish race name eligible intent: nil publisher")
|
|
}
|
|
if ctx == nil {
|
|
return errors.New("publish race name eligible intent: nil context")
|
|
}
|
|
gameID := ev.GameID.String()
|
|
intent, err := notificationintent.NewLobbyRaceNameRegistrationEligibleIntent(
|
|
notificationintent.Metadata{
|
|
IdempotencyKey: "game-lobby:race-name-eligible:" + gameID + ":" + ev.UserID,
|
|
OccurredAt: ev.FinishedAt,
|
|
},
|
|
ev.UserID,
|
|
notificationintent.LobbyRaceNameRegistrationEligiblePayload{
|
|
GameID: gameID,
|
|
GameName: ev.GameName,
|
|
RaceName: ev.RaceName,
|
|
EligibleUntilMs: ev.EligibleUntil.UnixMilli(),
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("publish race name eligible intent: build intent: %w", err)
|
|
}
|
|
if _, err := publisher.publisher.Publish(ctx, intent); err != nil {
|
|
return fmt.Errorf("publish race name eligible intent: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PublishDenied builds a lobby.race_name.registration_denied intent from
|
|
// ev and forwards it to the underlying intent publisher.
|
|
func (publisher *Publisher) PublishDenied(ctx context.Context, ev capabilityevaluation.DeniedEvent) error {
|
|
if publisher == nil {
|
|
return errors.New("publish race name denied intent: nil publisher")
|
|
}
|
|
if ctx == nil {
|
|
return errors.New("publish race name denied intent: nil context")
|
|
}
|
|
gameID := ev.GameID.String()
|
|
intent, err := notificationintent.NewLobbyRaceNameRegistrationDeniedIntent(
|
|
notificationintent.Metadata{
|
|
IdempotencyKey: "game-lobby:race-name-denied:" + gameID + ":" + ev.UserID,
|
|
OccurredAt: ev.FinishedAt,
|
|
},
|
|
ev.UserID,
|
|
notificationintent.LobbyRaceNameRegistrationDeniedPayload{
|
|
GameID: gameID,
|
|
GameName: ev.GameName,
|
|
RaceName: ev.RaceName,
|
|
Reason: ev.Reason,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("publish race name denied intent: build intent: %w", err)
|
|
}
|
|
if _, err := publisher.publisher.Publish(ctx, intent); err != nil {
|
|
return fmt.Errorf("publish race name denied intent: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Compile-time interface assertion.
|
|
var _ capabilityevaluation.RaceNameIntents = (*Publisher)(nil)
|