Add player profiles and community settings
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / compose (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-19 21:43:48 +03:00
parent 5e0825f43f
commit fa4edc0df5
23 changed files with 1280 additions and 57 deletions

View File

@@ -20,6 +20,7 @@ type Store interface {
CreateSession(context.Context, string, string, time.Time) error
DeleteSession(context.Context, string) error
UpdatePlayer(context.Context, domain.Player) (domain.Player, error)
GetPlayer(context.Context, string) (domain.Player, error)
ListPlayers(context.Context) ([]domain.Player, error)
EnqueueDiscordRoleSync(context.Context, string, string, int64) error
CreateParticipant(context.Context, domain.Player, domain.RSVP) (domain.Player, domain.RSVP, error)
@@ -49,11 +50,14 @@ type Store interface {
GetRuleset(context.Context, string) (domain.Ruleset, error)
SaveSeries(context.Context, domain.Series) (domain.Series, error)
GetSeries(context.Context, string) (domain.Series, error)
ListSeriesByEvent(context.Context, string) ([]domain.Series, error)
SaveTournament(context.Context, domain.Tournament) (domain.Tournament, error)
GetTournament(context.Context, string) (domain.Tournament, error)
GetTournamentByEvent(context.Context, string) (domain.Tournament, error)
SaveDraft(context.Context, string, string, any, int) error
GetDraft(context.Context, string, any) (string, int, error)
GetCommunitySettings(context.Context) (domain.CommunitySettings, error)
UpdateCommunitySettings(context.Context, domain.CommunitySettings) (domain.CommunitySettings, error)
AppendAudit(context.Context, string, string, string, any) error
}
@@ -125,20 +129,35 @@ func (s *Service) SetModerator(ctx context.Context, actor domain.Account, accoun
return account, err
}
func (s *Service) UpdateOwnProfile(ctx context.Context, actor domain.Account, current domain.Player, displayName string, ratings domain.Ratings, preferredRoles []domain.Role, preferredPlayerIDs, avoidedPlayerIDs []string) (domain.Player, error) {
type UpdateOwnProfileInput struct {
DisplayName *string
BattleTag *string
Ratings domain.Ratings
PreferredRoles []domain.Role
PreferredPlayerIDs []string
AvoidedPlayerIDs []string
}
func (s *Service) UpdateOwnProfile(ctx context.Context, actor domain.Account, current domain.Player, input UpdateOwnProfileInput) (domain.Player, error) {
if actor.ID != current.AccountID {
return domain.Player{}, domain.ErrForbidden
}
if err := ratings.Validate(); err != nil {
if err := input.Ratings.Validate(); err != nil {
return domain.Player{}, err
}
if displayName != "" {
current.DisplayName = displayName
if input.DisplayName != nil {
current.DisplayName = *input.DisplayName
}
current.Ratings = ratings
current.PreferredRoles = preferredRoles
current.PreferredPlayerIDs = preferredPlayerIDs
current.AvoidedPlayerIDs = avoidedPlayerIDs
if input.BattleTag != nil {
current.BattleTag = *input.BattleTag
}
if err := current.ValidateIdentity(); err != nil {
return domain.Player{}, err
}
current.Ratings = input.Ratings
current.PreferredRoles = input.PreferredRoles
current.PreferredPlayerIDs = input.PreferredPlayerIDs
current.AvoidedPlayerIDs = input.AvoidedPlayerIDs
current.UpdatedAt = s.Now()
if err := current.ValidatePreferences(); err != nil {
return domain.Player{}, err
@@ -168,6 +187,75 @@ func (s *Service) UpdateOwnProfile(ctx context.Context, actor domain.Account, cu
return out, err
}
func (s *Service) GetPlayerProfile(ctx context.Context, playerID string) (domain.PlayerProfile, error) {
player, err := s.Store.GetPlayer(ctx, playerID)
if err != nil {
return domain.PlayerProfile{}, err
}
events, err := s.Store.ListEvents(ctx, time.Unix(0, 0).UTC())
if err != nil {
return domain.PlayerProfile{}, err
}
matches := make([]domain.PlayerSeriesContext, 0)
for _, event := range events {
if event.State != domain.Completed {
continue
}
teams, listErr := s.Store.ListTeams(ctx, event.ID)
if listErr != nil {
return domain.PlayerProfile{}, listErr
}
var ownTeam *domain.Team
for index := range teams {
for _, slot := range teams[index].Slots {
if slot.PlayerID == playerID {
ownTeam = &teams[index]
break
}
}
}
if ownTeam == nil {
continue
}
series, listErr := s.Store.ListSeriesByEvent(ctx, event.ID)
if listErr != nil {
return domain.PlayerProfile{}, listErr
}
byID := make(map[string]domain.Team, len(teams))
for _, team := range teams {
byID[team.ID] = team
}
for _, item := range series {
teamA, okA := byID[item.TeamAID]
teamB, okB := byID[item.TeamBID]
if okA && okB {
matches = append(matches, domain.PlayerSeriesContext{Event: event, Series: item, TeamA: teamA, TeamB: teamB})
}
}
}
return domain.BuildPlayerProfile(player, matches, 10), nil
}
func (s *Service) GetCommunitySettings(ctx context.Context) (domain.CommunitySettings, error) {
return s.Store.GetCommunitySettings(ctx)
}
func (s *Service) UpdateCommunitySettings(ctx context.Context, actor domain.Account, inviteURL string) (domain.CommunitySettings, error) {
if !actor.IsAdmin() {
return domain.CommunitySettings{}, domain.ErrForbidden
}
settings := domain.CommunitySettings{DiscordInviteURL: inviteURL, UpdatedAt: s.Now(), UpdatedBy: actor.ID}
if err := settings.Validate(); err != nil {
return domain.CommunitySettings{}, fmt.Errorf("%w: invalid Discord invite URL", err)
}
out, err := s.Store.UpdateCommunitySettings(ctx, settings)
if err == nil {
_ = s.Store.AppendAudit(ctx, actor.ID, "community.settings_updated", "community", out)
s.Bus.Publish("community-settings", out)
}
return out, err
}
func (s *Service) CreateEvent(ctx context.Context, actor domain.Account, event domain.Event, options CreateEventOptions) (domain.Event, error) {
if !actor.IsStaff() {
return domain.Event{}, domain.ErrForbidden
@@ -300,6 +388,9 @@ func (s *Service) CreateParticipant(ctx context.Context, actor domain.Account, e
CreatedAt: now,
UpdatedAt: now,
}
if err := player.ValidateIdentity(); err != nil {
return domain.Player{}, domain.RSVP{}, err
}
rsvp := domain.RSVP{
EventID: eventID,
PlayerID: player.ID,