From 9cd44de9be62e1291f84763bc029f995301e1e03 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Fri, 22 Sep 2023 23:58:26 +0300 Subject: feat: discussions (#59) Former-commit-id: ac6ac59367650b6a37650f5aec0587c6ce4d3dd1 --- backend/api/routes.go | 89 +++++-- backend/database/init.sql | 34 +++ backend/handlers/discussions.go | 291 ++++++++++++++++++++++ backend/handlers/home.go | 2 - backend/handlers/login.go | 1 - backend/handlers/logs.go | 2 - backend/handlers/map.go | 35 ++- backend/handlers/mod.go | 28 +-- backend/handlers/record.go | 13 +- backend/handlers/user.go | 16 +- docs/docs.go | 527 ++++++++++++++++++++++++++++------------ docs/swagger.json | 527 ++++++++++++++++++++++++++++------------ docs/swagger.yaml | 353 +++++++++++++++++++-------- 13 files changed, 1422 insertions(+), 496 deletions(-) create mode 100644 backend/handlers/discussions.go 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 ( ginSwagger "github.com/swaggo/gin-swagger" ) +const ( + apiPath string = "/api" + v1Path string = "/v1" + swaggerPath string = "/swagger/*any" + indexPath string = "/" + tokenPath string = "/token" + loginPath string = "/login" + profilePath string = "/profile" + usersPath string = "/users/:userid" + demosPath string = "/demos" + mapSummaryPath string = "/maps/:mapid/summary" + mapImagePath string = "/maps/:mapid/image" + mapLeaderboardsPath string = "/maps/:mapid/leaderboards" + mapRecordPath string = "/maps/:mapid/record" + mapDiscussionsPath string = "/maps/:mapid/discussions" + mapDiscussionIDPath string = "/maps/:mapid/discussions/:discussionid" + rankingsPath string = "/rankings" + searchPath string = "/search" + gamesPath string = "/games" + chaptersPath string = "/games/:gameid" + chapterMapsPath string = "/chapters/:chapterid" + scoreLogsPath string = "/logs/score" + modLogsPath string = "/logs/mod" +) + func InitRoutes(router *gin.Engine) { - api := router.Group("/api") + api := router.Group(apiPath) { - v1 := api.Group("/v1") - v1.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) - v1.GET("/", func(c *gin.Context) { + v1 := api.Group(v1Path) + // Swagger + v1.GET(swaggerPath, ginSwagger.WrapHandler(swaggerfiles.Handler)) + v1.GET(indexPath, func(c *gin.Context) { c.File("docs/index.html") }) - v1.GET("/token", handlers.GetCookie) - v1.DELETE("/token", handlers.DeleteCookie) - v1.GET("/login", handlers.Login) - v1.GET("/profile", CheckAuth, handlers.Profile) - v1.PUT("/profile", CheckAuth, handlers.UpdateCountryCode) - v1.POST("/profile", CheckAuth, handlers.UpdateUser) - v1.GET("/users/:id", CheckAuth, handlers.FetchUser) - v1.GET("/demos", handlers.DownloadDemoWithID) - v1.GET("/maps/:id/summary", handlers.FetchMapSummary) - v1.POST("/maps/:id/summary", CheckAuth, handlers.CreateMapSummary) - v1.PUT("/maps/:id/summary", CheckAuth, handlers.EditMapSummary) - v1.DELETE("/maps/:id/summary", CheckAuth, handlers.DeleteMapSummary) - v1.PUT("/maps/:id/image", CheckAuth, handlers.EditMapImage) - v1.GET("/maps/:id/leaderboards", handlers.FetchMapLeaderboards) - v1.POST("/maps/:id/record", CheckAuth, handlers.CreateRecordWithDemo) - v1.GET("/rankings", handlers.Rankings) - v1.GET("/search", handlers.SearchWithQuery) - v1.GET("/games", handlers.FetchGames) - v1.GET("/games/:id", handlers.FetchChapters) - v1.GET("/chapters/:id", handlers.FetchChapterMaps) - v1.GET("/logs/score", handlers.ScoreLogs) - v1.GET("/logs/mod", CheckAuth, handlers.ModLogs) + // Tokens, login + v1.GET(tokenPath, handlers.GetCookie) + v1.DELETE(tokenPath, handlers.DeleteCookie) + v1.GET(loginPath, handlers.Login) + // Users, profiles + v1.GET(profilePath, CheckAuth, handlers.Profile) + v1.PUT(profilePath, CheckAuth, handlers.UpdateCountryCode) + v1.POST(profilePath, CheckAuth, handlers.UpdateUser) + v1.GET(usersPath, CheckAuth, handlers.FetchUser) + // Maps + v1.GET(mapSummaryPath, handlers.FetchMapSummary) + v1.POST(mapSummaryPath, CheckAuth, handlers.CreateMapSummary) + v1.PUT(mapSummaryPath, CheckAuth, handlers.EditMapSummary) + v1.DELETE(mapSummaryPath, CheckAuth, handlers.DeleteMapSummary) + v1.PUT(mapImagePath, CheckAuth, handlers.EditMapImage) + v1.GET(mapLeaderboardsPath, handlers.FetchMapLeaderboards) + v1.POST(mapRecordPath, CheckAuth, handlers.CreateRecordWithDemo) + v1.GET(demosPath, handlers.DownloadDemoWithID) + v1.GET(mapDiscussionsPath, handlers.FetchMapDiscussions) + v1.GET(mapDiscussionIDPath, handlers.FetchMapDiscussion) + v1.POST(mapDiscussionsPath, CheckAuth, handlers.CreateMapDiscussion) + v1.PUT(mapDiscussionIDPath, CheckAuth, handlers.EditMapDiscussion) + v1.DELETE(mapDiscussionIDPath, CheckAuth, handlers.DeleteMapDiscussion) + // Rankings, search + v1.GET(rankingsPath, handlers.Rankings) + v1.GET(searchPath, handlers.SearchWithQuery) + // Games, chapters, maps + v1.GET(gamesPath, handlers.FetchGames) + v1.GET(chaptersPath, handlers.FetchChapters) + v1.GET(chapterMapsPath, handlers.FetchChapterMaps) + // Logs + v1.GET(scoreLogsPath, handlers.ScoreLogs) + v1.GET(modLogsPath, CheckAuth, handlers.ModLogs) } } 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 ( FOREIGN KEY (user_id) REFERENCES users(steam_id) ); +CREATE TABLE map_discussions ( + id SERIAL, + map_id SMALLINT NOT NULL, + user_id TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now(), + updated_at TIMESTAMP NOT NULL DEFAULT now(), + PRIMARY KEY (id), + FOREIGN KEY (map_id) REFERENCES maps(id), + FOREIGN KEY (user_id) REFERENCES users(steam_id) +); + +CREATE TABLE map_discussions_comments ( + id SERIAL, + discussion_id INT NOT NULL, + user_id TEXT NOT NULL, + comment TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now(), + PRIMARY KEY (id), + FOREIGN KEY (discussion_id) REFERENCES map_discussions(id), + FOREIGN KEY (user_id) REFERENCES users(steam_id) +); + +CREATE TABLE map_discussions_upvotes ( + id SERIAL, + discussion_id INT NOT NULL, + user_id TEXT NOT NULL, + upvoted BOOLEAN NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (discussion_id) REFERENCES map_discussions(id), + FOREIGN KEY (user_id) REFERENCES users(steam_id) +); + CREATE TABLE demos ( id UUID, 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 @@ +package handlers + +import ( + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/pektezol/leastportalshub/backend/database" + "github.com/pektezol/leastportalshub/backend/models" +) + +type MapDiscussionResponse struct { + Discussion MapDiscussion `json:"discussion"` +} + +type MapDiscussionsResponse struct { + Discussions []MapDiscussionOnlyTitle `json:"discussions"` +} + +type MapDiscussion struct { + ID int `json:"id"` + Creator models.UserShortWithAvatar `json:"creator"` + Title string `json:"title"` + Content string `json:"content"` + // Upvotes int `json:"upvotes"` + UpdatedAt time.Time `json:"updated_at"` + Comments []MapDiscussionComment `json:"comments"` +} + +type MapDiscussionOnlyTitle struct { + ID int `json:"id"` + Creator models.UserShortWithAvatar `json:"creator"` + Title string `json:"title"` + // Upvotes int `json:"upvotes"` + UpdatedAt time.Time `json:"updated_at"` + Comments []MapDiscussionComment `json:"comments"` +} + +type MapDiscussionComment struct { + User models.UserShortWithAvatar `json:"user"` + Comment string `json:"comment"` + Date time.Time `json:"date"` +} + +type CreateMapDiscussionRequest struct { + Title string `json:"title" binding:"required"` + Content string `json:"content" binding:"required"` +} + +type EditMapDiscussionRequest struct { + Title string `json:"title" binding:"required"` + Content string `json:"content" binding:"required"` +} + +// GET Map Discussions +// +// @Description Get map discussions with specified map id. +// @Tags maps +// @Produce json +// @Param mapid path int true "Map ID" +// @Success 200 {object} models.Response{data=MapDiscussionsResponse} +// @Router /maps/{mapid}/discussions [get] +func FetchMapDiscussions(c *gin.Context) { + // TODO: get upvotes + response := MapDiscussionsResponse{} + mapID, err := strconv.Atoi(c.Param("mapid")) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + sql := `SELECT md.id, u.steam_id, u.user_name, u.avatar_link, md.title, md.updated_at FROM map_discussions md + INNER JOIN users u ON md.user_id = u.steam_id WHERE md.map_id = $1 + ORDER BY md.updated_at DESC` + rows, err := database.DB.Query(sql, mapID) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + // Get discussion data + for rows.Next() { + discussion := MapDiscussionOnlyTitle{} + err := rows.Scan(&discussion.ID, &discussion.Creator.SteamID, &discussion.Creator.UserName, &discussion.Creator.AvatarLink, &discussion.Title, &discussion.UpdatedAt) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + response.Discussions = append(response.Discussions, discussion) + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully retrieved map discussions.", + Data: response, + }) +} + +// GET Map Discussion +// +// @Description Get map discussion with specified map and discussion id. +// @Tags maps +// @Produce json +// @Param mapid path int true "Map ID" +// @Param discussionid path int true "Discussion ID" +// @Success 200 {object} models.Response{data=MapDiscussionResponse} +// @Router /maps/{mapid}/discussions/{discussionid} [get] +func FetchMapDiscussion(c *gin.Context) { + // TODO: get upvotes + response := MapDiscussionResponse{} + mapID, err := strconv.Atoi(c.Param("mapid")) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + discussionID, err := strconv.Atoi(c.Param("discussionid")) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + sql := `SELECT md.id, u.steam_id, u.user_name, u.avatar_link, md.title, md.content, md.updated_at FROM map_discussions md + INNER JOIN users u ON md.user_id = u.steam_id WHERE md.map_id = $1 AND md.id = $2` + 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) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + // Get commments + sql = `SELECT u.steam_id, u.user_name, u.avatar_link, mdc.comment, mdc.created_at FROM map_discussions_comments mdc + INNER JOIN users u ON mdc.user_id = u.steam_id WHERE mdc.discussion_id = $1` + comments, err := database.DB.Query(sql, response.Discussion.ID) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + for comments.Next() { + comment := MapDiscussionComment{} + err = comments.Scan(&comment.User.SteamID, &comment.User.UserName, &comment.User.AvatarLink, &comment.Comment, &comment.Date) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + response.Discussion.Comments = append(response.Discussion.Comments, comment) + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully retrieved map discussion.", + Data: response, + }) +} + +// POST Map Discussion +// +// @Description Create map discussion with specified map id. +// @Tags maps +// @Produce json +// @Param Authorization header string true "JWT Token" +// @Param mapid path int true "Map ID" +// @Param discussionid path int true "Discussion ID" +// @Param request body CreateMapDiscussionRequest true "Body" +// @Success 200 {object} models.Response{data=CreateMapDiscussionRequest} +// @Router /maps/{mapid}/discussions [post] +func CreateMapDiscussion(c *gin.Context) { + mapID, err := strconv.Atoi(c.Param("mapid")) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) + return + } + var request CreateMapDiscussionRequest + if err := c.BindJSON(&request); err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + sql := `INSERT INTO map_discussions (map_id,user_id,title,"content") + VALUES($1,$2,$3,$4);` + _, err = database.DB.Exec(sql, mapID, user.(models.User).SteamID, request.Title, request.Content) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully created map discussion.", + Data: request, + }) +} + +// PUT Map Discussion +// +// @Description Edit map discussion with specified map id. +// @Tags maps +// @Produce json +// @Param Authorization header string true "JWT Token" +// @Param mapid path int true "Map ID" +// @Param discussionid path int true "Discussion ID" +// @Param request body EditMapDiscussionRequest true "Body" +// @Success 200 {object} models.Response{data=EditMapDiscussionRequest} +// @Router /maps/{mapid}/discussions/{discussionid} [put] +func EditMapDiscussion(c *gin.Context) { + mapID, err := strconv.Atoi(c.Param("mapid")) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + discussionID, err := strconv.Atoi(c.Param("discussionid")) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) + return + } + var request EditMapDiscussionRequest + if err := c.BindJSON(&request); err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + sql := `UPDATE map_discussions SET title = $4, content = $5, updated_at = $6 WHERE id = $1 AND map_id = $2 AND user_id = $3` + result, err := database.DB.Exec(sql, discussionID, mapID, user.(models.User).SteamID, request.Title, request.Content, time.Now().UTC()) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + affectedRows, err := result.RowsAffected() + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + if affectedRows == 0 { + c.JSON(http.StatusOK, models.ErrorResponse("You can only edit your own post.")) + return + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully edited map discussion.", + Data: request, + }) +} + +// DELETE Map Summary +// +// @Description Delete map summary with specified map id. +// @Tags maps +// @Produce json +// @Param Authorization header string true "JWT Token" +// @Param mapid path int true "Map ID" +// @Param discussionid path int true "Discussion ID" +// @Success 200 {object} models.Response +// @Router /maps/{mapid}/discussions/{discussionid} [delete] +func DeleteMapDiscussion(c *gin.Context) { + mapID, err := strconv.Atoi(c.Param("mapid")) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + discussionID, err := strconv.Atoi(c.Param("discussionid")) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusOK, models.ErrorResponse("User not logged in.")) + return + } + sql := `DELETE FROM map_discussions WHERE id = $1 AND map_id = $2 AND user_id = $3` + result, err := database.DB.Exec(sql, discussionID, mapID, user.(models.User).SteamID) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + affectedRows, err := result.RowsAffected() + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + if affectedRows == 0 { + c.JSON(http.StatusOK, models.ErrorResponse("You can only delete your own post.")) + return + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully deleted map discussion.", + Data: nil, + }) +} 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 { // @Tags rankings // @Produce json // @Success 200 {object} models.Response{data=RankingsResponse} -// @Failure 400 {object} models.Response // @Router /rankings [get] func Rankings(c *gin.Context) { response := RankingsResponse{ @@ -142,7 +141,6 @@ func Rankings(c *gin.Context) { // @Produce json // @Param q query string false "Search user or map name." // @Success 200 {object} models.Response{data=SearchResponse} -// @Failure 400 {object} models.Response // @Router /search [get] func SearchWithQuery(c *gin.Context) { 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 { // @Accept json // @Produce json // @Success 200 {object} models.Response{data=LoginResponse} -// @Failure 400 {object} models.Response // @Router /login [get] func Login(c *gin.Context) { 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 { // @Produce json // @Param Authorization header string true "JWT Token" // @Success 200 {object} models.Response{data=LogsResponse} -// @Failure 400 {object} models.Response // @Router /logs/mod [get] func ModLogs(c *gin.Context) { mod, exists := c.Get("mod") @@ -125,7 +124,6 @@ func ModLogs(c *gin.Context) { // @Tags logs // @Produce json // @Success 200 {object} models.Response{data=ScoreLogsResponse} -// @Failure 400 {object} models.Response // @Router /logs/score [get] func ScoreLogs(c *gin.Context) { 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 { // @Description Get map summary with specified id. // @Tags maps // @Produce json -// @Param id path int true "Map ID" -// @Success 200 {object} models.Response{data=MapSummaryResponse} -// @Failure 400 {object} models.Response -// @Router /maps/{id}/summary [get] +// @Param mapid path int true "Map ID" +// @Success 200 {object} models.Response{data=MapSummaryResponse} +// @Router /maps/{mapid}/summary [get] func FetchMapSummary(c *gin.Context) { - id := c.Param("id") + id := c.Param("mapid") response := MapSummaryResponse{Map: models.Map{}, Summary: models.MapSummary{Routes: []models.MapRoute{}}} intID, err := strconv.Atoi(id) if err != nil { @@ -138,14 +137,13 @@ func FetchMapSummary(c *gin.Context) { // @Description Get map leaderboards with specified id. // @Tags maps // @Produce json -// @Param id path int true "Map ID" +// @Param mapid path int true "Map ID" // @Param page query int false "Page Number (default: 1)" // @Param pageSize query int false "Number of Records Per Page (default: 20)" // @Success 200 {object} models.Response{data=MapLeaderboardsResponse} -// @Failure 400 {object} models.Response -// @Router /maps/{id}/leaderboards [get] +// @Router /maps/{mapid}/leaderboards [get] func FetchMapLeaderboards(c *gin.Context) { - id := c.Param("id") + id := c.Param("mapid") // Get map data response := MapLeaderboardsResponse{Map: models.Map{}, Records: nil, Pagination: models.Pagination{}} page, err := strconv.Atoi(c.DefaultQuery("page", "1")) @@ -349,12 +347,11 @@ func FetchGames(c *gin.Context) { // @Description Get chapters from the specified game id. // @Tags games & chapters // @Produce json -// @Param id path int true "Game ID" -// @Success 200 {object} models.Response{data=ChaptersResponse} -// @Failure 400 {object} models.Response -// @Router /games/{id} [get] +// @Param gameid path int true "Game ID" +// @Success 200 {object} models.Response{data=ChaptersResponse} +// @Router /games/{gameid} [get] func FetchChapters(c *gin.Context) { - gameID := c.Param("id") + gameID := c.Param("gameid") intID, err := strconv.Atoi(gameID) if err != nil { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) @@ -391,12 +388,12 @@ func FetchChapters(c *gin.Context) { // @Description Get maps from the specified chapter id. // @Tags games & chapters // @Produce json -// @Param id path int true "Chapter ID" -// @Success 200 {object} models.Response{data=ChapterMapsResponse} -// @Failure 400 {object} models.Response -// @Router /chapters/{id} [get] +// @Param chapterid path int true "Chapter ID" +// @Success 200 {object} models.Response{data=ChapterMapsResponse} +// @Failure 400 {object} models.Response +// @Router /chapters/{chapterid} [get] func FetchChapterMaps(c *gin.Context) { - chapterID := c.Param("id") + chapterID := c.Param("chapterid") intID, err := strconv.Atoi(chapterID) if err != nil { 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 { // @Tags maps // @Produce json // @Param Authorization header string true "JWT Token" -// @Param id path int true "Map ID" +// @Param mapid path int true "Map ID" // @Param request body CreateMapSummaryRequest true "Body" // @Success 200 {object} models.Response{data=CreateMapSummaryRequest} -// @Failure 400 {object} models.Response -// @Router /maps/{id}/summary [post] +// @Router /maps/{mapid}/summary [post] func CreateMapSummary(c *gin.Context) { // Check if user exists user, exists := c.Get("user") @@ -60,7 +59,7 @@ func CreateMapSummary(c *gin.Context) { return } // Bind parameter and body - id := c.Param("id") + id := c.Param("mapid") mapID, err := strconv.Atoi(id) if err != nil { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) @@ -123,11 +122,10 @@ func CreateMapSummary(c *gin.Context) { // @Tags maps // @Produce json // @Param Authorization header string true "JWT Token" -// @Param id path int true "Map ID" +// @Param mapid path int true "Map ID" // @Param request body EditMapSummaryRequest true "Body" // @Success 200 {object} models.Response{data=EditMapSummaryRequest} -// @Failure 400 {object} models.Response -// @Router /maps/{id}/summary [put] +// @Router /maps/{mapid}/summary [put] func EditMapSummary(c *gin.Context) { // Check if user exists user, exists := c.Get("user") @@ -141,7 +139,7 @@ func EditMapSummary(c *gin.Context) { return } // Bind parameter and body - id := c.Param("id") + id := c.Param("mapid") mapID, err := strconv.Atoi(id) if err != nil { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) @@ -204,11 +202,10 @@ func EditMapSummary(c *gin.Context) { // @Tags maps // @Produce json // @Param Authorization header string true "JWT Token" -// @Param id path int true "Map ID" +// @Param mapid path int true "Map ID" // @Param request body DeleteMapSummaryRequest true "Body" // @Success 200 {object} models.Response{data=DeleteMapSummaryRequest} -// @Failure 400 {object} models.Response -// @Router /maps/{id}/summary [delete] +// @Router /maps/{mapid}/summary [delete] func DeleteMapSummary(c *gin.Context) { // Check if user exists user, exists := c.Get("user") @@ -222,7 +219,7 @@ func DeleteMapSummary(c *gin.Context) { return } // Bind parameter and body - id := c.Param("id") + id := c.Param("mapid") mapID, err := strconv.Atoi(id) if err != nil { c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) @@ -289,11 +286,10 @@ func DeleteMapSummary(c *gin.Context) { // @Tags maps // @Produce json // @Param Authorization header string true "JWT Token" -// @Param id path int true "Map ID" +// @Param mapid path int true "Map ID" // @Param request body EditMapImageRequest true "Body" // @Success 200 {object} models.Response{data=EditMapImageRequest} -// @Failure 400 {object} models.Response -// @Router /maps/{id}/image [put] +// @Router /maps/{mapid}/image [put] func EditMapImage(c *gin.Context) { // Check if user exists user, exists := c.Get("user") @@ -307,7 +303,7 @@ func EditMapImage(c *gin.Context) { return } // Bind parameter and body - id := c.Param("id") + id := c.Param("mapid") mapID, err := strconv.Atoi(id) if err != nil { 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 { // @Tags maps // @Accept mpfd // @Produce json -// @Param id path int true "Map ID" +// @Param mapid path int true "Map ID" // @Param Authorization header string true "JWT Token" // @Param host_demo formData file true "Host Demo" // @Param partner_demo formData file false "Partner Demo" // @Param is_partner_orange formData boolean false "Is Partner Orange" // @Param partner_id formData string false "Partner ID" // @Success 200 {object} models.Response{data=RecordResponse} -// @Failure 400 {object} models.Response -// @Failure 401 {object} models.Response -// @Router /maps/{id}/record [post] +// @Router /maps/{mapid}/record [post] func CreateRecordWithDemo(c *gin.Context) { - mapId := c.Param("id") + mapId := c.Param("mapid") // Check if user exists user, exists := c.Get("user") if !exists { @@ -216,9 +214,8 @@ func CreateRecordWithDemo(c *gin.Context) { // @Tags demo // @Accept json // @Produce octet-stream -// @Param uuid query string true "Demo UUID" -// @Success 200 {file} binary "Demo File" -// @Failure 400 {object} models.Response +// @Param uuid query string true "Demo UUID" +// @Success 200 {file} binary "Demo File" // @Router /demos [get] func DownloadDemoWithID(c *gin.Context) { 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 { // @Produce json // @Param Authorization header string true "JWT Token" // @Success 200 {object} models.Response{data=ProfileResponse} -// @Failure 400 {object} models.Response -// @Failure 401 {object} models.Response // @Router /profile [get] func Profile(c *gin.Context) { // Check if user exists @@ -343,13 +341,11 @@ func Profile(c *gin.Context) { // @Tags users // @Accept json // @Produce json -// @Param id path int true "User ID" -// @Success 200 {object} models.Response{data=ProfileResponse} -// @Failure 400 {object} models.Response -// @Failure 404 {object} models.Response -// @Router /users/{id} [get] +// @Param userid path int true "User ID" +// @Success 200 {object} models.Response{data=ProfileResponse} +// @Router /users/{userid} [get] func FetchUser(c *gin.Context) { - id := c.Param("id") + id := c.Param("userid") // Check if id is all numbers and 17 length match, _ := regexp.MatchString("^[0-9]{17}$", id) if !match { @@ -634,8 +630,6 @@ func FetchUser(c *gin.Context) { // @Produce json // @Param Authorization header string true "JWT Token" // @Success 200 {object} models.Response{data=ProfileResponse} -// @Failure 400 {object} models.Response -// @Failure 401 {object} models.Response // @Router /profile [post] func UpdateUser(c *gin.Context) { // Check if user exists @@ -681,8 +675,6 @@ func UpdateUser(c *gin.Context) { // @Param Authorization header string true "JWT Token" // @Param country_code query string true "Country Code [XX]" // @Success 200 {object} models.Response -// @Failure 400 {object} models.Response -// @Failure 401 {object} models.Response // @Router /profile [put] func UpdateCountryCode(c *gin.Context) { // Check if user exists diff --git a/docs/docs.go b/docs/docs.go index 1db1bba..9712377 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -20,7 +20,7 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/chapters/{id}": { + "/chapters/{chapterid}": { "get": { "description": "Get maps from the specified chapter id.", "produces": [ @@ -33,7 +33,7 @@ const docTemplate = `{ { "type": "integer", "description": "Chapter ID", - "name": "id", + "name": "chapterid", "in": "path", "required": true } @@ -93,12 +93,6 @@ const docTemplate = `{ "schema": { "type": "file" } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -143,7 +137,7 @@ const docTemplate = `{ } } }, - "/games/{id}": { + "/games/{gameid}": { "get": { "description": "Get chapters from the specified game id.", "produces": [ @@ -156,7 +150,7 @@ const docTemplate = `{ { "type": "integer", "description": "Game ID", - "name": "id", + "name": "gameid", "in": "path", "required": true } @@ -179,12 +173,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -219,12 +207,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -265,12 +247,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -302,19 +278,159 @@ const docTemplate = `{ } ] } + } + } + } + }, + "/maps/{mapid}/discussions": { + "get": { + "description": "Get map discussions with specified map id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.MapDiscussionsResponse" + } + } + } + ] + } + } + } + }, + "post": { + "description": "Create map discussion with specified map id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "string", + "description": "JWT Token", + "name": "Authorization", + "in": "header", + "required": true }, - "400": { - "description": "Bad Request", + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Discussion ID", + "name": "discussionid", + "in": "path", + "required": true + }, + { + "description": "Body", + "name": "request", + "in": "body", + "required": true, "schema": { - "$ref": "#/definitions/models.Response" + "$ref": "#/definitions/handlers.CreateMapDiscussionRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.CreateMapDiscussionRequest" + } + } + } + ] } } } } }, - "/maps/{id}/image": { + "/maps/{mapid}/discussions/{discussionid}": { + "get": { + "description": "Get map discussion with specified map and discussion id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Discussion ID", + "name": "discussionid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.MapDiscussionResponse" + } + } + } + ] + } + } + } + }, "put": { - "description": "Edit map image with specified map id.", + "description": "Edit map discussion with specified map id.", "produces": [ "application/json" ], @@ -332,7 +448,14 @@ const docTemplate = `{ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Discussion ID", + "name": "discussionid", "in": "path", "required": true }, @@ -342,7 +465,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.EditMapImageRequest" + "$ref": "#/definitions/handlers.EditMapDiscussionRequest" } } ], @@ -358,15 +481,49 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/handlers.EditMapImageRequest" + "$ref": "#/definitions/handlers.EditMapDiscussionRequest" } } } ] } + } + } + }, + "delete": { + "description": "Delete map summary with specified map id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "string", + "description": "JWT Token", + "name": "Authorization", + "in": "header", + "required": true }, - "400": { - "description": "Bad Request", + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Discussion ID", + "name": "discussionid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", "schema": { "$ref": "#/definitions/models.Response" } @@ -374,7 +531,63 @@ const docTemplate = `{ } } }, - "/maps/{id}/leaderboards": { + "/maps/{mapid}/image": { + "put": { + "description": "Edit map image with specified map id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "string", + "description": "JWT Token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + }, + { + "description": "Body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.EditMapImageRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.EditMapImageRequest" + } + } + } + ] + } + } + } + } + }, + "/maps/{mapid}/leaderboards": { "get": { "description": "Get map leaderboards with specified id.", "produces": [ @@ -387,7 +600,7 @@ const docTemplate = `{ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -422,17 +635,11 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } }, - "/maps/{id}/record": { + "/maps/{mapid}/record": { "post": { "description": "Post record with demo of a specific map.", "consumes": [ @@ -448,7 +655,7 @@ const docTemplate = `{ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -503,23 +710,11 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } }, - "/maps/{id}/summary": { + "/maps/{mapid}/summary": { "get": { "description": "Get map summary with specified id.", "produces": [ @@ -532,7 +727,7 @@ const docTemplate = `{ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true } @@ -555,12 +750,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -583,7 +772,7 @@ const docTemplate = `{ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -615,12 +804,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -643,7 +826,7 @@ const docTemplate = `{ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -675,12 +858,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -703,7 +880,7 @@ const docTemplate = `{ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -735,12 +912,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -784,18 +955,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -832,18 +991,6 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/models.Response" } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -885,18 +1032,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -928,12 +1063,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -973,12 +1102,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -1055,7 +1178,7 @@ const docTemplate = `{ } } }, - "/users/{id}": { + "/users/{userid}": { "get": { "description": "Get profile page of another user.", "consumes": [ @@ -1071,7 +1194,7 @@ const docTemplate = `{ { "type": "integer", "description": "User ID", - "name": "id", + "name": "userid", "in": "path", "required": true } @@ -1094,18 +1217,6 @@ const docTemplate = `{ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -1140,6 +1251,21 @@ const docTemplate = `{ } } }, + "handlers.CreateMapDiscussionRequest": { + "type": "object", + "required": [ + "content", + "title" + ], + "properties": { + "content": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, "handlers.CreateMapSummaryRequest": { "type": "object", "required": [ @@ -1181,6 +1307,21 @@ const docTemplate = `{ } } }, + "handlers.EditMapDiscussionRequest": { + "type": "object", + "required": [ + "content", + "title" + ], + "properties": { + "content": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, "handlers.EditMapImageRequest": { "type": "object", "required": [ @@ -1255,6 +1396,90 @@ const docTemplate = `{ } } }, + "handlers.MapDiscussion": { + "type": "object", + "properties": { + "comments": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.MapDiscussionComment" + } + }, + "content": { + "type": "string" + }, + "creator": { + "$ref": "#/definitions/models.UserShortWithAvatar" + }, + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "updated_at": { + "description": "Upvotes int ` + "`" + `json:\"upvotes\"` + "`" + `", + "type": "string" + } + } + }, + "handlers.MapDiscussionComment": { + "type": "object", + "properties": { + "comment": { + "type": "string" + }, + "date": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.UserShortWithAvatar" + } + } + }, + "handlers.MapDiscussionOnlyTitle": { + "type": "object", + "properties": { + "comments": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.MapDiscussionComment" + } + }, + "creator": { + "$ref": "#/definitions/models.UserShortWithAvatar" + }, + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "updated_at": { + "description": "Upvotes int ` + "`" + `json:\"upvotes\"` + "`" + `", + "type": "string" + } + } + }, + "handlers.MapDiscussionResponse": { + "type": "object", + "properties": { + "discussion": { + "$ref": "#/definitions/handlers.MapDiscussion" + } + } + }, + "handlers.MapDiscussionsResponse": { + "type": "object", + "properties": { + "discussions": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.MapDiscussionOnlyTitle" + } + } + } + }, "handlers.MapLeaderboardsResponse": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index ae83321..226cadd 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -13,7 +13,7 @@ "host": "lp.ardapektezol.com/api", "basePath": "/v1", "paths": { - "/chapters/{id}": { + "/chapters/{chapterid}": { "get": { "description": "Get maps from the specified chapter id.", "produces": [ @@ -26,7 +26,7 @@ { "type": "integer", "description": "Chapter ID", - "name": "id", + "name": "chapterid", "in": "path", "required": true } @@ -86,12 +86,6 @@ "schema": { "type": "file" } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -136,7 +130,7 @@ } } }, - "/games/{id}": { + "/games/{gameid}": { "get": { "description": "Get chapters from the specified game id.", "produces": [ @@ -149,7 +143,7 @@ { "type": "integer", "description": "Game ID", - "name": "id", + "name": "gameid", "in": "path", "required": true } @@ -172,12 +166,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -212,12 +200,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -258,12 +240,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -295,19 +271,159 @@ } ] } + } + } + } + }, + "/maps/{mapid}/discussions": { + "get": { + "description": "Get map discussions with specified map id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.MapDiscussionsResponse" + } + } + } + ] + } + } + } + }, + "post": { + "description": "Create map discussion with specified map id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "string", + "description": "JWT Token", + "name": "Authorization", + "in": "header", + "required": true }, - "400": { - "description": "Bad Request", + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Discussion ID", + "name": "discussionid", + "in": "path", + "required": true + }, + { + "description": "Body", + "name": "request", + "in": "body", + "required": true, "schema": { - "$ref": "#/definitions/models.Response" + "$ref": "#/definitions/handlers.CreateMapDiscussionRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.CreateMapDiscussionRequest" + } + } + } + ] } } } } }, - "/maps/{id}/image": { + "/maps/{mapid}/discussions/{discussionid}": { + "get": { + "description": "Get map discussion with specified map and discussion id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Discussion ID", + "name": "discussionid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.MapDiscussionResponse" + } + } + } + ] + } + } + } + }, "put": { - "description": "Edit map image with specified map id.", + "description": "Edit map discussion with specified map id.", "produces": [ "application/json" ], @@ -325,7 +441,14 @@ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Discussion ID", + "name": "discussionid", "in": "path", "required": true }, @@ -335,7 +458,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.EditMapImageRequest" + "$ref": "#/definitions/handlers.EditMapDiscussionRequest" } } ], @@ -351,15 +474,49 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/handlers.EditMapImageRequest" + "$ref": "#/definitions/handlers.EditMapDiscussionRequest" } } } ] } + } + } + }, + "delete": { + "description": "Delete map summary with specified map id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "string", + "description": "JWT Token", + "name": "Authorization", + "in": "header", + "required": true }, - "400": { - "description": "Bad Request", + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Discussion ID", + "name": "discussionid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", "schema": { "$ref": "#/definitions/models.Response" } @@ -367,7 +524,63 @@ } } }, - "/maps/{id}/leaderboards": { + "/maps/{mapid}/image": { + "put": { + "description": "Edit map image with specified map id.", + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "parameters": [ + { + "type": "string", + "description": "JWT Token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Map ID", + "name": "mapid", + "in": "path", + "required": true + }, + { + "description": "Body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.EditMapImageRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.EditMapImageRequest" + } + } + } + ] + } + } + } + } + }, + "/maps/{mapid}/leaderboards": { "get": { "description": "Get map leaderboards with specified id.", "produces": [ @@ -380,7 +593,7 @@ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -415,17 +628,11 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } }, - "/maps/{id}/record": { + "/maps/{mapid}/record": { "post": { "description": "Post record with demo of a specific map.", "consumes": [ @@ -441,7 +648,7 @@ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -496,23 +703,11 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } }, - "/maps/{id}/summary": { + "/maps/{mapid}/summary": { "get": { "description": "Get map summary with specified id.", "produces": [ @@ -525,7 +720,7 @@ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true } @@ -548,12 +743,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -576,7 +765,7 @@ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -608,12 +797,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -636,7 +819,7 @@ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -668,12 +851,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -696,7 +873,7 @@ { "type": "integer", "description": "Map ID", - "name": "id", + "name": "mapid", "in": "path", "required": true }, @@ -728,12 +905,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -777,18 +948,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -825,18 +984,6 @@ "schema": { "$ref": "#/definitions/models.Response" } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.Response" - } } } }, @@ -878,18 +1025,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -921,12 +1056,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -966,12 +1095,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -1048,7 +1171,7 @@ } } }, - "/users/{id}": { + "/users/{userid}": { "get": { "description": "Get profile page of another user.", "consumes": [ @@ -1064,7 +1187,7 @@ { "type": "integer", "description": "User ID", - "name": "id", + "name": "userid", "in": "path", "required": true } @@ -1087,18 +1210,6 @@ } ] } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/models.Response" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/models.Response" - } } } } @@ -1133,6 +1244,21 @@ } } }, + "handlers.CreateMapDiscussionRequest": { + "type": "object", + "required": [ + "content", + "title" + ], + "properties": { + "content": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, "handlers.CreateMapSummaryRequest": { "type": "object", "required": [ @@ -1174,6 +1300,21 @@ } } }, + "handlers.EditMapDiscussionRequest": { + "type": "object", + "required": [ + "content", + "title" + ], + "properties": { + "content": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, "handlers.EditMapImageRequest": { "type": "object", "required": [ @@ -1248,6 +1389,90 @@ } } }, + "handlers.MapDiscussion": { + "type": "object", + "properties": { + "comments": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.MapDiscussionComment" + } + }, + "content": { + "type": "string" + }, + "creator": { + "$ref": "#/definitions/models.UserShortWithAvatar" + }, + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "updated_at": { + "description": "Upvotes int `json:\"upvotes\"`", + "type": "string" + } + } + }, + "handlers.MapDiscussionComment": { + "type": "object", + "properties": { + "comment": { + "type": "string" + }, + "date": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.UserShortWithAvatar" + } + } + }, + "handlers.MapDiscussionOnlyTitle": { + "type": "object", + "properties": { + "comments": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.MapDiscussionComment" + } + }, + "creator": { + "$ref": "#/definitions/models.UserShortWithAvatar" + }, + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "updated_at": { + "description": "Upvotes int `json:\"upvotes\"`", + "type": "string" + } + } + }, + "handlers.MapDiscussionResponse": { + "type": "object", + "properties": { + "discussion": { + "$ref": "#/definitions/handlers.MapDiscussion" + } + } + }, + "handlers.MapDiscussionsResponse": { + "type": "object", + "properties": { + "discussions": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.MapDiscussionOnlyTitle" + } + } + } + }, "handlers.MapLeaderboardsResponse": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 98bd042..b20f762 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -18,6 +18,16 @@ definitions: game: $ref: '#/definitions/models.Game' type: object + handlers.CreateMapDiscussionRequest: + properties: + content: + type: string + title: + type: string + required: + - content + - title + type: object handlers.CreateMapSummaryRequest: properties: category_id: @@ -46,6 +56,16 @@ definitions: required: - route_id type: object + handlers.EditMapDiscussionRequest: + properties: + content: + type: string + title: + type: string + required: + - content + - title + type: object handlers.EditMapImageRequest: properties: image: @@ -95,6 +115,61 @@ definitions: user: $ref: '#/definitions/models.UserShort' type: object + handlers.MapDiscussion: + properties: + comments: + items: + $ref: '#/definitions/handlers.MapDiscussionComment' + type: array + content: + type: string + creator: + $ref: '#/definitions/models.UserShortWithAvatar' + id: + type: integer + title: + type: string + updated_at: + description: Upvotes int `json:"upvotes"` + type: string + type: object + handlers.MapDiscussionComment: + properties: + comment: + type: string + date: + type: string + user: + $ref: '#/definitions/models.UserShortWithAvatar' + type: object + handlers.MapDiscussionOnlyTitle: + properties: + comments: + items: + $ref: '#/definitions/handlers.MapDiscussionComment' + type: array + creator: + $ref: '#/definitions/models.UserShortWithAvatar' + id: + type: integer + title: + type: string + updated_at: + description: Upvotes int `json:"upvotes"` + type: string + type: object + handlers.MapDiscussionResponse: + properties: + discussion: + $ref: '#/definitions/handlers.MapDiscussion' + type: object + handlers.MapDiscussionsResponse: + properties: + discussions: + items: + $ref: '#/definitions/handlers.MapDiscussionOnlyTitle' + type: array + type: object handlers.MapLeaderboardsResponse: properties: map: @@ -397,13 +472,13 @@ info: title: Least Portals Database API version: "1.0" paths: - /chapters/{id}: + /chapters/{chapterid}: get: description: Get maps from the specified chapter id. parameters: - description: Chapter ID in: path - name: id + name: chapterid required: true type: integer produces: @@ -442,10 +517,6 @@ paths: description: Demo File schema: type: file - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - demo /games: @@ -471,13 +542,13 @@ paths: $ref: '#/definitions/models.Response' tags: - games & chapters - /games/{id}: + /games/{gameid}: get: description: Get chapters from the specified game id. parameters: - description: Game ID in: path - name: id + name: gameid required: true type: integer produces: @@ -492,10 +563,6 @@ paths: data: $ref: '#/definitions/handlers.ChaptersResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - games & chapters /login: @@ -515,10 +582,6 @@ paths: data: $ref: '#/definitions/handlers.LoginResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - login /logs/mod: @@ -542,10 +605,6 @@ paths: data: $ref: '#/definitions/handlers.LogsResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - logs /logs/score: @@ -563,13 +622,163 @@ paths: data: $ref: '#/definitions/handlers.ScoreLogsResponse' type: object - "400": - description: Bad Request + tags: + - logs + /maps/{mapid}/discussions: + get: + description: Get map discussions with specified map id. + parameters: + - description: Map ID + in: path + name: mapid + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + $ref: '#/definitions/handlers.MapDiscussionsResponse' + type: object + tags: + - maps + post: + description: Create map discussion with specified map id. + parameters: + - description: JWT Token + in: header + name: Authorization + required: true + type: string + - description: Map ID + in: path + name: mapid + required: true + type: integer + - description: Discussion ID + in: path + name: discussionid + required: true + type: integer + - description: Body + in: body + name: request + required: true + schema: + $ref: '#/definitions/handlers.CreateMapDiscussionRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + $ref: '#/definitions/handlers.CreateMapDiscussionRequest' + type: object + tags: + - maps + /maps/{mapid}/discussions/{discussionid}: + delete: + description: Delete map summary with specified map id. + parameters: + - description: JWT Token + in: header + name: Authorization + required: true + type: string + - description: Map ID + in: path + name: mapid + required: true + type: integer + - description: Discussion ID + in: path + name: discussionid + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK schema: $ref: '#/definitions/models.Response' tags: - - logs - /maps/{id}/image: + - maps + get: + description: Get map discussion with specified map and discussion id. + parameters: + - description: Map ID + in: path + name: mapid + required: true + type: integer + - description: Discussion ID + in: path + name: discussionid + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + $ref: '#/definitions/handlers.MapDiscussionResponse' + type: object + tags: + - maps + put: + description: Edit map discussion with specified map id. + parameters: + - description: JWT Token + in: header + name: Authorization + required: true + type: string + - description: Map ID + in: path + name: mapid + required: true + type: integer + - description: Discussion ID + in: path + name: discussionid + required: true + type: integer + - description: Body + in: body + name: request + required: true + schema: + $ref: '#/definitions/handlers.EditMapDiscussionRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + $ref: '#/definitions/handlers.EditMapDiscussionRequest' + type: object + tags: + - maps + /maps/{mapid}/image: put: description: Edit map image with specified map id. parameters: @@ -580,7 +789,7 @@ paths: type: string - description: Map ID in: path - name: id + name: mapid required: true type: integer - description: Body @@ -601,19 +810,15 @@ paths: data: $ref: '#/definitions/handlers.EditMapImageRequest' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - maps - /maps/{id}/leaderboards: + /maps/{mapid}/leaderboards: get: description: Get map leaderboards with specified id. parameters: - description: Map ID in: path - name: id + name: mapid required: true type: integer - description: 'Page Number (default: 1)' @@ -636,13 +841,9 @@ paths: data: $ref: '#/definitions/handlers.MapLeaderboardsResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - maps - /maps/{id}/record: + /maps/{mapid}/record: post: consumes: - multipart/form-data @@ -650,7 +851,7 @@ paths: parameters: - description: Map ID in: path - name: id + name: mapid required: true type: integer - description: JWT Token @@ -687,17 +888,9 @@ paths: data: $ref: '#/definitions/handlers.RecordResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/models.Response' tags: - maps - /maps/{id}/summary: + /maps/{mapid}/summary: delete: description: Delete map summary with specified map id. parameters: @@ -708,7 +901,7 @@ paths: type: string - description: Map ID in: path - name: id + name: mapid required: true type: integer - description: Body @@ -729,10 +922,6 @@ paths: data: $ref: '#/definitions/handlers.DeleteMapSummaryRequest' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - maps get: @@ -740,7 +929,7 @@ paths: parameters: - description: Map ID in: path - name: id + name: mapid required: true type: integer produces: @@ -755,10 +944,6 @@ paths: data: $ref: '#/definitions/handlers.MapSummaryResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - maps post: @@ -771,7 +956,7 @@ paths: type: string - description: Map ID in: path - name: id + name: mapid required: true type: integer - description: Body @@ -792,10 +977,6 @@ paths: data: $ref: '#/definitions/handlers.CreateMapSummaryRequest' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - maps put: @@ -808,7 +989,7 @@ paths: type: string - description: Map ID in: path - name: id + name: mapid required: true type: integer - description: Body @@ -829,10 +1010,6 @@ paths: data: $ref: '#/definitions/handlers.EditMapSummaryRequest' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - maps /profile: @@ -858,14 +1035,6 @@ paths: data: $ref: '#/definitions/handlers.ProfileResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/models.Response' tags: - users post: @@ -890,14 +1059,6 @@ paths: data: $ref: '#/definitions/handlers.ProfileResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/models.Response' tags: - users put: @@ -922,14 +1083,6 @@ paths: description: OK schema: $ref: '#/definitions/models.Response' - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/models.Response' tags: - users /rankings: @@ -947,10 +1100,6 @@ paths: data: $ref: '#/definitions/handlers.RankingsResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - rankings /search: @@ -973,10 +1122,6 @@ paths: data: $ref: '#/definitions/handlers.SearchResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' tags: - search /token: @@ -1020,7 +1165,7 @@ paths: $ref: '#/definitions/models.Response' tags: - auth - /users/{id}: + /users/{userid}: get: consumes: - application/json @@ -1028,7 +1173,7 @@ paths: parameters: - description: User ID in: path - name: id + name: userid required: true type: integer produces: @@ -1043,14 +1188,6 @@ paths: data: $ref: '#/definitions/handlers.ProfileResponse' type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/models.Response' - "404": - description: Not Found - schema: - $ref: '#/definitions/models.Response' tags: - users swagger: "2.0" -- cgit v1.2.3