diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | backend/controllers/modController.go | 44 | ||||
| -rw-r--r-- | backend/controllers/userController.go | 95 | ||||
| -rw-r--r-- | backend/database/init.sql | 15 | ||||
| -rw-r--r-- | backend/middleware/auth.go | 13 | ||||
| -rw-r--r-- | backend/models/models.go | 14 |
6 files changed, 95 insertions, 89 deletions
| @@ -2,4 +2,5 @@ | |||
| 2 | .vscode | 2 | .vscode |
| 3 | *.sh | 3 | *.sh |
| 4 | *.txt | 4 | *.txt |
| 5 | *.dem \ No newline at end of file | 5 | *.dem |
| 6 | *.json \ No newline at end of file | ||
diff --git a/backend/controllers/modController.go b/backend/controllers/modController.go index 7ce5cb4..7acdb5d 100644 --- a/backend/controllers/modController.go +++ b/backend/controllers/modController.go | |||
| @@ -49,18 +49,13 @@ type EditMapImageRequest struct { | |||
| 49 | // @Router /maps/{id}/summary [post] | 49 | // @Router /maps/{id}/summary [post] |
| 50 | func CreateMapSummary(c *gin.Context) { | 50 | func CreateMapSummary(c *gin.Context) { |
| 51 | // Check if user exists | 51 | // Check if user exists |
| 52 | user, exists := c.Get("user") | 52 | _, exists := c.Get("user") |
| 53 | if !exists { | 53 | if !exists { |
| 54 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) | 54 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) |
| 55 | return | 55 | return |
| 56 | } | 56 | } |
| 57 | var moderator bool | 57 | mod, exists := c.Get("mod") |
| 58 | for _, title := range user.(models.User).Titles { | 58 | if !exists || !mod.(bool) { |
| 59 | if title == "Moderator" { | ||
| 60 | moderator = true | ||
| 61 | } | ||
| 62 | } | ||
| 63 | if !moderator { | ||
| 64 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions.")) | 59 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions.")) |
| 65 | return | 60 | return |
| 66 | } | 61 | } |
| @@ -135,18 +130,13 @@ func CreateMapSummary(c *gin.Context) { | |||
| 135 | // @Router /maps/{id}/summary [put] | 130 | // @Router /maps/{id}/summary [put] |
| 136 | func EditMapSummary(c *gin.Context) { | 131 | func EditMapSummary(c *gin.Context) { |
| 137 | // Check if user exists | 132 | // Check if user exists |
| 138 | user, exists := c.Get("user") | 133 | _, exists := c.Get("user") |
| 139 | if !exists { | 134 | if !exists { |
| 140 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) | 135 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) |
| 141 | return | 136 | return |
| 142 | } | 137 | } |
| 143 | var moderator bool | 138 | mod, exists := c.Get("mod") |
| 144 | for _, title := range user.(models.User).Titles { | 139 | if !exists || !mod.(bool) { |
| 145 | if title == "Moderator" { | ||
| 146 | moderator = true | ||
| 147 | } | ||
| 148 | } | ||
| 149 | if !moderator { | ||
| 150 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions.")) | 140 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions.")) |
| 151 | return | 141 | return |
| 152 | } | 142 | } |
| @@ -221,18 +211,13 @@ func EditMapSummary(c *gin.Context) { | |||
| 221 | // @Router /maps/{id}/summary [delete] | 211 | // @Router /maps/{id}/summary [delete] |
| 222 | func DeleteMapSummary(c *gin.Context) { | 212 | func DeleteMapSummary(c *gin.Context) { |
| 223 | // Check if user exists | 213 | // Check if user exists |
| 224 | user, exists := c.Get("user") | 214 | _, exists := c.Get("user") |
| 225 | if !exists { | 215 | if !exists { |
| 226 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) | 216 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) |
| 227 | return | 217 | return |
| 228 | } | 218 | } |
| 229 | var moderator bool | 219 | mod, exists := c.Get("mod") |
| 230 | for _, title := range user.(models.User).Titles { | 220 | if !exists || !mod.(bool) { |
| 231 | if title == "Moderator" { | ||
| 232 | moderator = true | ||
| 233 | } | ||
| 234 | } | ||
| 235 | if !moderator { | ||
| 236 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions.")) | 221 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions.")) |
| 237 | return | 222 | return |
| 238 | } | 223 | } |
| @@ -311,18 +296,13 @@ func DeleteMapSummary(c *gin.Context) { | |||
| 311 | // @Router /maps/{id}/image [put] | 296 | // @Router /maps/{id}/image [put] |
| 312 | func EditMapImage(c *gin.Context) { | 297 | func EditMapImage(c *gin.Context) { |
| 313 | // Check if user exists | 298 | // Check if user exists |
| 314 | user, exists := c.Get("user") | 299 | _, exists := c.Get("user") |
| 315 | if !exists { | 300 | if !exists { |
| 316 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) | 301 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) |
| 317 | return | 302 | return |
| 318 | } | 303 | } |
| 319 | var moderator bool | 304 | mod, exists := c.Get("mod") |
| 320 | for _, title := range user.(models.User).Titles { | 305 | if !exists || !mod.(bool) { |
| 321 | if title == "Moderator" { | ||
| 322 | moderator = true | ||
| 323 | } | ||
| 324 | } | ||
| 325 | if !moderator { | ||
| 326 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions.")) | 306 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions.")) |
| 327 | return | 307 | return |
| 328 | } | 308 | } |
diff --git a/backend/controllers/userController.go b/backend/controllers/userController.go index 0dae155..64f144a 100644 --- a/backend/controllers/userController.go +++ b/backend/controllers/userController.go | |||
| @@ -17,8 +17,39 @@ type ProfileResponse struct { | |||
| 17 | UserName string `json:"user_name"` | 17 | UserName string `json:"user_name"` |
| 18 | AvatarLink string `json:"avatar_link"` | 18 | AvatarLink string `json:"avatar_link"` |
| 19 | CountryCode string `json:"country_code"` | 19 | CountryCode string `json:"country_code"` |
| 20 | ScoresSP []ScoreResponse `json:"scores_sp"` | 20 | Titles []models.Title `json:"titles"` |
| 21 | ScoresMP []ScoreResponse `json:"scores_mp"` | 21 | Links models.Links `json:"links"` |
| 22 | Rankings ProfileRankings `json:"rankings"` | ||
| 23 | Records ProfileRecords `json:"records"` | ||
| 24 | } | ||
| 25 | |||
| 26 | type ProfileRankings struct { | ||
| 27 | Overall ProfileRankingsDetails `json:"overall"` | ||
| 28 | Singleplayer ProfileRankingsDetails `json:"singleplayer"` | ||
| 29 | Cooperative ProfileRankingsDetails `json:"cooperative"` | ||
| 30 | } | ||
| 31 | |||
| 32 | type ProfileRankingsDetails struct { | ||
| 33 | Rank int `json:"rank"` | ||
| 34 | CompletionCount int `json:"completion_count"` | ||
| 35 | CompletionTotal int `json:"completion_total"` | ||
| 36 | } | ||
| 37 | |||
| 38 | type ProfileRecords struct { | ||
| 39 | P2Singleplayer ProfileRecordsDetails `json:"portal2_singleplayer"` | ||
| 40 | P2Cooperative ProfileRecordsDetails `json:"portal2_cooperative"` | ||
| 41 | } | ||
| 42 | |||
| 43 | type ProfileRecordsDetails struct { | ||
| 44 | MapID int `json:"map_id"` | ||
| 45 | Scores []ProfileScores `json:"scores"` | ||
| 46 | } | ||
| 47 | |||
| 48 | type ProfileScores struct { | ||
| 49 | DemoID string `json:"demo_id"` | ||
| 50 | ScoreCount int `json:"score_count"` | ||
| 51 | ScoreTime int `json:"score_time"` | ||
| 52 | Date time.Time `json:"date"` | ||
| 22 | } | 53 | } |
| 23 | 54 | ||
| 24 | type ScoreResponse struct { | 55 | type ScoreResponse struct { |
| @@ -44,58 +75,22 @@ func Profile(c *gin.Context) { | |||
| 44 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) | 75 | c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in.")) |
| 45 | return | 76 | return |
| 46 | } | 77 | } |
| 47 | // Retrieve singleplayer records | 78 | // Get user titles |
| 48 | var scoresSP []ScoreResponse | 79 | titles := []models.Title{} |
| 49 | sql := `SELECT id, map_id, score_count, score_time, demo_id, record_date FROM records_sp WHERE user_id = $1 ORDER BY map_id` | 80 | sql := `SELECT t.title_name, t.title_color FROM titles t |
| 81 | INNER JOIN user_titles ut ON t.id=ut.title_id WHERE ut.user_id = $1` | ||
| 50 | rows, err := database.DB.Query(sql, user.(models.User).SteamID) | 82 | rows, err := database.DB.Query(sql, user.(models.User).SteamID) |
| 51 | if err != nil { | 83 | if err != nil { |
| 52 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) | 84 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) |
| 53 | return | 85 | return |
| 54 | } | 86 | } |
| 55 | var recordsSP []models.RecordSP | ||
| 56 | for rows.Next() { | ||
| 57 | var mapID int | ||
| 58 | var record models.RecordSP | ||
| 59 | rows.Scan(&record.RecordID, &mapID, &record.ScoreCount, &record.ScoreTime, &record.DemoID, &record.RecordDate) | ||
| 60 | // More than one record in one map | ||
| 61 | if len(scoresSP) != 0 && mapID == scoresSP[len(scoresSP)-1].MapID { | ||
| 62 | scoresSP[len(scoresSP)-1].Records = append(scoresSP[len(scoresSP)-1].Records.([]models.RecordSP), record) | ||
| 63 | continue | ||
| 64 | } | ||
| 65 | // New map | ||
| 66 | recordsSP = []models.RecordSP{} | ||
| 67 | recordsSP = append(recordsSP, record) | ||
| 68 | scoresSP = append(scoresSP, ScoreResponse{ | ||
| 69 | MapID: mapID, | ||
| 70 | Records: recordsSP, | ||
| 71 | }) | ||
| 72 | } | ||
| 73 | // Retrieve multiplayer records | ||
| 74 | var scoresMP []ScoreResponse | ||
| 75 | sql = `SELECT id, map_id, host_id, partner_id, score_count, score_time, host_demo_id, partner_demo_id, record_date FROM records_mp | ||
| 76 | WHERE host_id = $1 OR partner_id = $2 ORDER BY map_id` | ||
| 77 | rows, err = database.DB.Query(sql, user.(models.User).SteamID, user.(models.User).SteamID) | ||
| 78 | if err != nil { | ||
| 79 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) | ||
| 80 | return | ||
| 81 | } | ||
| 82 | var recordsMP []models.RecordMP | ||
| 83 | for rows.Next() { | 87 | for rows.Next() { |
| 84 | var mapID int | 88 | var title models.Title |
| 85 | var record models.RecordMP | 89 | if err := rows.Scan(&title.Name, &title.Color); err != nil { |
| 86 | rows.Scan(&record.RecordID, &mapID, &record.HostID, &record.PartnerID, &record.ScoreCount, &record.ScoreTime, &record.HostDemoID, &record.PartnerDemoID, &record.RecordDate) | 90 | c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) |
| 87 | // More than one record in one map | 91 | return |
| 88 | if len(scoresMP) != 0 && mapID == scoresMP[len(scoresMP)-1].MapID { | ||
| 89 | scoresMP[len(scoresMP)-1].Records = append(scoresMP[len(scoresMP)-1].Records.([]models.RecordMP), record) | ||
| 90 | continue | ||
| 91 | } | 92 | } |
| 92 | // New map | 93 | titles = append(titles, title) |
| 93 | recordsMP = []models.RecordMP{} | ||
| 94 | recordsMP = append(recordsMP, record) | ||
| 95 | scoresMP = append(scoresMP, ScoreResponse{ | ||
| 96 | MapID: mapID, | ||
| 97 | Records: recordsMP, | ||
| 98 | }) | ||
| 99 | } | 94 | } |
| 100 | c.JSON(http.StatusOK, models.Response{ | 95 | c.JSON(http.StatusOK, models.Response{ |
| 101 | Success: true, | 96 | Success: true, |
| @@ -106,8 +101,10 @@ func Profile(c *gin.Context) { | |||
| 106 | UserName: user.(models.User).UserName, | 101 | UserName: user.(models.User).UserName, |
| 107 | AvatarLink: user.(models.User).AvatarLink, | 102 | AvatarLink: user.(models.User).AvatarLink, |
| 108 | CountryCode: user.(models.User).CountryCode, | 103 | CountryCode: user.(models.User).CountryCode, |
| 109 | ScoresSP: scoresSP, | 104 | Titles: user.(models.User).Titles, |
| 110 | ScoresMP: scoresMP, | 105 | Links: models.Links{}, |
| 106 | Rankings: ProfileRankings{}, | ||
| 107 | Records: ProfileRecords{}, | ||
| 111 | }, | 108 | }, |
| 112 | }) | 109 | }) |
| 113 | return | 110 | return |
diff --git a/backend/database/init.sql b/backend/database/init.sql index 11d4944..25de872 100644 --- a/backend/database/init.sql +++ b/backend/database/init.sql | |||
| @@ -3,6 +3,10 @@ CREATE TABLE users ( | |||
| 3 | user_name TEXT NOT NULL, | 3 | user_name TEXT NOT NULL, |
| 4 | avatar_link TEXT NOT NULL, | 4 | avatar_link TEXT NOT NULL, |
| 5 | country_code CHAR(2) NOT NULL, | 5 | country_code CHAR(2) NOT NULL, |
| 6 | p2sr TEXT NOT NULL DEFAULT '-', | ||
| 7 | steam TEXT NOT NULL DEFAULT '-', | ||
| 8 | youtube TEXT NOT NULL DEFAULT '-', | ||
| 9 | twitch TEXT NOT NULL DEFAULT '-', | ||
| 6 | created_at TIMESTAMP NOT NULL DEFAULT now(), | 10 | created_at TIMESTAMP NOT NULL DEFAULT now(), |
| 7 | updated_at TIMESTAMP NOT NULL DEFAULT now(), | 11 | updated_at TIMESTAMP NOT NULL DEFAULT now(), |
| 8 | PRIMARY KEY (steam_id) | 12 | PRIMARY KEY (steam_id) |
| @@ -117,9 +121,16 @@ CREATE TABLE records_mp ( | |||
| 117 | ); | 121 | ); |
| 118 | 122 | ||
| 119 | CREATE TABLE titles ( | 123 | CREATE TABLE titles ( |
| 120 | user_id TEXT, | 124 | id SERIAL, |
| 121 | title_name TEXT NOT NULL, | 125 | title_name TEXT NOT NULL, |
| 122 | PRIMARY KEY (user_id), | 126 | title_color CHAR(6) NOT NULL, |
| 127 | PRIMARY KEY (id) | ||
| 128 | ); | ||
| 129 | |||
| 130 | CREATE TABLE user_titles ( | ||
| 131 | title_id INT NOT NULL, | ||
| 132 | user_id TEXT NOT NULL, | ||
| 133 | FOREIGN KEY (title_id) REFERENCES titles(id), | ||
| 123 | FOREIGN KEY (user_id) REFERENCES users(steam_id) | 134 | FOREIGN KEY (user_id) REFERENCES users(steam_id) |
| 124 | ); | 135 | ); |
| 125 | 136 | ||
diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go index 6a057da..e2c84fa 100644 --- a/backend/middleware/auth.go +++ b/backend/middleware/auth.go | |||
| @@ -44,14 +44,19 @@ func CheckAuth(c *gin.Context) { | |||
| 44 | return | 44 | return |
| 45 | } | 45 | } |
| 46 | // Get user titles from DB | 46 | // Get user titles from DB |
| 47 | user.Titles = []string{} | 47 | var moderator bool |
| 48 | rows, _ := database.DB.Query(`SELECT title_name FROM titles t INNER JOIN user_titles ut ON t.id=ut.title_id WHERE ut.user_id = $1`, user.SteamID) | 48 | user.Titles = []models.Title{} |
| 49 | rows, _ := database.DB.Query(`SELECT t.title_name, t.title_color FROM titles t INNER JOIN user_titles ut ON t.id=ut.title_id WHERE ut.user_id = $1`, user.SteamID) | ||
| 49 | for rows.Next() { | 50 | for rows.Next() { |
| 50 | var title string | 51 | var title models.Title |
| 51 | rows.Scan(&title) | 52 | rows.Scan(&title.Name, &title.Color) |
| 53 | if title.Name == "Moderator" { | ||
| 54 | moderator = true | ||
| 55 | } | ||
| 52 | user.Titles = append(user.Titles, title) | 56 | user.Titles = append(user.Titles, title) |
| 53 | } | 57 | } |
| 54 | c.Set("user", user) | 58 | c.Set("user", user) |
| 59 | c.Set("mod", moderator) | ||
| 55 | c.Next() | 60 | c.Next() |
| 56 | } else { | 61 | } else { |
| 57 | c.Next() | 62 | c.Next() |
diff --git a/backend/models/models.go b/backend/models/models.go index e21ba6a..f124db5 100644 --- a/backend/models/models.go +++ b/backend/models/models.go | |||
| @@ -25,7 +25,7 @@ type User struct { | |||
| 25 | CountryCode string `json:"country_code"` | 25 | CountryCode string `json:"country_code"` |
| 26 | CreatedAt time.Time `json:"created_at"` | 26 | CreatedAt time.Time `json:"created_at"` |
| 27 | UpdatedAt time.Time `json:"updated_at"` | 27 | UpdatedAt time.Time `json:"updated_at"` |
| 28 | Titles []string `json:"titles"` | 28 | Titles []Title `json:"titles"` |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | type UserShort struct { | 31 | type UserShort struct { |
| @@ -92,6 +92,18 @@ type Category struct { | |||
| 92 | Name string `json:"name"` | 92 | Name string `json:"name"` |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | type Title struct { | ||
| 96 | Name string `json:"name"` | ||
| 97 | Color string `json:"color"` | ||
| 98 | } | ||
| 99 | |||
| 100 | type Links struct { | ||
| 101 | P2SR string `json:"p2sr"` | ||
| 102 | Steam string `json:"stream"` | ||
| 103 | YouTube string `json:"youtube"` | ||
| 104 | Twitch string `json:"twitch"` | ||
| 105 | } | ||
| 106 | |||
| 95 | type RecordSP struct { | 107 | type RecordSP struct { |
| 96 | RecordID int `json:"record_id"` | 108 | RecordID int `json:"record_id"` |
| 97 | Placement int `json:"placement"` | 109 | Placement int `json:"placement"` |