82 lines
2.6 KiB
Go
82 lines
2.6 KiB
Go
package lobby
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// OnUserBlocked releases every lobby binding owned by the user under
|
|
// the `blocked` semantics: active memberships flip to `blocked`,
|
|
// pending applications get rejected, pending invites incoming get
|
|
// declined / outgoing get revoked, race-name entries are deleted, and
|
|
// owned games in non-running statuses are cancelled.
|
|
//
|
|
// Implements `internal/user.LobbyCascade.OnUserBlocked`. Errors during
|
|
// the cascade are joined and returned but never roll back the
|
|
// already-committed user write — the canonical state is the row in
|
|
// Postgres.
|
|
func (s *Service) OnUserBlocked(ctx context.Context, userID uuid.UUID) error {
|
|
return s.runCascade(ctx, userID, MembershipStatusBlocked)
|
|
}
|
|
|
|
// OnUserDeleted runs the same cascade as OnUserBlocked but transitions
|
|
// memberships to `removed` instead of `blocked`. Implements
|
|
// `internal/user.LobbyCascade.OnUserDeleted`.
|
|
func (s *Service) OnUserDeleted(ctx context.Context, userID uuid.UUID) error {
|
|
return s.runCascade(ctx, userID, MembershipStatusRemoved)
|
|
}
|
|
|
|
func (s *Service) runCascade(ctx context.Context, userID uuid.UUID, membershipStatus string) error {
|
|
snap, err := s.deps.Store.LoadCascadeSnapshot(ctx, userID)
|
|
if err != nil {
|
|
return fmt.Errorf("lobby cascade: load snapshot: %w", err)
|
|
}
|
|
if snap.empty() {
|
|
return nil
|
|
}
|
|
now := s.deps.Now().UTC()
|
|
if err := s.deps.Store.CascadeUser(ctx, userID, snap, membershipStatus, now); err != nil {
|
|
return fmt.Errorf("lobby cascade: write: %w", err)
|
|
}
|
|
s.deps.Cache.EvictUserMemberships(userID)
|
|
s.deps.Cache.EvictUserRaceNames(userID)
|
|
s.deps.Cache.EvictOwnerGames(userID)
|
|
|
|
var notifyErrs []error
|
|
for _, gameID := range snap.OwnedGameIDs {
|
|
s.deps.Cache.RemoveGame(gameID)
|
|
}
|
|
if len(snap.ActiveMembershipIDs) > 0 {
|
|
intent := LobbyNotification{
|
|
Kind: NotificationLobbyMembershipRemoved,
|
|
IdempotencyKey: "user-cascade-membership:" + userID.String(),
|
|
Recipients: []uuid.UUID{userID},
|
|
Payload: map[string]any{
|
|
"reason": membershipStatus,
|
|
},
|
|
}
|
|
if pubErr := s.deps.Notification.PublishLobbyEvent(ctx, intent); pubErr != nil {
|
|
notifyErrs = append(notifyErrs, pubErr)
|
|
}
|
|
}
|
|
if len(notifyErrs) > 0 {
|
|
s.deps.Logger.Warn("lobby cascade notification failures",
|
|
zap.String("user_id", userID.String()),
|
|
zap.Int("notify_errors", len(notifyErrs)))
|
|
}
|
|
return errors.Join(notifyErrs...)
|
|
}
|
|
|
|
func (snap CascadeUserSnapshot) empty() bool {
|
|
return len(snap.OwnedGameIDs) == 0 &&
|
|
len(snap.ActiveMembershipIDs) == 0 &&
|
|
len(snap.PendingApplications) == 0 &&
|
|
len(snap.IncomingInvites) == 0 &&
|
|
len(snap.OutgoingInvites) == 0 &&
|
|
len(snap.RaceNameKeys) == 0
|
|
}
|