package domain import ( "sort" "strings" "time" ) type PublicPlayer struct { ID string `json:"id"` DisplayName string `json:"displayName"` BattleTag string `json:"battleTag"` Ratings Ratings `json:"ratings"` IsGuest bool `json:"isGuest"` } type PlayerStatsSummary struct { SeriesPlayed int `json:"seriesPlayed"` SeriesWon int `json:"seriesWon"` SeriesLost int `json:"seriesLost"` WinRate float64 `json:"winRate"` MapsPlayed int `json:"mapsPlayed"` MapsWon int `json:"mapsWon"` MapsLost int `json:"mapsLost"` MapsDrawn int `json:"mapsDrawn"` } type PlayerMatchHistory struct { SeriesID string `json:"seriesId"` EventID string `json:"eventId"` EventName string `json:"eventName"` EventDate time.Time `json:"eventDate"` OwnTeamID string `json:"ownTeamId"` OwnTeamName string `json:"ownTeamName"` OpponentTeamID string `json:"opponentTeamId"` OpponentTeamName string `json:"opponentTeamName"` OwnScore int `json:"ownScore"` OpponentScore int `json:"opponentScore"` Outcome string `json:"outcome"` } type PlayerProfile struct { Player PublicPlayer `json:"player"` Summary PlayerStatsSummary `json:"summary"` Recent []PlayerMatchHistory `json:"recentMatches"` } type PlayerSeriesContext struct { Event Event Series Series TeamA Team TeamB Team } func BuildPlayerProfile(player Player, matches []PlayerSeriesContext, recentLimit int) PlayerProfile { profile := PlayerProfile{ Player: PublicPlayer{ ID: player.ID, DisplayName: player.DisplayName, BattleTag: player.BattleTag, Ratings: player.Ratings, IsGuest: player.AccountID == "", }, Recent: []PlayerMatchHistory{}, } for _, match := range matches { if match.Event.State != Completed || match.Series.Phase != SeriesComplete || match.Series.WinnerTeamID == "" { continue } own, opponent, ownIsA := teamContaining(match.TeamA, player.ID), match.TeamB, true if own.ID == "" { own, opponent, ownIsA = teamContaining(match.TeamB, player.ID), match.TeamA, false } if own.ID == "" || opponent.ID == "" { continue } ownScore, opponentScore, mapsWon, mapsLost, mapsDrawn := scoreEffectiveResults(match.Series, ownIsA) profile.Summary.SeriesPlayed++ profile.Summary.MapsPlayed += mapsWon + mapsLost + mapsDrawn profile.Summary.MapsWon += mapsWon profile.Summary.MapsLost += mapsLost profile.Summary.MapsDrawn += mapsDrawn outcome := "loss" if match.Series.WinnerTeamID == own.ID { outcome = "win" profile.Summary.SeriesWon++ } else { profile.Summary.SeriesLost++ } profile.Recent = append(profile.Recent, PlayerMatchHistory{ SeriesID: match.Series.ID, EventID: match.Event.ID, EventName: match.Event.Name, EventDate: match.Event.StartsAt, OwnTeamID: own.ID, OwnTeamName: own.Name, OpponentTeamID: opponent.ID, OpponentTeamName: opponent.Name, OwnScore: ownScore, OpponentScore: opponentScore, Outcome: outcome, }) } if profile.Summary.SeriesPlayed > 0 { profile.Summary.WinRate = float64(profile.Summary.SeriesWon) / float64(profile.Summary.SeriesPlayed) } sort.SliceStable(profile.Recent, func(i, j int) bool { if profile.Recent[i].EventDate.Equal(profile.Recent[j].EventDate) { return profile.Recent[i].SeriesID > profile.Recent[j].SeriesID } return profile.Recent[i].EventDate.After(profile.Recent[j].EventDate) }) if recentLimit >= 0 && len(profile.Recent) > recentLimit { profile.Recent = profile.Recent[:recentLimit] } return profile } func teamContaining(team Team, playerID string) Team { for _, slot := range team.Slots { if slot.PlayerID == playerID { return team } } return Team{} } func scoreEffectiveResults(series Series, ownIsA bool) (ownScore, opponentScore, won, lost, drawn int) { superseded := make(map[int]bool) for _, result := range series.Results { if result.CorrectionOf > 0 { superseded[result.CorrectionOf] = true } } for index, result := range series.Results { if superseded[index+1] { continue } switch result.Outcome { case Draw: drawn++ case TeamAWin: if ownIsA { ownScore, won = ownScore+1, won+1 } else { opponentScore, lost = opponentScore+1, lost+1 } case TeamBWin: if ownIsA { opponentScore, lost = opponentScore+1, lost+1 } else { ownScore, won = ownScore+1, won+1 } } } return ownScore, opponentScore, won, lost, drawn } type CommunitySettings struct { DiscordInviteURL string `json:"discordInviteUrl"` UpdatedAt time.Time `json:"updatedAt"` UpdatedBy string `json:"updatedBy"` } func (settings *CommunitySettings) Validate() error { settings.DiscordInviteURL = strings.TrimSpace(settings.DiscordInviteURL) if settings.DiscordInviteURL == "" { return nil } if !strings.HasPrefix(settings.DiscordInviteURL, "https://discord.gg/") && !strings.HasPrefix(settings.DiscordInviteURL, "https://discord.com/invite/") { return ErrInvalid } code := strings.TrimPrefix(settings.DiscordInviteURL, "https://discord.gg/") code = strings.TrimPrefix(code, "https://discord.com/invite/") if code == "" || strings.ContainsAny(code, "/?# \t\r\n") { return ErrInvalid } return nil }