diff options
| author | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2023-08-30 16:31:29 +0300 |
|---|---|---|
| committer | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2023-08-30 16:31:29 +0300 |
| commit | ee533d9405e3ffe1fd1a073e29f839568c465ba7 (patch) | |
| tree | 3545800d74f99fe5055f3b81bd83536bdcc94bc9 | |
| parent | refactor: reorganizing packages (diff) | |
| download | lphub-ee533d9405e3ffe1fd1a073e29f839568c465ba7.tar.gz lphub-ee533d9405e3ffe1fd1a073e29f839568c465ba7.tar.bz2 lphub-ee533d9405e3ffe1fd1a073e29f839568c465ba7.zip | |
feat: better leaderboards response, coop additions (#45)
Former-commit-id: 3a4a8af14d054512204b5ca4c25a6603ec94773e
| -rw-r--r-- | backend/handlers/map.go | 104 | ||||
| -rw-r--r-- | backend/models/models.go | 28 |
2 files changed, 79 insertions, 53 deletions
diff --git a/backend/handlers/map.go b/backend/handlers/map.go index b47e793..9b0caef 100644 --- a/backend/handlers/map.go +++ b/backend/handlers/map.go | |||
| @@ -3,6 +3,7 @@ package handlers | |||
| 3 | import ( | 3 | import ( |
| 4 | "net/http" | 4 | "net/http" |
| 5 | "strconv" | 5 | "strconv" |
| 6 | "time" | ||
| 6 | 7 | ||
| 7 | "github.com/gin-gonic/gin" | 8 | "github.com/gin-gonic/gin" |
| 8 | "github.com/pektezol/leastportalshub/backend/database" | 9 | "github.com/pektezol/leastportalshub/backend/database" |
| @@ -14,6 +15,11 @@ type MapSummaryResponse struct { | |||
| 14 | Summary models.MapSummary `json:"summary"` | 15 | Summary models.MapSummary `json:"summary"` |
| 15 | } | 16 | } |
| 16 | 17 | ||
| 18 | type MapLeaderboardsResponse struct { | ||
| 19 | Map models.Map `json:"map"` | ||
| 20 | Records any `json:"records"` | ||
| 21 | } | ||
| 22 | |||
| 17 | type ChaptersResponse struct { | 23 | type ChaptersResponse struct { |
| 18 | Game models.Game `json:"game"` | 24 | Game models.Game `json:"game"` |
| 19 | Chapters []models.Chapter `json:"chapters"` | 25 | Chapters []models.Chapter `json:"chapters"` |
| @@ -24,6 +30,34 @@ type ChapterMapsResponse struct { | |||
| 24 | Maps []models.MapShort `json:"maps"` | 30 | Maps []models.MapShort `json:"maps"` |
| 25 | } | 31 | } |
| 26 | 32 | ||
| 33 | type RecordSingleplayer struct { | ||
| 34 | Placement int `json:"placement"` | ||
| 35 | RecordID int `json:"record_id"` | ||
| 36 | ScoreCount int `json:"score_count"` | ||
| 37 | ScoreTime int `json:"score_time"` | ||
| 38 | UserID string `json:"user_id"` | ||
| 39 | UserName string `json:"user_name"` | ||
| 40 | UserAvatar string `json:"user_avatar"` | ||
| 41 | DemoID string `json:"demo_id"` | ||
| 42 | RecordDate time.Time `json:"record_date"` | ||
| 43 | } | ||
| 44 | |||
| 45 | type RecordMultiplayer struct { | ||
| 46 | Placement int `json:"placement"` | ||
| 47 | RecordID int `json:"record_id"` | ||
| 48 | ScoreCount int `json:"score_count"` | ||
| 49 | ScoreTime int `json:"score_time"` | ||
| 50 | HostID string `json:"host_id"` | ||
| 51 | HostName string `json:"host_name"` | ||
| 52 | HostAvatar string `json:"host_avatar"` | ||
| 53 | PartnerID string `json:"partner_id"` | ||
| 54 | PartnerName string `json:"partner_name"` | ||
| 55 | PartnerAvatar string `json:"partner_avatar"` | ||
| 56 | HostDemoID string `json:"host_demo_id"` | ||
| 57 | PartnerDemoID string `json:"partner_demo_id"` | ||
| 58 | RecordDate time.Time `json:"record_date"` | ||
| 59 | } | ||
| 60 | |||
| 27 | // GET Map Summary | 61 | // GET Map Summary |
| 28 | // | 62 | // |
| 29 | // @Description Get map summary with specified id. | 63 | // @Description Get map summary with specified id. |
| @@ -88,28 +122,29 @@ func FetchMapSummary(c *gin.Context) { | |||
| 88 | // @Tags maps | 122 | // @Tags maps |
| 89 | // @Produce json | 123 | // @Produce json |
| 90 | // @Param id path int true "Map ID" | 124 | // @Param id path int true "Map ID" |
| 91 | // @Success 200 {object} models.Response{data=models.Map{data=models.MapRecords}} | 125 | // @Success 200 {object} models.Response{data=MapLeaderboardsResponse} |
| 92 | // @Failure 400 {object} models.Response | 126 | // @Failure 400 {object} models.Response |
| 93 | // @Router /maps/{id}/leaderboards [get] | 127 | // @Router /maps/{id}/leaderboards [get] |
| 94 | func FetchMapLeaderboards(c *gin.Context) { | 128 | func FetchMapLeaderboards(c *gin.Context) { |
| 95 | // TODO: make new response type | 129 | // TODO: make new response type |
| 96 | id := c.Param("id") | 130 | id := c.Param("id") |
| 97 | // Get map data | 131 | // Get map data |
| 98 | var mapData models.Map | 132 | response := MapLeaderboardsResponse{Map: models.Map{}, Records: nil} |
| 99 | var mapRecordsData models.MapRecords | 133 | // var mapData models.Map |
| 134 | // var mapRecordsData models.MapRecords | ||
| 100 | var isDisabled bool | 135 | var isDisabled bool |
| 101 | intID, err := strconv.Atoi(id) | 136 | intID, err := strconv.Atoi(id) |
| 102 | if err != nil { | 137 | if err != nil { |
| 103 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) | 138 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) |
| 104 | return | 139 | return |
| 105 | } | 140 | } |
| 106 | mapData.ID = intID | 141 | response.Map.ID = intID |
| 107 | sql := `SELECT g.name, c.name, m.name, is_disabled, m.image | 142 | sql := `SELECT g.name, c.name, m.name, is_disabled, m.image |
| 108 | FROM maps m | 143 | FROM maps m |
| 109 | INNER JOIN games g ON m.game_id = g.id | 144 | INNER JOIN games g ON m.game_id = g.id |
| 110 | INNER JOIN chapters c ON m.chapter_id = c.id | 145 | INNER JOIN chapters c ON m.chapter_id = c.id |
| 111 | WHERE m.id = $1` | 146 | WHERE m.id = $1` |
| 112 | err = database.DB.QueryRow(sql, id).Scan(&mapData.GameName, &mapData.ChapterName, &mapData.MapName, &isDisabled, &mapData.Image) | 147 | err = database.DB.QueryRow(sql, id).Scan(&response.Map.GameName, &response.Map.ChapterName, &response.Map.MapName, &isDisabled, &response.Map.Image) |
| 113 | if err != nil { | 148 | if err != nil { |
| 114 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) | 149 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) |
| 115 | return | 150 | return |
| @@ -119,17 +154,38 @@ func FetchMapLeaderboards(c *gin.Context) { | |||
| 119 | return | 154 | return |
| 120 | } | 155 | } |
| 121 | // TODO: avatar and names for host & partner | 156 | // TODO: avatar and names for host & partner |
| 122 | // Get records from the map | 157 | if response.Map.GameName == "Portal 2 - Cooperative" { |
| 123 | if mapData.GameName == "Portal 2 - Cooperative" { | 158 | records := []RecordMultiplayer{} |
| 124 | var records []models.RecordMP | 159 | sql = `SELECT |
| 125 | sql = `SELECT id, host_id, partner_id, score_count, score_time, host_demo_id, partner_demo_id, record_date | 160 | sub.id, |
| 126 | FROM ( | 161 | sub.host_id, |
| 127 | SELECT id, host_id, partner_id, score_count, score_time, host_demo_id, partner_demo_id, record_date, | 162 | host.user_name AS host_user_name, |
| 128 | ROW_NUMBER() OVER (PARTITION BY host_id, partner_id ORDER BY score_count, score_time) AS rn | 163 | host.avatar_link AS host_avatar_link, |
| 129 | FROM records_mp | 164 | sub.partner_id, |
| 130 | WHERE map_id = $1 | 165 | partner.user_name AS partner_user_name, |
| 131 | ) sub | 166 | partner.avatar_link AS partner_avatar_link, |
| 132 | WHERE rn = 1` | 167 | sub.score_count, |
| 168 | sub.score_time, | ||
| 169 | sub.host_demo_id, | ||
| 170 | sub.partner_demo_id, | ||
| 171 | sub.record_date | ||
| 172 | FROM ( | ||
| 173 | SELECT | ||
| 174 | id, | ||
| 175 | host_id, | ||
| 176 | partner_id, | ||
| 177 | score_count, | ||
| 178 | score_time, | ||
| 179 | host_demo_id, | ||
| 180 | partner_demo_id, | ||
| 181 | record_date, | ||
| 182 | ROW_NUMBER() OVER (PARTITION BY host_id, partner_id ORDER BY score_count, score_time) AS rn | ||
| 183 | FROM records_mp | ||
| 184 | WHERE map_id = $1 | ||
| 185 | ) sub | ||
| 186 | JOIN users AS host ON sub.host_id = host.steam_id | ||
| 187 | JOIN users AS partner ON sub.partner_id = partner.steam_id | ||
| 188 | WHERE sub.rn = 1;` | ||
| 133 | rows, err := database.DB.Query(sql, id) | 189 | rows, err := database.DB.Query(sql, id) |
| 134 | if err != nil { | 190 | if err != nil { |
| 135 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) | 191 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) |
| @@ -138,8 +194,8 @@ func FetchMapLeaderboards(c *gin.Context) { | |||
| 138 | placement := 1 | 194 | placement := 1 |
| 139 | ties := 0 | 195 | ties := 0 |
| 140 | for rows.Next() { | 196 | for rows.Next() { |
| 141 | var record models.RecordMP | 197 | var record RecordMultiplayer |
| 142 | err := rows.Scan(&record.RecordID, &record.HostID, &record.PartnerID, &record.ScoreCount, &record.ScoreTime, &record.HostDemoID, &record.PartnerDemoID, &record.RecordDate) | 198 | err := rows.Scan(&record.RecordID, &record.HostID, &record.HostName, &record.HostAvatar, &record.PartnerID, &record.PartnerName, &record.PartnerAvatar, &record.ScoreCount, &record.ScoreTime, &record.HostDemoID, &record.PartnerDemoID, &record.RecordDate) |
| 143 | if err != nil { | 199 | if err != nil { |
| 144 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) | 200 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) |
| 145 | return | 201 | return |
| @@ -153,9 +209,9 @@ func FetchMapLeaderboards(c *gin.Context) { | |||
| 153 | records = append(records, record) | 209 | records = append(records, record) |
| 154 | placement++ | 210 | placement++ |
| 155 | } | 211 | } |
| 156 | mapRecordsData.Records = records | 212 | response.Records = records |
| 157 | } else { | 213 | } else { |
| 158 | var records []models.RecordSP | 214 | records := []RecordSingleplayer{} |
| 159 | sql = `SELECT id, user_id, users.user_name, users.avatar_link, score_count, score_time, demo_id, record_date | 215 | sql = `SELECT id, user_id, users.user_name, users.avatar_link, score_count, score_time, demo_id, record_date |
| 160 | FROM ( | 216 | FROM ( |
| 161 | SELECT id, user_id, score_count, score_time, demo_id, record_date, | 217 | SELECT id, user_id, score_count, score_time, demo_id, record_date, |
| @@ -173,7 +229,7 @@ func FetchMapLeaderboards(c *gin.Context) { | |||
| 173 | placement := 1 | 229 | placement := 1 |
| 174 | ties := 0 | 230 | ties := 0 |
| 175 | for rows.Next() { | 231 | for rows.Next() { |
| 176 | var record models.RecordSP | 232 | var record RecordSingleplayer |
| 177 | err := rows.Scan(&record.RecordID, &record.UserID, &record.UserName, &record.UserAvatar, &record.ScoreCount, &record.ScoreTime, &record.DemoID, &record.RecordDate) | 233 | err := rows.Scan(&record.RecordID, &record.UserID, &record.UserName, &record.UserAvatar, &record.ScoreCount, &record.ScoreTime, &record.DemoID, &record.RecordDate) |
| 178 | if err != nil { | 234 | if err != nil { |
| 179 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) | 235 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) |
| @@ -188,14 +244,12 @@ func FetchMapLeaderboards(c *gin.Context) { | |||
| 188 | records = append(records, record) | 244 | records = append(records, record) |
| 189 | placement++ | 245 | placement++ |
| 190 | } | 246 | } |
| 191 | mapRecordsData.Records = records | 247 | response.Records = records |
| 192 | } | 248 | } |
| 193 | // mapData.Data = mapRecordsData | ||
| 194 | // Return response | ||
| 195 | c.JSON(http.StatusOK, models.Response{ | 249 | c.JSON(http.StatusOK, models.Response{ |
| 196 | Success: true, | 250 | Success: true, |
| 197 | Message: "Successfully retrieved map leaderboards.", | 251 | Message: "Successfully retrieved map leaderboards.", |
| 198 | Data: mapData, | 252 | Data: response, |
| 199 | }) | 253 | }) |
| 200 | } | 254 | } |
| 201 | 255 | ||
diff --git a/backend/models/models.go b/backend/models/models.go index f124db5..b706d25 100644 --- a/backend/models/models.go +++ b/backend/models/models.go | |||
| @@ -104,34 +104,6 @@ type Links struct { | |||
| 104 | Twitch string `json:"twitch"` | 104 | Twitch string `json:"twitch"` |
| 105 | } | 105 | } |
| 106 | 106 | ||
| 107 | type RecordSP struct { | ||
| 108 | RecordID int `json:"record_id"` | ||
| 109 | Placement int `json:"placement"` | ||
| 110 | UserID string `json:"user_id"` | ||
| 111 | UserName string `json:"user_name"` | ||
| 112 | UserAvatar string `json:"user_avatar"` | ||
| 113 | ScoreCount int `json:"score_count"` | ||
| 114 | ScoreTime int `json:"score_time"` | ||
| 115 | DemoID string `json:"demo_id"` | ||
| 116 | RecordDate time.Time `json:"record_date"` | ||
| 117 | } | ||
| 118 | |||
| 119 | type RecordMP struct { | ||
| 120 | RecordID int `json:"record_id"` | ||
| 121 | Placement int `json:"placement"` | ||
| 122 | HostID string `json:"host_id"` | ||
| 123 | HostName string `json:"host_name"` | ||
| 124 | HostAvatar string `json:"host_avatar"` | ||
| 125 | PartnerID string `json:"partner_id"` | ||
| 126 | PartnerName string `json:"partner_name"` | ||
| 127 | PartnerAvatar string `json:"partner_avatar"` | ||
| 128 | ScoreCount int `json:"score_count"` | ||
| 129 | ScoreTime int `json:"score_time"` | ||
| 130 | HostDemoID string `json:"host_demo_id"` | ||
| 131 | PartnerDemoID string `json:"partner_demo_id"` | ||
| 132 | RecordDate time.Time `json:"record_date"` | ||
| 133 | } | ||
| 134 | |||
| 135 | type PlayerSummaries struct { | 107 | type PlayerSummaries struct { |
| 136 | SteamId string `json:"steamid"` | 108 | SteamId string `json:"steamid"` |
| 137 | CommunityVisibilityState int `json:"communityvisibilitystate"` | 109 | CommunityVisibilityState int `json:"communityvisibilitystate"` |