aboutsummaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
authorArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2023-09-22 23:58:26 +0300
committerGitHub <noreply@github.com>2023-09-22 23:58:26 +0300
commit9cd44de9be62e1291f84763bc029f995301e1e03 (patch)
tree5b4b8bc6d7a89173373fbb32c5a8c5013f8c9d82 /backend
parentfeat: map search (#78) (diff)
downloadlphub-9cd44de9be62e1291f84763bc029f995301e1e03.tar.gz
lphub-9cd44de9be62e1291f84763bc029f995301e1e03.tar.bz2
lphub-9cd44de9be62e1291f84763bc029f995301e1e03.zip
feat: discussions (#59)
Former-commit-id: ac6ac59367650b6a37650f5aec0587c6ce4d3dd1
Diffstat (limited to 'backend')
-rw-r--r--backend/api/routes.go89
-rw-r--r--backend/database/init.sql34
-rw-r--r--backend/handlers/discussions.go291
-rw-r--r--backend/handlers/home.go2
-rw-r--r--backend/handlers/login.go1
-rw-r--r--backend/handlers/logs.go2
-rw-r--r--backend/handlers/map.go35
-rw-r--r--backend/handlers/mod.go28
-rw-r--r--backend/handlers/record.go13
-rw-r--r--backend/handlers/user.go16
10 files changed, 425 insertions, 86 deletions
diff --git a/backend/api/routes.go b/backend/api/routes.go
index fd3b8cc..339dc73 100644
--- a/backend/api/routes.go
+++ b/backend/api/routes.go
@@ -7,35 +7,72 @@ import (
7 ginSwagger "github.com/swaggo/gin-swagger" 7 ginSwagger "github.com/swaggo/gin-swagger"
8) 8)
9 9
10const (
11 apiPath string = "/api"
12 v1Path string = "/v1"
13 swaggerPath string = "/swagger/*any"
14 indexPath string = "/"
15 tokenPath string = "/token"
16 loginPath string = "/login"
17 profilePath string = "/profile"
18 usersPath string = "/users/:userid"
19 demosPath string = "/demos"
20 mapSummaryPath string = "/maps/:mapid/summary"
21 mapImagePath string = "/maps/:mapid/image"
22 mapLeaderboardsPath string = "/maps/:mapid/leaderboards"
23 mapRecordPath string = "/maps/:mapid/record"
24 mapDiscussionsPath string = "/maps/:mapid/discussions"
25 mapDiscussionIDPath string = "/maps/:mapid/discussions/:discussionid"
26 rankingsPath string = "/rankings"
27 searchPath string = "/search"
28 gamesPath string = "/games"
29 chaptersPath string = "/games/:gameid"
30 chapterMapsPath string = "/chapters/:chapterid"
31 scoreLogsPath string = "/logs/score"
32 modLogsPath string = "/logs/mod"
33)
34
10func InitRoutes(router *gin.Engine) { 35func InitRoutes(router *gin.Engine) {
11 api := router.Group("/api") 36 api := router.Group(apiPath)
12 { 37 {
13 v1 := api.Group("/v1") 38 v1 := api.Group(v1Path)
14 v1.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) 39 // Swagger
15 v1.GET("/", func(c *gin.Context) { 40 v1.GET(swaggerPath, ginSwagger.WrapHandler(swaggerfiles.Handler))
41 v1.GET(indexPath, func(c *gin.Context) {
16 c.File("docs/index.html") 42 c.File("docs/index.html")
17 }) 43 })
18 v1.GET("/token", handlers.GetCookie) 44 // Tokens, login
19 v1.DELETE("/token", handlers.DeleteCookie) 45 v1.GET(tokenPath, handlers.GetCookie)
20 v1.GET("/login", handlers.Login) 46 v1.DELETE(tokenPath, handlers.DeleteCookie)
21 v1.GET("/profile", CheckAuth, handlers.Profile) 47 v1.GET(loginPath, handlers.Login)
22 v1.PUT("/profile", CheckAuth, handlers.UpdateCountryCode) 48 // Users, profiles
23 v1.POST("/profile", CheckAuth, handlers.UpdateUser) 49 v1.GET(profilePath, CheckAuth, handlers.Profile)
24 v1.GET("/users/:id", CheckAuth, handlers.FetchUser) 50 v1.PUT(profilePath, CheckAuth, handlers.UpdateCountryCode)
25 v1.GET("/demos", handlers.DownloadDemoWithID) 51 v1.POST(profilePath, CheckAuth, handlers.UpdateUser)
26 v1.GET("/maps/:id/summary", handlers.FetchMapSummary) 52 v1.GET(usersPath, CheckAuth, handlers.FetchUser)
27 v1.POST("/maps/:id/summary", CheckAuth, handlers.CreateMapSummary) 53 // Maps
28 v1.PUT("/maps/:id/summary", CheckAuth, handlers.EditMapSummary) 54 v1.GET(mapSummaryPath, handlers.FetchMapSummary)
29 v1.DELETE("/maps/:id/summary", CheckAuth, handlers.DeleteMapSummary) 55 v1.POST(mapSummaryPath, CheckAuth, handlers.CreateMapSummary)
30 v1.PUT("/maps/:id/image", CheckAuth, handlers.EditMapImage) 56 v1.PUT(mapSummaryPath, CheckAuth, handlers.EditMapSummary)
31 v1.GET("/maps/:id/leaderboards", handlers.FetchMapLeaderboards) 57 v1.DELETE(mapSummaryPath, CheckAuth, handlers.DeleteMapSummary)
32 v1.POST("/maps/:id/record", CheckAuth, handlers.CreateRecordWithDemo) 58 v1.PUT(mapImagePath, CheckAuth, handlers.EditMapImage)
33 v1.GET("/rankings", handlers.Rankings) 59 v1.GET(mapLeaderboardsPath, handlers.FetchMapLeaderboards)
34 v1.GET("/search", handlers.SearchWithQuery) 60 v1.POST(mapRecordPath, CheckAuth, handlers.CreateRecordWithDemo)
35 v1.GET("/games", handlers.FetchGames) 61 v1.GET(demosPath, handlers.DownloadDemoWithID)
36 v1.GET("/games/:id", handlers.FetchChapters) 62 v1.GET(mapDiscussionsPath, handlers.FetchMapDiscussions)
37 v1.GET("/chapters/:id", handlers.FetchChapterMaps) 63 v1.GET(mapDiscussionIDPath, handlers.FetchMapDiscussion)
38 v1.GET("/logs/score", handlers.ScoreLogs) 64 v1.POST(mapDiscussionsPath, CheckAuth, handlers.CreateMapDiscussion)
39 v1.GET("/logs/mod", CheckAuth, handlers.ModLogs) 65 v1.PUT(mapDiscussionIDPath, CheckAuth, handlers.EditMapDiscussion)
66 v1.DELETE(mapDiscussionIDPath, CheckAuth, handlers.DeleteMapDiscussion)
67 // Rankings, search
68 v1.GET(rankingsPath, handlers.Rankings)
69 v1.GET(searchPath, handlers.SearchWithQuery)
70 // Games, chapters, maps
71 v1.GET(gamesPath, handlers.FetchGames)
72 v1.GET(chaptersPath, handlers.FetchChapters)
73 v1.GET(chapterMapsPath, handlers.FetchChapterMaps)
74 // Logs
75 v1.GET(scoreLogsPath, handlers.ScoreLogs)
76 v1.GET(modLogsPath, CheckAuth, handlers.ModLogs)
40 } 77 }
41} 78}
diff --git a/backend/database/init.sql b/backend/database/init.sql
index abace5c..0f8196b 100644
--- a/backend/database/init.sql
+++ b/backend/database/init.sql
@@ -82,6 +82,40 @@ CREATE TABLE map_ratings (
82 FOREIGN KEY (user_id) REFERENCES users(steam_id) 82 FOREIGN KEY (user_id) REFERENCES users(steam_id)
83); 83);
84 84
85CREATE TABLE map_discussions (
86 id SERIAL,
87 map_id SMALLINT NOT NULL,
88 user_id TEXT NOT NULL,
89 title TEXT NOT NULL,
90 content TEXT NOT NULL,
91 created_at TIMESTAMP NOT NULL DEFAULT now(),
92 updated_at TIMESTAMP NOT NULL DEFAULT now(),
93 PRIMARY KEY (id),
94 FOREIGN KEY (map_id) REFERENCES maps(id),
95 FOREIGN KEY (user_id) REFERENCES users(steam_id)
96);
97
98CREATE TABLE map_discussions_comments (
99 id SERIAL,
100 discussion_id INT NOT NULL,
101 user_id TEXT NOT NULL,
102 comment TEXT NOT NULL,
103 created_at TIMESTAMP NOT NULL DEFAULT now(),
104 PRIMARY KEY (id),
105 FOREIGN KEY (discussion_id) REFERENCES map_discussions(id),
106 FOREIGN KEY (user_id) REFERENCES users(steam_id)
107);
108
109CREATE TABLE map_discussions_upvotes (
110 id SERIAL,
111 discussion_id INT NOT NULL,
112 user_id TEXT NOT NULL,
113 upvoted BOOLEAN NOT NULL,
114 PRIMARY KEY (id),
115 FOREIGN KEY (discussion_id) REFERENCES map_discussions(id),
116 FOREIGN KEY (user_id) REFERENCES users(steam_id)
117);
118
85CREATE TABLE demos ( 119CREATE TABLE demos (
86 id UUID, 120 id UUID,
87 location_id TEXT NOT NULL, 121 location_id TEXT NOT NULL,
diff --git a/backend/handlers/discussions.go b/backend/handlers/discussions.go
new file mode 100644
index 0000000..605c7c3
--- /dev/null
+++ b/backend/handlers/discussions.go
@@ -0,0 +1,291 @@
1package handlers
2
3import (
4 "net/http"
5 "strconv"
6 "time"
7
8 "github.com/gin-gonic/gin"
9 "github.com/pektezol/leastportalshub/backend/database"
10 "github.com/pektezol/leastportalshub/backend/models"
11)
12
13type MapDiscussionResponse struct {
14 Discussion MapDiscussion `json:"discussion"`
15}
16
17type MapDiscussionsResponse struct {
18 Discussions []MapDiscussionOnlyTitle `json:"discussions"`
19}
20
21type MapDiscussion struct {
22 ID int `json:"id"`
23 Creator models.UserShortWithAvatar `json:"creator"`
24 Title string `json:"title"`
25 Content string `json:"content"`
26 // Upvotes int `json:"upvotes"`
27 UpdatedAt time.Time `json:"updated_at"`
28 Comments []MapDiscussionComment `json:"comments"`
29}
30
31type MapDiscussionOnlyTitle struct {
32 ID int `json:"id"`
33 Creator models.UserShortWithAvatar `json:"creator"`
34 Title string `json:"title"`
35 // Upvotes int `json:"upvotes"`
36 UpdatedAt time.Time `json:"updated_at"`
37 Comments []MapDiscussionComment `json:"comments"`
38}
39
40type MapDiscussionComment struct {
41 User models.UserShortWithAvatar `json:"user"`
42 Comment string `json:"comment"`
43 Date time.Time `json:"date"`
44}
45
46type CreateMapDiscussionRequest struct {
47 Title string `json:"title" binding:"required"`
48 Content string `json:"content" binding:"required"`
49}
50
51type EditMapDiscussionRequest struct {
52 Title string `json:"title" binding:"required"`
53 Content string `json:"content" binding:"required"`
54}
55
56// GET Map Discussions
57//
58// @Description Get map discussions with specified map id.
59// @Tags maps
60// @Produce json
61// @Param mapid path int true "Map ID"
62// @Success 200 {object} models.Response{data=MapDiscussionsResponse}
63// @Router /maps/{mapid}/discussions [get]
64func FetchMapDiscussions(c *gin.Context) {
65 // TODO: get upvotes
66 response := MapDiscussionsResponse{}
67 mapID, err := strconv.Atoi(c.Param("mapid"))
68 if err != nil {
69 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
70 return
71 }
72 sql := `SELECT md.id, u.steam_id, u.user_name, u.avatar_link, md.title, md.updated_at FROM map_discussions md
73 INNER JOIN users u ON md.user_id = u.steam_id WHERE md.map_id = $1
74 ORDER BY md.updated_at DESC`
75 rows, err := database.DB.Query(sql, mapID)
76 if err != nil {
77 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
78 return
79 }
80 // Get discussion data
81 for rows.Next() {
82 discussion := MapDiscussionOnlyTitle{}
83 err := rows.Scan(&discussion.ID, &discussion.Creator.SteamID, &discussion.Creator.UserName, &discussion.Creator.AvatarLink, &discussion.Title, &discussion.UpdatedAt)
84 if err != nil {
85 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
86 return
87 }
88 response.Discussions = append(response.Discussions, discussion)
89 }
90 c.JSON(http.StatusOK, models.Response{
91 Success: true,
92 Message: "Successfully retrieved map discussions.",
93 Data: response,
94 })
95}
96
97// GET Map Discussion
98//
99// @Description Get map discussion with specified map and discussion id.
100// @Tags maps
101// @Produce json
102// @Param mapid path int true "Map ID"
103// @Param discussionid path int true "Discussion ID"
104// @Success 200 {object} models.Response{data=MapDiscussionResponse}
105// @Router /maps/{mapid}/discussions/{discussionid} [get]
106func FetchMapDiscussion(c *gin.Context) {
107 // TODO: get upvotes
108 response := MapDiscussionResponse{}
109 mapID, err := strconv.Atoi(c.Param("mapid"))
110 if err != nil {
111 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
112 return
113 }
114 discussionID, err := strconv.Atoi(c.Param("discussionid"))
115 if err != nil {
116 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
117 return
118 }
119 sql := `SELECT md.id, u.steam_id, u.user_name, u.avatar_link, md.title, md.content, md.updated_at FROM map_discussions md
120 INNER JOIN users u ON md.user_id = u.steam_id WHERE md.map_id = $1 AND md.id = $2`
121 err = database.DB.QueryRow(sql, mapID, discussionID).Scan(&response.Discussion.ID, &response.Discussion.Creator.SteamID, &response.Discussion.Creator.UserName, &response.Discussion.Creator.AvatarLink, &response.Discussion.Title, &response.Discussion.Content, &response.Discussion.UpdatedAt)
122 if err != nil {
123 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
124 return
125 }
126 // Get commments
127 sql = `SELECT u.steam_id, u.user_name, u.avatar_link, mdc.comment, mdc.created_at FROM map_discussions_comments mdc
128 INNER JOIN users u ON mdc.user_id = u.steam_id WHERE mdc.discussion_id = $1`
129 comments, err := database.DB.Query(sql, response.Discussion.ID)
130 if err != nil {
131 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
132 return
133 }
134 for comments.Next() {
135 comment := MapDiscussionComment{}
136 err = comments.Scan(&comment.User.SteamID, &comment.User.UserName, &comment.User.AvatarLink, &comment.Comment, &comment.Date)
137 if err != nil {
138 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
139 return
140 }
141 response.Discussion.Comments = append(response.Discussion.Comments, comment)
142 }
143 c.JSON(http.StatusOK, models.Response{
144 Success: true,
145 Message: "Successfully retrieved map discussion.",
146 Data: response,
147 })
148}
149
150// POST Map Discussion
151//
152// @Description Create map discussion with specified map id.
153// @Tags maps
154// @Produce json
155// @Param Authorization header string true "JWT Token"
156// @Param mapid path int true "Map ID"
157// @Param discussionid path int true "Discussion ID"
158// @Param request body CreateMapDiscussionRequest true "Body"
159// @Success 200 {object} models.Response{data=CreateMapDiscussionRequest}
160// @Router /maps/{mapid}/discussions [post]
161func CreateMapDiscussion(c *gin.Context) {
162 mapID, err := strconv.Atoi(c.Param("mapid"))
163 if err != nil {
164 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
165 return
166 }
167 user, exists := c.Get("user")
168 if !exists {
169 c.JSON(http.StatusOK, models.ErrorResponse("User not logged in."))
170 return
171 }
172 var request CreateMapDiscussionRequest
173 if err := c.BindJSON(&request); err != nil {
174 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
175 return
176 }
177 sql := `INSERT INTO map_discussions (map_id,user_id,title,"content")
178 VALUES($1,$2,$3,$4);`
179 _, err = database.DB.Exec(sql, mapID, user.(models.User).SteamID, request.Title, request.Content)
180 if err != nil {
181 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
182 return
183 }
184 c.JSON(http.StatusOK, models.Response{
185 Success: true,
186 Message: "Successfully created map discussion.",
187 Data: request,
188 })
189}
190
191// PUT Map Discussion
192//
193// @Description Edit map discussion with specified map id.
194// @Tags maps
195// @Produce json
196// @Param Authorization header string true "JWT Token"
197// @Param mapid path int true "Map ID"
198// @Param discussionid path int true "Discussion ID"
199// @Param request body EditMapDiscussionRequest true "Body"
200// @Success 200 {object} models.Response{data=EditMapDiscussionRequest}
201// @Router /maps/{mapid}/discussions/{discussionid} [put]
202func EditMapDiscussion(c *gin.Context) {
203 mapID, err := strconv.Atoi(c.Param("mapid"))
204 if err != nil {
205 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
206 return
207 }
208 discussionID, err := strconv.Atoi(c.Param("discussionid"))
209 if err != nil {
210 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
211 return
212 }
213 user, exists := c.Get("user")
214 if !exists {
215 c.JSON(http.StatusOK, models.ErrorResponse("User not logged in."))
216 return
217 }
218 var request EditMapDiscussionRequest
219 if err := c.BindJSON(&request); err != nil {
220 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
221 return
222 }
223 sql := `UPDATE map_discussions SET title = $4, content = $5, updated_at = $6 WHERE id = $1 AND map_id = $2 AND user_id = $3`
224 result, err := database.DB.Exec(sql, discussionID, mapID, user.(models.User).SteamID, request.Title, request.Content, time.Now().UTC())
225 if err != nil {
226 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
227 return
228 }
229 affectedRows, err := result.RowsAffected()
230 if err != nil {
231 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
232 return
233 }
234 if affectedRows == 0 {
235 c.JSON(http.StatusOK, models.ErrorResponse("You can only edit your own post."))
236 return
237 }
238 c.JSON(http.StatusOK, models.Response{
239 Success: true,
240 Message: "Successfully edited map discussion.",
241 Data: request,
242 })
243}
244
245// DELETE Map Summary
246//
247// @Description Delete map summary with specified map id.
248// @Tags maps
249// @Produce json
250// @Param Authorization header string true "JWT Token"
251// @Param mapid path int true "Map ID"
252// @Param discussionid path int true "Discussion ID"
253// @Success 200 {object} models.Response
254// @Router /maps/{mapid}/discussions/{discussionid} [delete]
255func DeleteMapDiscussion(c *gin.Context) {
256 mapID, err := strconv.Atoi(c.Param("mapid"))
257 if err != nil {
258 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
259 return
260 }
261 discussionID, err := strconv.Atoi(c.Param("discussionid"))
262 if err != nil {
263 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
264 return
265 }
266 user, exists := c.Get("user")
267 if !exists {
268 c.JSON(http.StatusOK, models.ErrorResponse("User not logged in."))
269 return
270 }
271 sql := `DELETE FROM map_discussions WHERE id = $1 AND map_id = $2 AND user_id = $3`
272 result, err := database.DB.Exec(sql, discussionID, mapID, user.(models.User).SteamID)
273 if err != nil {
274 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
275 return
276 }
277 affectedRows, err := result.RowsAffected()
278 if err != nil {
279 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
280 return
281 }
282 if affectedRows == 0 {
283 c.JSON(http.StatusOK, models.ErrorResponse("You can only delete your own post."))
284 return
285 }
286 c.JSON(http.StatusOK, models.Response{
287 Success: true,
288 Message: "Successfully deleted map discussion.",
289 Data: nil,
290 })
291}
diff --git a/backend/handlers/home.go b/backend/handlers/home.go
index 42e27be..c9742f2 100644
--- a/backend/handlers/home.go
+++ b/backend/handlers/home.go
@@ -35,7 +35,6 @@ type MapShortWithGame struct {
35// @Tags rankings 35// @Tags rankings
36// @Produce json 36// @Produce json
37// @Success 200 {object} models.Response{data=RankingsResponse} 37// @Success 200 {object} models.Response{data=RankingsResponse}
38// @Failure 400 {object} models.Response
39// @Router /rankings [get] 38// @Router /rankings [get]
40func Rankings(c *gin.Context) { 39func Rankings(c *gin.Context) {
41 response := RankingsResponse{ 40 response := RankingsResponse{
@@ -142,7 +141,6 @@ func Rankings(c *gin.Context) {
142// @Produce json 141// @Produce json
143// @Param q query string false "Search user or map name." 142// @Param q query string false "Search user or map name."
144// @Success 200 {object} models.Response{data=SearchResponse} 143// @Success 200 {object} models.Response{data=SearchResponse}
145// @Failure 400 {object} models.Response
146// @Router /search [get] 144// @Router /search [get]
147func SearchWithQuery(c *gin.Context) { 145func SearchWithQuery(c *gin.Context) {
148 query := c.Query("q") 146 query := c.Query("q")
diff --git a/backend/handlers/login.go b/backend/handlers/login.go
index a7e4379..dc6e4a5 100644
--- a/backend/handlers/login.go
+++ b/backend/handlers/login.go
@@ -26,7 +26,6 @@ type LoginResponse struct {
26// @Accept json 26// @Accept json
27// @Produce json 27// @Produce json
28// @Success 200 {object} models.Response{data=LoginResponse} 28// @Success 200 {object} models.Response{data=LoginResponse}
29// @Failure 400 {object} models.Response
30// @Router /login [get] 29// @Router /login [get]
31func Login(c *gin.Context) { 30func Login(c *gin.Context) {
32 openID := steam_go.NewOpenId(c.Request) 31 openID := steam_go.NewOpenId(c.Request)
diff --git a/backend/handlers/logs.go b/backend/handlers/logs.go
index b6bcef6..6f59238 100644
--- a/backend/handlers/logs.go
+++ b/backend/handlers/logs.go
@@ -78,7 +78,6 @@ type ScoreLogsResponseDetails struct {
78// @Produce json 78// @Produce json
79// @Param Authorization header string true "JWT Token" 79// @Param Authorization header string true "JWT Token"
80// @Success 200 {object} models.Response{data=LogsResponse} 80// @Success 200 {object} models.Response{data=LogsResponse}
81// @Failure 400 {object} models.Response
82// @Router /logs/mod [get] 81// @Router /logs/mod [get]
83func ModLogs(c *gin.Context) { 82func ModLogs(c *gin.Context) {
84 mod, exists := c.Get("mod") 83 mod, exists := c.Get("mod")
@@ -125,7 +124,6 @@ func ModLogs(c *gin.Context) {
125// @Tags logs 124// @Tags logs
126// @Produce json 125// @Produce json
127// @Success 200 {object} models.Response{data=ScoreLogsResponse} 126// @Success 200 {object} models.Response{data=ScoreLogsResponse}
128// @Failure 400 {object} models.Response
129// @Router /logs/score [get] 127// @Router /logs/score [get]
130func ScoreLogs(c *gin.Context) { 128func ScoreLogs(c *gin.Context) {
131 response := ScoreLogsResponse{Logs: []ScoreLogsResponseDetails{}} 129 response := ScoreLogsResponse{Logs: []ScoreLogsResponseDetails{}}
diff --git a/backend/handlers/map.go b/backend/handlers/map.go
index f3198ff..3bf14cd 100644
--- a/backend/handlers/map.go
+++ b/backend/handlers/map.go
@@ -58,12 +58,11 @@ type RecordMultiplayer struct {
58// @Description Get map summary with specified id. 58// @Description Get map summary with specified id.
59// @Tags maps 59// @Tags maps
60// @Produce json 60// @Produce json
61// @Param id path int true "Map ID" 61// @Param mapid path int true "Map ID"
62// @Success 200 {object} models.Response{data=MapSummaryResponse} 62// @Success 200 {object} models.Response{data=MapSummaryResponse}
63// @Failure 400 {object} models.Response 63// @Router /maps/{mapid}/summary [get]
64// @Router /maps/{id}/summary [get]
65func FetchMapSummary(c *gin.Context) { 64func FetchMapSummary(c *gin.Context) {
66 id := c.Param("id") 65 id := c.Param("mapid")
67 response := MapSummaryResponse{Map: models.Map{}, Summary: models.MapSummary{Routes: []models.MapRoute{}}} 66 response := MapSummaryResponse{Map: models.Map{}, Summary: models.MapSummary{Routes: []models.MapRoute{}}}
68 intID, err := strconv.Atoi(id) 67 intID, err := strconv.Atoi(id)
69 if err != nil { 68 if err != nil {
@@ -138,14 +137,13 @@ func FetchMapSummary(c *gin.Context) {
138// @Description Get map leaderboards with specified id. 137// @Description Get map leaderboards with specified id.
139// @Tags maps 138// @Tags maps
140// @Produce json 139// @Produce json
141// @Param id path int true "Map ID" 140// @Param mapid path int true "Map ID"
142// @Param page query int false "Page Number (default: 1)" 141// @Param page query int false "Page Number (default: 1)"
143// @Param pageSize query int false "Number of Records Per Page (default: 20)" 142// @Param pageSize query int false "Number of Records Per Page (default: 20)"
144// @Success 200 {object} models.Response{data=MapLeaderboardsResponse} 143// @Success 200 {object} models.Response{data=MapLeaderboardsResponse}
145// @Failure 400 {object} models.Response 144// @Router /maps/{mapid}/leaderboards [get]
146// @Router /maps/{id}/leaderboards [get]
147func FetchMapLeaderboards(c *gin.Context) { 145func FetchMapLeaderboards(c *gin.Context) {
148 id := c.Param("id") 146 id := c.Param("mapid")
149 // Get map data 147 // Get map data
150 response := MapLeaderboardsResponse{Map: models.Map{}, Records: nil, Pagination: models.Pagination{}} 148 response := MapLeaderboardsResponse{Map: models.Map{}, Records: nil, Pagination: models.Pagination{}}
151 page, err := strconv.Atoi(c.DefaultQuery("page", "1")) 149 page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
@@ -349,12 +347,11 @@ func FetchGames(c *gin.Context) {
349// @Description Get chapters from the specified game id. 347// @Description Get chapters from the specified game id.
350// @Tags games & chapters 348// @Tags games & chapters
351// @Produce json 349// @Produce json
352// @Param id path int true "Game ID" 350// @Param gameid path int true "Game ID"
353// @Success 200 {object} models.Response{data=ChaptersResponse} 351// @Success 200 {object} models.Response{data=ChaptersResponse}
354// @Failure 400 {object} models.Response 352// @Router /games/{gameid} [get]
355// @Router /games/{id} [get]
356func FetchChapters(c *gin.Context) { 353func FetchChapters(c *gin.Context) {
357 gameID := c.Param("id") 354 gameID := c.Param("gameid")
358 intID, err := strconv.Atoi(gameID) 355 intID, err := strconv.Atoi(gameID)
359 if err != nil { 356 if err != nil {
360 c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) 357 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
@@ -391,12 +388,12 @@ func FetchChapters(c *gin.Context) {
391// @Description Get maps from the specified chapter id. 388// @Description Get maps from the specified chapter id.
392// @Tags games & chapters 389// @Tags games & chapters
393// @Produce json 390// @Produce json
394// @Param id path int true "Chapter ID" 391// @Param chapterid path int true "Chapter ID"
395// @Success 200 {object} models.Response{data=ChapterMapsResponse} 392// @Success 200 {object} models.Response{data=ChapterMapsResponse}
396// @Failure 400 {object} models.Response 393// @Failure 400 {object} models.Response
397// @Router /chapters/{id} [get] 394// @Router /chapters/{chapterid} [get]
398func FetchChapterMaps(c *gin.Context) { 395func FetchChapterMaps(c *gin.Context) {
399 chapterID := c.Param("id") 396 chapterID := c.Param("chapterid")
400 intID, err := strconv.Atoi(chapterID) 397 intID, err := strconv.Atoi(chapterID)
401 if err != nil { 398 if err != nil {
402 c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) 399 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
diff --git a/backend/handlers/mod.go b/backend/handlers/mod.go
index 1d04a96..3559887 100644
--- a/backend/handlers/mod.go
+++ b/backend/handlers/mod.go
@@ -42,11 +42,10 @@ type EditMapImageRequest struct {
42// @Tags maps 42// @Tags maps
43// @Produce json 43// @Produce json
44// @Param Authorization header string true "JWT Token" 44// @Param Authorization header string true "JWT Token"
45// @Param id path int true "Map ID" 45// @Param mapid path int true "Map ID"
46// @Param request body CreateMapSummaryRequest true "Body" 46// @Param request body CreateMapSummaryRequest true "Body"
47// @Success 200 {object} models.Response{data=CreateMapSummaryRequest} 47// @Success 200 {object} models.Response{data=CreateMapSummaryRequest}
48// @Failure 400 {object} models.Response 48// @Router /maps/{mapid}/summary [post]
49// @Router /maps/{id}/summary [post]
50func CreateMapSummary(c *gin.Context) { 49func CreateMapSummary(c *gin.Context) {
51 // Check if user exists 50 // Check if user exists
52 user, exists := c.Get("user") 51 user, exists := c.Get("user")
@@ -60,7 +59,7 @@ func CreateMapSummary(c *gin.Context) {
60 return 59 return
61 } 60 }
62 // Bind parameter and body 61 // Bind parameter and body
63 id := c.Param("id") 62 id := c.Param("mapid")
64 mapID, err := strconv.Atoi(id) 63 mapID, err := strconv.Atoi(id)
65 if err != nil { 64 if err != nil {
66 c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) 65 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
@@ -123,11 +122,10 @@ func CreateMapSummary(c *gin.Context) {
123// @Tags maps 122// @Tags maps
124// @Produce json 123// @Produce json
125// @Param Authorization header string true "JWT Token" 124// @Param Authorization header string true "JWT Token"
126// @Param id path int true "Map ID" 125// @Param mapid path int true "Map ID"
127// @Param request body EditMapSummaryRequest true "Body" 126// @Param request body EditMapSummaryRequest true "Body"
128// @Success 200 {object} models.Response{data=EditMapSummaryRequest} 127// @Success 200 {object} models.Response{data=EditMapSummaryRequest}
129// @Failure 400 {object} models.Response 128// @Router /maps/{mapid}/summary [put]
130// @Router /maps/{id}/summary [put]
131func EditMapSummary(c *gin.Context) { 129func EditMapSummary(c *gin.Context) {
132 // Check if user exists 130 // Check if user exists
133 user, exists := c.Get("user") 131 user, exists := c.Get("user")
@@ -141,7 +139,7 @@ func EditMapSummary(c *gin.Context) {
141 return 139 return
142 } 140 }
143 // Bind parameter and body 141 // Bind parameter and body
144 id := c.Param("id") 142 id := c.Param("mapid")
145 mapID, err := strconv.Atoi(id) 143 mapID, err := strconv.Atoi(id)
146 if err != nil { 144 if err != nil {
147 c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) 145 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
@@ -204,11 +202,10 @@ func EditMapSummary(c *gin.Context) {
204// @Tags maps 202// @Tags maps
205// @Produce json 203// @Produce json
206// @Param Authorization header string true "JWT Token" 204// @Param Authorization header string true "JWT Token"
207// @Param id path int true "Map ID" 205// @Param mapid path int true "Map ID"
208// @Param request body DeleteMapSummaryRequest true "Body" 206// @Param request body DeleteMapSummaryRequest true "Body"
209// @Success 200 {object} models.Response{data=DeleteMapSummaryRequest} 207// @Success 200 {object} models.Response{data=DeleteMapSummaryRequest}
210// @Failure 400 {object} models.Response 208// @Router /maps/{mapid}/summary [delete]
211// @Router /maps/{id}/summary [delete]
212func DeleteMapSummary(c *gin.Context) { 209func DeleteMapSummary(c *gin.Context) {
213 // Check if user exists 210 // Check if user exists
214 user, exists := c.Get("user") 211 user, exists := c.Get("user")
@@ -222,7 +219,7 @@ func DeleteMapSummary(c *gin.Context) {
222 return 219 return
223 } 220 }
224 // Bind parameter and body 221 // Bind parameter and body
225 id := c.Param("id") 222 id := c.Param("mapid")
226 mapID, err := strconv.Atoi(id) 223 mapID, err := strconv.Atoi(id)
227 if err != nil { 224 if err != nil {
228 c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) 225 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
@@ -289,11 +286,10 @@ func DeleteMapSummary(c *gin.Context) {
289// @Tags maps 286// @Tags maps
290// @Produce json 287// @Produce json
291// @Param Authorization header string true "JWT Token" 288// @Param Authorization header string true "JWT Token"
292// @Param id path int true "Map ID" 289// @Param mapid path int true "Map ID"
293// @Param request body EditMapImageRequest true "Body" 290// @Param request body EditMapImageRequest true "Body"
294// @Success 200 {object} models.Response{data=EditMapImageRequest} 291// @Success 200 {object} models.Response{data=EditMapImageRequest}
295// @Failure 400 {object} models.Response 292// @Router /maps/{mapid}/image [put]
296// @Router /maps/{id}/image [put]
297func EditMapImage(c *gin.Context) { 293func EditMapImage(c *gin.Context) {
298 // Check if user exists 294 // Check if user exists
299 user, exists := c.Get("user") 295 user, exists := c.Get("user")
@@ -307,7 +303,7 @@ func EditMapImage(c *gin.Context) {
307 return 303 return
308 } 304 }
309 // Bind parameter and body 305 // Bind parameter and body
310 id := c.Param("id") 306 id := c.Param("mapid")
311 mapID, err := strconv.Atoi(id) 307 mapID, err := strconv.Atoi(id)
312 if err != nil { 308 if err != nil {
313 c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) 309 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
diff --git a/backend/handlers/record.go b/backend/handlers/record.go
index 75b5583..70234c3 100644
--- a/backend/handlers/record.go
+++ b/backend/handlers/record.go
@@ -37,18 +37,16 @@ type RecordResponse struct {
37// @Tags maps 37// @Tags maps
38// @Accept mpfd 38// @Accept mpfd
39// @Produce json 39// @Produce json
40// @Param id path int true "Map ID" 40// @Param mapid path int true "Map ID"
41// @Param Authorization header string true "JWT Token" 41// @Param Authorization header string true "JWT Token"
42// @Param host_demo formData file true "Host Demo" 42// @Param host_demo formData file true "Host Demo"
43// @Param partner_demo formData file false "Partner Demo" 43// @Param partner_demo formData file false "Partner Demo"
44// @Param is_partner_orange formData boolean false "Is Partner Orange" 44// @Param is_partner_orange formData boolean false "Is Partner Orange"
45// @Param partner_id formData string false "Partner ID" 45// @Param partner_id formData string false "Partner ID"
46// @Success 200 {object} models.Response{data=RecordResponse} 46// @Success 200 {object} models.Response{data=RecordResponse}
47// @Failure 400 {object} models.Response 47// @Router /maps/{mapid}/record [post]
48// @Failure 401 {object} models.Response
49// @Router /maps/{id}/record [post]
50func CreateRecordWithDemo(c *gin.Context) { 48func CreateRecordWithDemo(c *gin.Context) {
51 mapId := c.Param("id") 49 mapId := c.Param("mapid")
52 // Check if user exists 50 // Check if user exists
53 user, exists := c.Get("user") 51 user, exists := c.Get("user")
54 if !exists { 52 if !exists {
@@ -216,9 +214,8 @@ func CreateRecordWithDemo(c *gin.Context) {
216// @Tags demo 214// @Tags demo
217// @Accept json 215// @Accept json
218// @Produce octet-stream 216// @Produce octet-stream
219// @Param uuid query string true "Demo UUID" 217// @Param uuid query string true "Demo UUID"
220// @Success 200 {file} binary "Demo File" 218// @Success 200 {file} binary "Demo File"
221// @Failure 400 {object} models.Response
222// @Router /demos [get] 219// @Router /demos [get]
223func DownloadDemoWithID(c *gin.Context) { 220func DownloadDemoWithID(c *gin.Context) {
224 uuid := c.Query("uuid") 221 uuid := c.Query("uuid")
diff --git a/backend/handlers/user.go b/backend/handlers/user.go
index 2df2040..f04145e 100644
--- a/backend/handlers/user.go
+++ b/backend/handlers/user.go
@@ -63,8 +63,6 @@ type ScoreResponse struct {
63// @Produce json 63// @Produce json
64// @Param Authorization header string true "JWT Token" 64// @Param Authorization header string true "JWT Token"
65// @Success 200 {object} models.Response{data=ProfileResponse} 65// @Success 200 {object} models.Response{data=ProfileResponse}
66// @Failure 400 {object} models.Response
67// @Failure 401 {object} models.Response
68// @Router /profile [get] 66// @Router /profile [get]
69func Profile(c *gin.Context) { 67func Profile(c *gin.Context) {
70 // Check if user exists 68 // Check if user exists
@@ -343,13 +341,11 @@ func Profile(c *gin.Context) {
343// @Tags users 341// @Tags users
344// @Accept json 342// @Accept json
345// @Produce json 343// @Produce json
346// @Param id path int true "User ID" 344// @Param userid path int true "User ID"
347// @Success 200 {object} models.Response{data=ProfileResponse} 345// @Success 200 {object} models.Response{data=ProfileResponse}
348// @Failure 400 {object} models.Response 346// @Router /users/{userid} [get]
349// @Failure 404 {object} models.Response
350// @Router /users/{id} [get]
351func FetchUser(c *gin.Context) { 347func FetchUser(c *gin.Context) {
352 id := c.Param("id") 348 id := c.Param("userid")
353 // Check if id is all numbers and 17 length 349 // Check if id is all numbers and 17 length
354 match, _ := regexp.MatchString("^[0-9]{17}$", id) 350 match, _ := regexp.MatchString("^[0-9]{17}$", id)
355 if !match { 351 if !match {
@@ -634,8 +630,6 @@ func FetchUser(c *gin.Context) {
634// @Produce json 630// @Produce json
635// @Param Authorization header string true "JWT Token" 631// @Param Authorization header string true "JWT Token"
636// @Success 200 {object} models.Response{data=ProfileResponse} 632// @Success 200 {object} models.Response{data=ProfileResponse}
637// @Failure 400 {object} models.Response
638// @Failure 401 {object} models.Response
639// @Router /profile [post] 633// @Router /profile [post]
640func UpdateUser(c *gin.Context) { 634func UpdateUser(c *gin.Context) {
641 // Check if user exists 635 // Check if user exists
@@ -681,8 +675,6 @@ func UpdateUser(c *gin.Context) {
681// @Param Authorization header string true "JWT Token" 675// @Param Authorization header string true "JWT Token"
682// @Param country_code query string true "Country Code [XX]" 676// @Param country_code query string true "Country Code [XX]"
683// @Success 200 {object} models.Response 677// @Success 200 {object} models.Response
684// @Failure 400 {object} models.Response
685// @Failure 401 {object} models.Response
686// @Router /profile [put] 678// @Router /profile [put]
687func UpdateCountryCode(c *gin.Context) { 679func UpdateCountryCode(c *gin.Context) {
688 // Check if user exists 680 // Check if user exists