aboutsummaryrefslogtreecommitdiff
path: root/backend/handlers/map.go
diff options
context:
space:
mode:
authorArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2023-08-26 08:53:24 +0300
committerArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2023-08-26 08:53:24 +0300
commitf1b7589b2936335957a6a1da1eea3d66233ad0ce (patch)
tree1975af217c190f5dbdb23b96015cef45206302d4 /backend/handlers/map.go
parentdocs: profile improvement swagger (#51) (diff)
downloadlphub-f1b7589b2936335957a6a1da1eea3d66233ad0ce.tar.gz
lphub-f1b7589b2936335957a6a1da1eea3d66233ad0ce.tar.bz2
lphub-f1b7589b2936335957a6a1da1eea3d66233ad0ce.zip
refactor: reorganizing packages
Former-commit-id: 99410223654c2a5ffc15fdab6ec3e921b5410cba
Diffstat (limited to 'backend/handlers/map.go')
-rw-r--r--backend/handlers/map.go314
1 files changed, 314 insertions, 0 deletions
diff --git a/backend/handlers/map.go b/backend/handlers/map.go
new file mode 100644
index 0000000..b47e793
--- /dev/null
+++ b/backend/handlers/map.go
@@ -0,0 +1,314 @@
1package handlers
2
3import (
4 "net/http"
5 "strconv"
6
7 "github.com/gin-gonic/gin"
8 "github.com/pektezol/leastportalshub/backend/database"
9 "github.com/pektezol/leastportalshub/backend/models"
10)
11
12type MapSummaryResponse struct {
13 Map models.Map `json:"map"`
14 Summary models.MapSummary `json:"summary"`
15}
16
17type ChaptersResponse struct {
18 Game models.Game `json:"game"`
19 Chapters []models.Chapter `json:"chapters"`
20}
21
22type ChapterMapsResponse struct {
23 Chapter models.Chapter `json:"chapter"`
24 Maps []models.MapShort `json:"maps"`
25}
26
27// GET Map Summary
28//
29// @Description Get map summary with specified id.
30// @Tags maps
31// @Produce json
32// @Param id path int true "Map ID"
33// @Success 200 {object} models.Response{data=MapSummaryResponse}
34// @Failure 400 {object} models.Response
35// @Router /maps/{id}/summary [get]
36func FetchMapSummary(c *gin.Context) {
37 id := c.Param("id")
38 response := MapSummaryResponse{Map: models.Map{}, Summary: models.MapSummary{Routes: []models.MapRoute{}}}
39 intID, err := strconv.Atoi(id)
40 if err != nil {
41 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
42 return
43 }
44 // Get map data
45 response.Map.ID = intID
46 sql := `SELECT m.id, g.name, c.name, m.name, m.image, g.is_coop
47 FROM maps m
48 INNER JOIN games g ON m.game_id = g.id
49 INNER JOIN chapters c ON m.chapter_id = c.id
50 WHERE m.id = $1`
51 err = database.DB.QueryRow(sql, id).Scan(&response.Map.ID, &response.Map.GameName, &response.Map.ChapterName, &response.Map.MapName, &response.Map.Image, &response.Map.IsCoop)
52 if err != nil {
53 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
54 return
55 }
56 // Get map routes and histories
57 sql = `SELECT r.id, c.id, c.name, h.user_name, h.score_count, h.record_date, r.description, r.showcase, COALESCE(avg(rating), 0.0) FROM map_routes r
58 INNER JOIN categories c ON r.category_id = c.id
59 INNER JOIN map_history h ON r.map_id = h.map_id AND r.category_id = h.category_id
60 LEFT JOIN map_ratings rt ON r.map_id = rt.map_id AND r.category_id = rt.category_id
61 WHERE r.map_id = $1 AND h.score_count = r.score_count GROUP BY r.id, c.id, h.user_name, h.score_count, h.record_date, r.description, r.showcase
62 ORDER BY h.record_date ASC;`
63 rows, err := database.DB.Query(sql, id)
64 if err != nil {
65 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
66 return
67 }
68 for rows.Next() {
69 route := models.MapRoute{Category: models.Category{}, History: models.MapHistory{}}
70 err = rows.Scan(&route.RouteID, &route.Category.ID, &route.Category.Name, &route.History.RunnerName, &route.History.ScoreCount, &route.History.Date, &route.Description, &route.Showcase, &route.Rating)
71 if err != nil {
72 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
73 return
74 }
75 response.Summary.Routes = append(response.Summary.Routes, route)
76 }
77 // Return response
78 c.JSON(http.StatusOK, models.Response{
79 Success: true,
80 Message: "Successfully retrieved map summary.",
81 Data: response,
82 })
83}
84
85// GET Map Leaderboards
86//
87// @Description Get map leaderboards with specified id.
88// @Tags maps
89// @Produce json
90// @Param id path int true "Map ID"
91// @Success 200 {object} models.Response{data=models.Map{data=models.MapRecords}}
92// @Failure 400 {object} models.Response
93// @Router /maps/{id}/leaderboards [get]
94func FetchMapLeaderboards(c *gin.Context) {
95 // TODO: make new response type
96 id := c.Param("id")
97 // Get map data
98 var mapData models.Map
99 var mapRecordsData models.MapRecords
100 var isDisabled bool
101 intID, err := strconv.Atoi(id)
102 if err != nil {
103 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
104 return
105 }
106 mapData.ID = intID
107 sql := `SELECT g.name, c.name, m.name, is_disabled, m.image
108 FROM maps m
109 INNER JOIN games g ON m.game_id = g.id
110 INNER JOIN chapters c ON m.chapter_id = c.id
111 WHERE m.id = $1`
112 err = database.DB.QueryRow(sql, id).Scan(&mapData.GameName, &mapData.ChapterName, &mapData.MapName, &isDisabled, &mapData.Image)
113 if err != nil {
114 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
115 return
116 }
117 if isDisabled {
118 c.JSON(http.StatusBadRequest, models.ErrorResponse("Map is not available for competitive boards."))
119 return
120 }
121 // TODO: avatar and names for host & partner
122 // Get records from the map
123 if mapData.GameName == "Portal 2 - Cooperative" {
124 var records []models.RecordMP
125 sql = `SELECT id, host_id, partner_id, score_count, score_time, host_demo_id, partner_demo_id, record_date
126 FROM (
127 SELECT id, host_id, partner_id, score_count, score_time, host_demo_id, partner_demo_id, record_date,
128 ROW_NUMBER() OVER (PARTITION BY host_id, partner_id ORDER BY score_count, score_time) AS rn
129 FROM records_mp
130 WHERE map_id = $1
131 ) sub
132 WHERE rn = 1`
133 rows, err := database.DB.Query(sql, id)
134 if err != nil {
135 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
136 return
137 }
138 placement := 1
139 ties := 0
140 for rows.Next() {
141 var record models.RecordMP
142 err := rows.Scan(&record.RecordID, &record.HostID, &record.PartnerID, &record.ScoreCount, &record.ScoreTime, &record.HostDemoID, &record.PartnerDemoID, &record.RecordDate)
143 if err != nil {
144 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
145 return
146 }
147 if len(records) != 0 && records[len(records)-1].ScoreTime == record.ScoreTime {
148 ties++
149 record.Placement = placement - ties
150 } else {
151 record.Placement = placement
152 }
153 records = append(records, record)
154 placement++
155 }
156 mapRecordsData.Records = records
157 } else {
158 var records []models.RecordSP
159 sql = `SELECT id, user_id, users.user_name, users.avatar_link, score_count, score_time, demo_id, record_date
160 FROM (
161 SELECT id, user_id, score_count, score_time, demo_id, record_date,
162 ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY score_count, score_time) AS rn
163 FROM records_sp
164 WHERE map_id = $1
165 ) sub
166 INNER JOIN users ON user_id = users.steam_id
167 WHERE rn = 1`
168 rows, err := database.DB.Query(sql, id)
169 if err != nil {
170 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
171 return
172 }
173 placement := 1
174 ties := 0
175 for rows.Next() {
176 var record models.RecordSP
177 err := rows.Scan(&record.RecordID, &record.UserID, &record.UserName, &record.UserAvatar, &record.ScoreCount, &record.ScoreTime, &record.DemoID, &record.RecordDate)
178 if err != nil {
179 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
180 return
181 }
182 if len(records) != 0 && records[len(records)-1].ScoreTime == record.ScoreTime {
183 ties++
184 record.Placement = placement - ties
185 } else {
186 record.Placement = placement
187 }
188 records = append(records, record)
189 placement++
190 }
191 mapRecordsData.Records = records
192 }
193 // mapData.Data = mapRecordsData
194 // Return response
195 c.JSON(http.StatusOK, models.Response{
196 Success: true,
197 Message: "Successfully retrieved map leaderboards.",
198 Data: mapData,
199 })
200}
201
202// GET Games
203//
204// @Description Get games from the leaderboards.
205// @Tags games & chapters
206// @Produce json
207// @Success 200 {object} models.Response{data=[]models.Game}
208// @Failure 400 {object} models.Response
209// @Router /games [get]
210func FetchGames(c *gin.Context) {
211 rows, err := database.DB.Query(`SELECT id, name, is_coop FROM games`)
212 if err != nil {
213 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
214 return
215 }
216 var games []models.Game
217 for rows.Next() {
218 var game models.Game
219 if err := rows.Scan(&game.ID, &game.Name, &game.IsCoop); err != nil {
220 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
221 return
222 }
223 games = append(games, game)
224 }
225 c.JSON(http.StatusOK, models.Response{
226 Success: true,
227 Message: "Successfully retrieved games.",
228 Data: games,
229 })
230}
231
232// GET Chapters of a Game
233//
234// @Description Get chapters from the specified game id.
235// @Tags games & chapters
236// @Produce json
237// @Param id path int true "Game ID"
238// @Success 200 {object} models.Response{data=ChaptersResponse}
239// @Failure 400 {object} models.Response
240// @Router /games/{id} [get]
241func FetchChapters(c *gin.Context) {
242 gameID := c.Param("id")
243 intID, err := strconv.Atoi(gameID)
244 if err != nil {
245 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
246 return
247 }
248 var response ChaptersResponse
249 rows, err := database.DB.Query(`SELECT c.id, c.name, g.name FROM chapters c INNER JOIN games g ON c.game_id = g.id WHERE game_id = $1`, gameID)
250 if err != nil {
251 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
252 return
253 }
254 var chapters []models.Chapter
255 var gameName string
256 for rows.Next() {
257 var chapter models.Chapter
258 if err := rows.Scan(&chapter.ID, &chapter.Name, &gameName); err != nil {
259 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
260 return
261 }
262 chapters = append(chapters, chapter)
263 }
264 response.Game.ID = intID
265 response.Game.Name = gameName
266 response.Chapters = chapters
267 c.JSON(http.StatusOK, models.Response{
268 Success: true,
269 Message: "Successfully retrieved chapters.",
270 Data: response,
271 })
272}
273
274// GET Maps of a Chapter
275//
276// @Description Get maps from the specified chapter id.
277// @Tags games & chapters
278// @Produce json
279// @Param id path int true "Chapter ID"
280// @Success 200 {object} models.Response{data=ChapterMapsResponse}
281// @Failure 400 {object} models.Response
282// @Router /chapters/{id} [get]
283func FetchChapterMaps(c *gin.Context) {
284 chapterID := c.Param("id")
285 intID, err := strconv.Atoi(chapterID)
286 if err != nil {
287 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
288 return
289 }
290 var response ChapterMapsResponse
291 rows, err := database.DB.Query(`SELECT m.id, m.name, c.name FROM maps m INNER JOIN chapters c ON m.chapter_id = c.id WHERE chapter_id = $1`, chapterID)
292 if err != nil {
293 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
294 return
295 }
296 var maps []models.MapShort
297 var chapterName string
298 for rows.Next() {
299 var mapShort models.MapShort
300 if err := rows.Scan(&mapShort.ID, &mapShort.Name, &chapterName); err != nil {
301 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
302 return
303 }
304 maps = append(maps, mapShort)
305 }
306 response.Chapter.ID = intID
307 response.Chapter.Name = chapterName
308 response.Maps = maps
309 c.JSON(http.StatusOK, models.Response{
310 Success: true,
311 Message: "Successfully retrieved maps.",
312 Data: response,
313 })
314}