diff options
Diffstat (limited to '')
| -rw-r--r-- | backend/handlers/discussions.go | 291 |
1 files changed, 291 insertions, 0 deletions
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 @@ | |||
| 1 | package handlers | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 13 | type MapDiscussionResponse struct { | ||
| 14 | Discussion MapDiscussion `json:"discussion"` | ||
| 15 | } | ||
| 16 | |||
| 17 | type MapDiscussionsResponse struct { | ||
| 18 | Discussions []MapDiscussionOnlyTitle `json:"discussions"` | ||
| 19 | } | ||
| 20 | |||
| 21 | type 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 | |||
| 31 | type 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 | |||
| 40 | type MapDiscussionComment struct { | ||
| 41 | User models.UserShortWithAvatar `json:"user"` | ||
| 42 | Comment string `json:"comment"` | ||
| 43 | Date time.Time `json:"date"` | ||
| 44 | } | ||
| 45 | |||
| 46 | type CreateMapDiscussionRequest struct { | ||
| 47 | Title string `json:"title" binding:"required"` | ||
| 48 | Content string `json:"content" binding:"required"` | ||
| 49 | } | ||
| 50 | |||
| 51 | type 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] | ||
| 64 | func 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] | ||
| 106 | func 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] | ||
| 161 | func 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] | ||
| 202 | func 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] | ||
| 255 | func 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 | } | ||