aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--FUNDING.yml1
-rw-r--r--README.md34
-rw-r--r--backend/controllers/homeController.go59
-rw-r--r--backend/controllers/loginController.go48
-rw-r--r--backend/controllers/mapController.go146
-rw-r--r--backend/controllers/modController.go327
-rw-r--r--backend/controllers/recordController.go134
-rw-r--r--backend/controllers/userController.go74
-rw-r--r--backend/database/chapters.sql32
-rw-r--r--backend/database/games.sql6
-rw-r--r--backend/database/history.sql524
-rw-r--r--backend/database/init.sql44
-rw-r--r--backend/database/route.sql279
-rw-r--r--backend/middleware/auth.go10
-rw-r--r--backend/models/models.go150
-rw-r--r--backend/models/requests.go39
-rw-r--r--backend/models/responses.go64
-rw-r--r--backend/parser/parser.REMOVED.git-id1
-rw-r--r--backend/parser/parser.go43
-rw-r--r--backend/routes/routes.go12
-rw-r--r--docs/docs.go641
-rw-r--r--docs/swagger.json641
-rw-r--r--docs/swagger.yaml422
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--main.go2
26 files changed, 2623 insertions, 1113 deletions
diff --git a/FUNDING.yml b/FUNDING.yml
new file mode 100644
index 0000000..bd67f90
--- /dev/null
+++ b/FUNDING.yml
@@ -0,0 +1 @@
github: pektezol \ No newline at end of file
diff --git a/README.md b/README.md
index 2063667..5a44f82 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
1# Portal 2 Least Portals Database 1# Portal 2 Least Portals Hub
2 2
3This project is work in progress. 3## About LPHUB
4
5Least Portals Hub (LPHUB) is a community-driven platform that aims to gather knowledge and competition of the Portal 2 Least Portals (LP) category in one place. LPHUB is dedicated to bringing together users who love the challenge of LP, where the goal is to complete a level using the fewest number of portals possible that is placed by the player.
6
7Our comprehensive map library contains detailed, wiki-style information for maps from Portal 2 and their mods, including strategies and routes for achieving specific portal counts. These guides are designed to assist players of all levels in improving their skills, understanding the game's mechanics better, and finding innovative ways to lower their portal usage.
8
9In addition, we host a leaderboard section for each map where users can compare their accomplishments and compete for having the lowest amount of portals possible. Users can upload their records, along with demo proof in order to get on to the leaderboards.
4 10
5## Project Team 11## Project Team
6 12
@@ -9,6 +15,10 @@ This project is work in progress.
9* [@Nidboj132](https://github.com/Nidboj132) - Frontend 15* [@Nidboj132](https://github.com/Nidboj132) - Frontend
10* [@Oryn](https://github.com/Oryn-Goia) - Subject Expert 16* [@Oryn](https://github.com/Oryn-Goia) - Subject Expert
11 17
18## Special Thanks
19
20* [@UncraftedName](https://github.com/UncraftedName) - For providing the parser that retrieves total portal count and elapsed time from the CM counter.
21
12## Documentation 22## Documentation
13 23
14Full documentation can be found at https://lp.ardapektezol.com/api/v1/ 24Full documentation can be found at https://lp.ardapektezol.com/api/v1/
@@ -19,4 +29,22 @@ This project is licensed under the GNU General Public License v2.0 - see the [LI
19 29
20## Contact 30## Contact
21 31
22If you have any questions or feedback, please feel free to contact us at our [Discord](https://discord.gg/xq6TySyA4c). \ No newline at end of file 32If you have any questions or feedback, please feel free to contact us at our [Discord](https://discord.gg/xq6TySyA4c).
33
34## Support
35
36If you want to support the creator, you can do so by using GitHub sponsorships by [clicking here](https://github.com/sponsors/pektezol).
37
38## Disclaimer
39
40This project, "Portal 2 Least Portals Hub" (hereafter referred to as "LPHUB"), is an unofficial community-driven resource providing strategies, routes, leaderboards, and other information related to the "Least Portals" category of the game "Portal 2". LPHUB is not affiliated with or endorsed by the creators, developers, or publishers of "Portal 2", including but not limited to Valve Corporation.
41
42The strategies, routes, leaderboard entries, and other information presented on LPHUB are provided by members of the community and are not guaranteed to be correct, accurate, or applicable to all versions or modes of "Portal 2". Users are encouraged to verify information and submit corrections or additions as necessary.
43
44Leaderboard entries must include demo proof, which is subject to review by LPHUB administrators and moderators. While every effort is made to ensure the integrity of the leaderboard, LPHUB cannot guarantee the authenticity or accuracy of any submitted record or demo.
45
46The source code for LPHUB is licensed under the GNU General Public License version 2.0 (GPL-2.0). By using LPHUB, you agree to abide by the terms and conditions of this license. The source code is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.
47
48Your use of LPHUB is at your own risk. LPHUB and its administrators and moderators disclaim all liability for any damages, whether direct, indirect, incidental, or consequential, that may result from your use of LPHUB or the strategies, routes, or other information provided therein.
49
50By using LPHUB, you acknowledge that you have read and understood this disclaimer and agree to its terms. \ No newline at end of file
diff --git a/backend/controllers/homeController.go b/backend/controllers/homeController.go
index edb770f..2780e63 100644
--- a/backend/controllers/homeController.go
+++ b/backend/controllers/homeController.go
@@ -3,6 +3,7 @@ package controllers
3import ( 3import (
4 "log" 4 "log"
5 "net/http" 5 "net/http"
6 "strings"
6 7
7 "github.com/gin-gonic/gin" 8 "github.com/gin-gonic/gin"
8 "github.com/pektezol/leastportals/backend/database" 9 "github.com/pektezol/leastportals/backend/database"
@@ -22,12 +23,12 @@ func Home(c *gin.Context) {
22 23
23// GET Rankings 24// GET Rankings
24// 25//
25// @Summary Get rankings of every player. 26// @Description Get rankings of every player.
26// @Tags rankings 27// @Tags rankings
27// @Produce json 28// @Produce json
28// @Success 200 {object} models.Response{data=models.RankingsResponse} 29// @Success 200 {object} models.Response{data=models.RankingsResponse}
29// @Failure 400 {object} models.Response 30// @Failure 400 {object} models.Response
30// @Router /demo [get] 31// @Router /rankings [get]
31func Rankings(c *gin.Context) { 32func Rankings(c *gin.Context) {
32 rows, err := database.DB.Query(`SELECT steam_id, user_name FROM users`) 33 rows, err := database.DB.Query(`SELECT steam_id, user_name FROM users`)
33 if err != nil { 34 if err != nil {
@@ -122,21 +123,22 @@ func Rankings(c *gin.Context) {
122 }) 123 })
123} 124}
124 125
125// GET Search 126// GET Search With Query
126// 127//
127// @Summary Get all user and map data. 128// @Description Get all user and map data matching to the query.
128// @Tags search 129// @Tags search
129// @Produce json 130// @Produce json
130// @Success 200 {object} models.Response{data=models.SearchResponse} 131// @Param q query string false "Search user or map name."
131// @Failure 400 {object} models.Response 132// @Success 200 {object} models.Response{data=models.SearchResponse}
132// @Router /search [get] 133// @Failure 400 {object} models.Response
133func Search(c *gin.Context) { 134// @Router /search [get]
135func SearchWithQuery(c *gin.Context) {
136 query := c.Query("q")
137 query = strings.ToLower(query)
138 log.Println(query)
134 var response models.SearchResponse 139 var response models.SearchResponse
135 // Cache all maps for faster response 140 // Cache all maps for faster response
136 var maps = []struct { 141 var maps = []models.MapShort{
137 ID int `json:"id"`
138 Name string `json:"name"`
139 }{
140 {ID: 1, Name: "Container Ride"}, 142 {ID: 1, Name: "Container Ride"},
141 {ID: 2, Name: "Portal Carousel"}, 143 {ID: 2, Name: "Portal Carousel"},
142 {ID: 3, Name: "Portal Gun"}, 144 {ID: 3, Name: "Portal Gun"},
@@ -248,23 +250,32 @@ func Search(c *gin.Context) {
248 {ID: 109, Name: "Gel Maze"}, 250 {ID: 109, Name: "Gel Maze"},
249 {ID: 110, Name: "Crazier Box"}, 251 {ID: 110, Name: "Crazier Box"},
250 } 252 }
251 response.Maps = maps 253 var filteredMaps []models.MapShort
252 rows, err := database.DB.Query("SELECT steam_id, user_name FROM users") //WHERE player_name LIKE ?", "%"+query+"%") 254 for _, m := range maps {
255 if strings.Contains(strings.ToLower(m.Name), strings.ToLower(query)) {
256 filteredMaps = append(filteredMaps, m)
257 }
258 }
259 response.Maps = filteredMaps
260 if len(response.Maps) == 0 {
261 response.Maps = []models.MapShort{}
262 }
263 rows, err := database.DB.Query("SELECT steam_id, user_name FROM users WHERE lower(user_name) LIKE $1", "%"+query+"%")
253 if err != nil { 264 if err != nil {
254 log.Fatal(err) 265 log.Fatal(err)
255 } 266 }
256 defer rows.Close() 267 defer rows.Close()
257 for rows.Next() { 268 for rows.Next() {
258 var user struct { 269 var user models.UserShort
259 SteamID string `json:"steam_id"`
260 UserName string `json:"user_name"`
261 }
262 if err := rows.Scan(&user.SteamID, &user.UserName); err != nil { 270 if err := rows.Scan(&user.SteamID, &user.UserName); err != nil {
263 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 271 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
264 return 272 return
265 } 273 }
266 response.Players = append(response.Players, user) 274 response.Players = append(response.Players, user)
267 } 275 }
276 if len(response.Players) == 0 {
277 response.Players = []models.UserShort{}
278 }
268 c.JSON(http.StatusOK, models.Response{ 279 c.JSON(http.StatusOK, models.Response{
269 Success: true, 280 Success: true,
270 Message: "Search successfully retrieved.", 281 Message: "Search successfully retrieved.",
diff --git a/backend/controllers/loginController.go b/backend/controllers/loginController.go
index cfe086d..ae6e957 100644
--- a/backend/controllers/loginController.go
+++ b/backend/controllers/loginController.go
@@ -17,13 +17,13 @@ import (
17 17
18// Login 18// Login
19// 19//
20// @Summary Get (redirect) login page for Steam auth. 20// @Description Get (redirect) login page for Steam auth.
21// @Tags login 21// @Tags login
22// @Accept json 22// @Accept json
23// @Produce json 23// @Produce json
24// @Success 200 {object} models.Response{data=models.LoginResponse} 24// @Success 200 {object} models.Response{data=models.LoginResponse}
25// @Failure 400 {object} models.Response 25// @Failure 400 {object} models.Response
26// @Router /login [get] 26// @Router /login [get]
27func Login(c *gin.Context) { 27func Login(c *gin.Context) {
28 openID := steam_go.NewOpenId(c.Request) 28 openID := steam_go.NewOpenId(c.Request)
29 switch openID.Mode() { 29 switch openID.Mode() {
@@ -59,10 +59,20 @@ func Login(c *gin.Context) {
59 database.DB.Exec(`INSERT INTO users (steam_id, user_name, avatar_link, country_code) 59 database.DB.Exec(`INSERT INTO users (steam_id, user_name, avatar_link, country_code)
60 VALUES ($1, $2, $3, $4)`, steamID, user.PersonaName, user.AvatarFull, user.LocCountryCode) 60 VALUES ($1, $2, $3, $4)`, steamID, user.PersonaName, user.AvatarFull, user.LocCountryCode)
61 } 61 }
62 moderator := false
63 rows, _ := database.DB.Query("SELECT title_name FROM titles WHERE user_id = $1", steamID)
64 for rows.Next() {
65 var title string
66 rows.Scan(&title)
67 if title == "Moderator" {
68 moderator = true
69 }
70 }
62 // Generate JWT token 71 // Generate JWT token
63 token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 72 token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
64 "sub": steamID, 73 "sub": steamID,
65 "exp": time.Now().Add(time.Hour * 24 * 30).Unix(), 74 "exp": time.Now().Add(time.Hour * 24 * 30).Unix(),
75 "mod": moderator,
66 }) 76 })
67 // Sign and get the complete encoded token as a string using the secret 77 // Sign and get the complete encoded token as a string using the secret
68 tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY"))) 78 tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY")))
@@ -85,13 +95,13 @@ func Login(c *gin.Context) {
85 95
86// GET Token 96// GET Token
87// 97//
88// @Summary Gets the token cookie value from the user. 98// @Description Gets the token cookie value from the user.
89// @Tags auth 99// @Tags auth
90// @Produce json 100// @Produce json
91// 101//
92// @Success 200 {object} models.Response{data=models.LoginResponse} 102// @Success 200 {object} models.Response{data=models.LoginResponse}
93// @Failure 404 {object} models.Response 103// @Failure 404 {object} models.Response
94// @Router /token [get] 104// @Router /token [get]
95func GetCookie(c *gin.Context) { 105func GetCookie(c *gin.Context) {
96 cookie, err := c.Cookie("token") 106 cookie, err := c.Cookie("token")
97 if err != nil { 107 if err != nil {
@@ -109,13 +119,13 @@ func GetCookie(c *gin.Context) {
109 119
110// DELETE Token 120// DELETE Token
111// 121//
112// @Summary Deletes the token cookie from the user. 122// @Description Deletes the token cookie from the user.
113// @Tags auth 123// @Tags auth
114// @Produce json 124// @Produce json
115// 125//
116// @Success 200 {object} models.Response{data=models.LoginResponse} 126// @Success 200 {object} models.Response{data=models.LoginResponse}
117// @Failure 404 {object} models.Response 127// @Failure 404 {object} models.Response
118// @Router /token [delete] 128// @Router /token [delete]
119func DeleteCookie(c *gin.Context) { 129func DeleteCookie(c *gin.Context) {
120 cookie, err := c.Cookie("token") 130 cookie, err := c.Cookie("token")
121 if err != nil { 131 if err != nil {
diff --git a/backend/controllers/mapController.go b/backend/controllers/mapController.go
index d8783b7..e7c5566 100644
--- a/backend/controllers/mapController.go
+++ b/backend/controllers/mapController.go
@@ -3,111 +3,81 @@ package controllers
3import ( 3import (
4 "net/http" 4 "net/http"
5 "strconv" 5 "strconv"
6 "time"
7 6
8 "github.com/gin-gonic/gin" 7 "github.com/gin-gonic/gin"
9 "github.com/lib/pq"
10 "github.com/pektezol/leastportals/backend/database" 8 "github.com/pektezol/leastportals/backend/database"
11 "github.com/pektezol/leastportals/backend/models" 9 "github.com/pektezol/leastportals/backend/models"
12) 10)
13 11
14// GET Map Summary 12// GET Map Summary
15// 13//
16// @Summary Get map summary with specified id. 14// @Description Get map summary with specified id.
17// @Tags maps 15// @Tags maps
18// @Produce json 16// @Produce json
19// @Param id path int true "Map ID" 17// @Param id path int true "Map ID"
20// @Success 200 {object} models.Response{data=models.Map{data=models.MapSummary}} 18// @Success 200 {object} models.Response{data=models.MapSummaryResponse}
21// @Failure 400 {object} models.Response 19// @Failure 400 {object} models.Response
22// @Router /maps/{id}/summary [get] 20// @Router /maps/{id}/summary [get]
23func FetchMapSummary(c *gin.Context) { 21func FetchMapSummary(c *gin.Context) {
24 id := c.Param("id") 22 id := c.Param("id")
25 // Get map data 23 response := models.MapSummaryResponse{Map: models.Map{}, Summary: models.MapSummary{Routes: []models.MapRoute{}}}
26 var mapData models.Map
27 var mapSummaryData models.MapSummary
28 var mapHistoryData []models.MapHistory
29 intID, err := strconv.Atoi(id) 24 intID, err := strconv.Atoi(id)
30 if err != nil { 25 if err != nil {
31 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 26 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
32 return 27 return
33 } 28 }
34 mapData.ID = intID 29 // Get map data
35 var routers pq.StringArray 30 response.Map.ID = intID
36 sql := `SELECT g.name, c.name, m.name, m.description, m.showcase, 31 sql := `SELECT m.id, g.name, c.name, m.name, m.image, g.is_coop
37 (
38 SELECT array_agg(user_name)
39 FROM map_routers
40 WHERE map_id = $1
41 AND score_count = (
42 SELECT score_count
43 FROM map_history
44 WHERE map_id = $1
45 ORDER BY score_count
46 LIMIT 1
47 )
48 GROUP BY map_routers.user_name
49 ORDER BY user_name
50 ),
51 (
52 SELECT COALESCE(avg(rating), 0.0)
53 FROM map_ratings
54 WHERE map_id = $1
55 )
56 FROM maps m 32 FROM maps m
57 INNER JOIN games g ON m.game_id = g.id 33 INNER JOIN games g ON m.game_id = g.id
58 INNER JOIN chapters c ON m.chapter_id = c.id 34 INNER JOIN chapters c ON m.chapter_id = c.id
59 WHERE m.id = $1` 35 WHERE m.id = $1`
60 // TODO: CategoryScores 36 err = database.DB.QueryRow(sql, id).Scan(&response.Map.ID, &response.Map.GameName, &response.Map.ChapterName, &response.Map.MapName, &response.Map.Image, &response.Map.IsCoop)
61 err = database.DB.QueryRow(sql, id).Scan(&mapData.GameName, &mapData.ChapterName, &mapData.MapName, &mapSummaryData.Description, &mapSummaryData.Showcase, &routers, &mapSummaryData.Rating)
62 if err != nil { 37 if err != nil {
63 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 38 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
64 return 39 return
65 } 40 }
66 var historyNames pq.StringArray 41 // Get map routes and histories
67 var historyScores pq.Int32Array 42 sql = `SELECT r.id, c.id, c.name, h.user_name, h.score_count, h.record_date, r.description, r.showcase, COALESCE(avg(rating), 0.0) FROM map_routes r
68 var historyDates pq.StringArray 43 INNER JOIN categories c ON r.category_id = c.id
69 sql = `SELECT array_agg(user_name), array_agg(score_count), array_agg(record_date) 44 INNER JOIN map_history h ON r.map_id = h.map_id AND r.category_id = h.category_id
70 FROM map_history 45 LEFT JOIN map_ratings rt ON r.map_id = rt.map_id AND r.category_id = rt.category_id
71 WHERE map_id = $1` 46 WHERE r.map_id = $1 AND h.score_count = r.score_count GROUP BY r.id, c.id, h.user_name, h.score_count, h.record_date, r.description, r.showcase
72 err = database.DB.QueryRow(sql, id).Scan(&historyNames, &historyScores, &historyDates) 47 ORDER BY h.record_date ASC;`
48 rows, err := database.DB.Query(sql, id)
73 if err != nil { 49 if err != nil {
74 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 50 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
75 return 51 return
76 } 52 }
77 for i := 0; i < len(historyNames); i++ { 53 for rows.Next() {
78 var history models.MapHistory 54 route := models.MapRoute{Category: models.Category{}, History: models.MapHistory{}}
79 history.RunnerName = historyNames[i] 55 err = rows.Scan(&route.RouteID, &route.Category.ID, &route.Category.Name, &route.History.RunnerName, &route.History.ScoreCount, &route.History.Date, &route.Description, &route.Showcase, &route.Rating)
80 history.ScoreCount = int(historyScores[i])
81 layout := "2006-01-02 15:04:05"
82 date, err := time.Parse(layout, historyDates[i])
83 if err != nil { 56 if err != nil {
84 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 57 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
85 return 58 return
86 } 59 }
87 history.Date = date 60 response.Summary.Routes = append(response.Summary.Routes, route)
88 mapHistoryData = append(mapHistoryData, history)
89 } 61 }
90 mapSummaryData.History = mapHistoryData
91 mapSummaryData.Routers = routers
92 mapData.Data = mapSummaryData
93 // Return response 62 // Return response
94 c.JSON(http.StatusOK, models.Response{ 63 c.JSON(http.StatusOK, models.Response{
95 Success: true, 64 Success: true,
96 Message: "Successfully retrieved map summary.", 65 Message: "Successfully retrieved map summary.",
97 Data: mapData, 66 Data: response,
98 }) 67 })
99} 68}
100 69
101// GET Map Leaderboards 70// GET Map Leaderboards
102// 71//
103// @Summary Get map leaderboards with specified id. 72// @Description Get map leaderboards with specified id.
104// @Tags maps 73// @Tags maps
105// @Produce json 74// @Produce json
106// @Param id path int true "Map ID" 75// @Param id path int true "Map ID"
107// @Success 200 {object} models.Response{data=models.Map{data=models.MapRecords}} 76// @Success 200 {object} models.Response{data=models.Map{data=models.MapRecords}}
108// @Failure 400 {object} models.Response 77// @Failure 400 {object} models.Response
109// @Router /maps/{id}/leaderboards [get] 78// @Router /maps/{id}/leaderboards [get]
110func FetchMapLeaderboards(c *gin.Context) { 79func FetchMapLeaderboards(c *gin.Context) {
80 // TODO: make new response type
111 id := c.Param("id") 81 id := c.Param("id")
112 // Get map data 82 // Get map data
113 var mapData models.Map 83 var mapData models.Map
@@ -119,12 +89,12 @@ func FetchMapLeaderboards(c *gin.Context) {
119 return 89 return
120 } 90 }
121 mapData.ID = intID 91 mapData.ID = intID
122 sql := `SELECT g.name, c.name, m.name, is_disabled 92 sql := `SELECT g.name, c.name, m.name, is_disabled, m.image
123 FROM maps m 93 FROM maps m
124 INNER JOIN games g ON m.game_id = g.id 94 INNER JOIN games g ON m.game_id = g.id
125 INNER JOIN chapters c ON m.chapter_id = c.id 95 INNER JOIN chapters c ON m.chapter_id = c.id
126 WHERE m.id = $1` 96 WHERE m.id = $1`
127 err = database.DB.QueryRow(sql, id).Scan(&mapData.GameName, &mapData.ChapterName, &mapData.MapName, &isDisabled) 97 err = database.DB.QueryRow(sql, id).Scan(&mapData.GameName, &mapData.ChapterName, &mapData.MapName, &isDisabled, &mapData.Image)
128 if err != nil { 98 if err != nil {
129 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 99 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
130 return 100 return
@@ -205,7 +175,7 @@ func FetchMapLeaderboards(c *gin.Context) {
205 } 175 }
206 mapRecordsData.Records = records 176 mapRecordsData.Records = records
207 } 177 }
208 mapData.Data = mapRecordsData 178 // mapData.Data = mapRecordsData
209 // Return response 179 // Return response
210 c.JSON(http.StatusOK, models.Response{ 180 c.JSON(http.StatusOK, models.Response{
211 Success: true, 181 Success: true,
@@ -216,14 +186,14 @@ func FetchMapLeaderboards(c *gin.Context) {
216 186
217// GET Games 187// GET Games
218// 188//
219// @Summary Get games from the leaderboards. 189// @Description Get games from the leaderboards.
220// @Tags games & chapters 190// @Tags games & chapters
221// @Produce json 191// @Produce json
222// @Success 200 {object} models.Response{data=[]models.Game} 192// @Success 200 {object} models.Response{data=[]models.Game}
223// @Failure 400 {object} models.Response 193// @Failure 400 {object} models.Response
224// @Router /games [get] 194// @Router /games [get]
225func FetchGames(c *gin.Context) { 195func FetchGames(c *gin.Context) {
226 rows, err := database.DB.Query(`SELECT id, name FROM games`) 196 rows, err := database.DB.Query(`SELECT id, name, is_coop FROM games`)
227 if err != nil { 197 if err != nil {
228 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 198 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
229 return 199 return
@@ -231,7 +201,7 @@ func FetchGames(c *gin.Context) {
231 var games []models.Game 201 var games []models.Game
232 for rows.Next() { 202 for rows.Next() {
233 var game models.Game 203 var game models.Game
234 if err := rows.Scan(&game.ID, &game.Name); err != nil { 204 if err := rows.Scan(&game.ID, &game.Name, &game.IsCoop); err != nil {
235 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 205 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
236 return 206 return
237 } 207 }
@@ -246,13 +216,13 @@ func FetchGames(c *gin.Context) {
246 216
247// GET Chapters of a Game 217// GET Chapters of a Game
248// 218//
249// @Summary Get chapters from the specified game id. 219// @Description Get chapters from the specified game id.
250// @Tags games & chapters 220// @Tags games & chapters
251// @Produce json 221// @Produce json
252// @Param id path int true "Game ID" 222// @Param id path int true "Game ID"
253// @Success 200 {object} models.Response{data=models.ChaptersResponse} 223// @Success 200 {object} models.Response{data=models.ChaptersResponse}
254// @Failure 400 {object} models.Response 224// @Failure 400 {object} models.Response
255// @Router /games/{id} [get] 225// @Router /games/{id} [get]
256func FetchChapters(c *gin.Context) { 226func FetchChapters(c *gin.Context) {
257 gameID := c.Param("id") 227 gameID := c.Param("id")
258 intID, err := strconv.Atoi(gameID) 228 intID, err := strconv.Atoi(gameID)
@@ -288,13 +258,13 @@ func FetchChapters(c *gin.Context) {
288 258
289// GET Maps of a Chapter 259// GET Maps of a Chapter
290// 260//
291// @Summary Get maps from the specified chapter id. 261// @Description Get maps from the specified chapter id.
292// @Tags games & chapters 262// @Tags games & chapters
293// @Produce json 263// @Produce json
294// @Param id path int true "Chapter ID" 264// @Param id path int true "Chapter ID"
295// @Success 200 {object} models.Response{data=models.ChapterMapsResponse} 265// @Success 200 {object} models.Response{data=models.ChapterMapsResponse}
296// @Failure 400 {object} models.Response 266// @Failure 400 {object} models.Response
297// @Router /chapters/{id} [get] 267// @Router /chapters/{id} [get]
298func FetchChapterMaps(c *gin.Context) { 268func FetchChapterMaps(c *gin.Context) {
299 chapterID := c.Param("id") 269 chapterID := c.Param("id")
300 intID, err := strconv.Atoi(chapterID) 270 intID, err := strconv.Atoi(chapterID)
diff --git a/backend/controllers/modController.go b/backend/controllers/modController.go
new file mode 100644
index 0000000..07edff5
--- /dev/null
+++ b/backend/controllers/modController.go
@@ -0,0 +1,327 @@
1package controllers
2
3import (
4 "net/http"
5 "strconv"
6
7 "github.com/gin-gonic/gin"
8 "github.com/pektezol/leastportals/backend/database"
9 "github.com/pektezol/leastportals/backend/models"
10)
11
12// POST Map Summary
13//
14// @Description Create map summary with specified map id.
15// @Tags maps
16// @Produce json
17// @Param Authorization header string true "JWT Token"
18// @Param id path int true "Map ID"
19// @Param request body models.CreateMapSummaryRequest true "Body"
20// @Success 200 {object} models.Response{data=models.CreateMapSummaryRequest}
21// @Failure 400 {object} models.Response
22// @Router /maps/{id}/summary [post]
23func CreateMapSummary(c *gin.Context) {
24 // Check if user exists
25 user, exists := c.Get("user")
26 if !exists {
27 c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in."))
28 return
29 }
30 var moderator bool
31 for _, title := range user.(models.User).Titles {
32 if title == "Moderator" {
33 moderator = true
34 }
35 }
36 if !moderator {
37 c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions."))
38 return
39 }
40 // Bind parameter and body
41 id := c.Param("id")
42 mapID, err := strconv.Atoi(id)
43 if err != nil {
44 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
45 return
46 }
47 var request models.CreateMapSummaryRequest
48 if err := c.BindJSON(&request); err != nil {
49 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
50 return
51 }
52 // Start database transaction
53 tx, err := database.DB.Begin()
54 if err != nil {
55 c.JSON(http.StatusInternalServerError, models.ErrorResponse(err.Error()))
56 return
57 }
58 defer tx.Rollback()
59 // Fetch route category and score count
60 var checkMapID int
61 sql := `SELECT m.id FROM maps m WHERE m.id = $1`
62 err = database.DB.QueryRow(sql, mapID).Scan(&checkMapID)
63 if err != nil {
64 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
65 return
66 }
67 if mapID != checkMapID {
68 c.JSON(http.StatusBadRequest, models.ErrorResponse("Map ID does not exist."))
69 return
70 }
71 // Update database with new data
72 sql = `INSERT INTO map_routes (map_id,category_id,score_count,description,showcase)
73 VALUES ($1,$2,$3,$4,$5)`
74 _, err = tx.Exec(sql, mapID, request.CategoryID, request.ScoreCount, request.Description, request.Showcase)
75 if err != nil {
76 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
77 return
78 }
79 sql = `INSERT INTO map_history (map_id,category_id,user_name,score_count,record_date)
80 VALUES ($1,$2,$3,$4,$5)`
81 _, err = tx.Exec(sql, mapID, request.CategoryID, request.UserName, request.ScoreCount, request.RecordDate)
82 if err != nil {
83 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
84 return
85 }
86 if err = tx.Commit(); err != nil {
87 c.JSON(http.StatusInternalServerError, models.ErrorResponse(err.Error()))
88 return
89 }
90 // Return response
91 c.JSON(http.StatusOK, models.Response{
92 Success: true,
93 Message: "Successfully created map summary.",
94 Data: request,
95 })
96}
97
98// PUT Map Summary
99//
100// @Description Edit map summary with specified map id.
101// @Tags maps
102// @Produce json
103// @Param Authorization header string true "JWT Token"
104// @Param id path int true "Map ID"
105// @Param request body models.EditMapSummaryRequest true "Body"
106// @Success 200 {object} models.Response{data=models.EditMapSummaryRequest}
107// @Failure 400 {object} models.Response
108// @Router /maps/{id}/summary [put]
109func EditMapSummary(c *gin.Context) {
110 // Check if user exists
111 user, exists := c.Get("user")
112 if !exists {
113 c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in."))
114 return
115 }
116 var moderator bool
117 for _, title := range user.(models.User).Titles {
118 if title == "Moderator" {
119 moderator = true
120 }
121 }
122 if !moderator {
123 c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions."))
124 return
125 }
126 // Bind parameter and body
127 id := c.Param("id")
128 mapID, err := strconv.Atoi(id)
129 if err != nil {
130 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
131 return
132 }
133 var request models.EditMapSummaryRequest
134 if err := c.BindJSON(&request); err != nil {
135 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
136 return
137 }
138 // Start database transaction
139 tx, err := database.DB.Begin()
140 if err != nil {
141 c.JSON(http.StatusInternalServerError, models.ErrorResponse(err.Error()))
142 return
143 }
144 defer tx.Rollback()
145 // Fetch route category and score count
146 var categoryID, scoreCount, historyID int
147 sql := `SELECT mr.category_id, mr.score_count FROM map_routes mr INNER JOIN maps m ON m.id = mr.map_id WHERE m.id = $1 AND mr.id = $2`
148 err = database.DB.QueryRow(sql, mapID, request.RouteID).Scan(&categoryID, &scoreCount)
149 if err != nil {
150 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
151 return
152 }
153 sql = `SELECT mh.id FROM map_history mh WHERE mh.score_count = $1 AND mh.category_id = $2 AND mh.map_id = $3`
154 err = database.DB.QueryRow(sql, scoreCount, categoryID, mapID).Scan(&historyID)
155 if err != nil {
156 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
157 return
158 }
159 // Update database with new data
160 sql = `UPDATE map_routes SET score_count = $2, description = $3, showcase = $4 WHERE id = $1`
161 _, err = tx.Exec(sql, request.RouteID, request.ScoreCount, request.Description, request.Showcase)
162 if err != nil {
163 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
164 return
165 }
166 sql = `UPDATE map_history SET user_name = $2, score_count = $3, record_date = $4 WHERE id = $1`
167 _, err = tx.Exec(sql, historyID, request.UserName, request.ScoreCount, request.RecordDate)
168 if err != nil {
169 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
170 return
171 }
172 if err = tx.Commit(); err != nil {
173 c.JSON(http.StatusInternalServerError, models.ErrorResponse(err.Error()))
174 return
175 }
176 // Return response
177 c.JSON(http.StatusOK, models.Response{
178 Success: true,
179 Message: "Successfully updated map summary.",
180 Data: request,
181 })
182}
183
184// DELETE Map Summary
185//
186// @Description Delete map summary with specified map id.
187// @Tags maps
188// @Produce json
189// @Param Authorization header string true "JWT Token"
190// @Param id path int true "Map ID"
191// @Param request body models.DeleteMapSummaryRequest true "Body"
192// @Success 200 {object} models.Response{data=models.DeleteMapSummaryRequest}
193// @Failure 400 {object} models.Response
194// @Router /maps/{id}/summary [delete]
195func DeleteMapSummary(c *gin.Context) {
196 // Check if user exists
197 user, exists := c.Get("user")
198 if !exists {
199 c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in."))
200 return
201 }
202 var moderator bool
203 for _, title := range user.(models.User).Titles {
204 if title == "Moderator" {
205 moderator = true
206 }
207 }
208 if !moderator {
209 c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions."))
210 return
211 }
212 // Bind parameter and body
213 id := c.Param("id")
214 mapID, err := strconv.Atoi(id)
215 if err != nil {
216 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
217 return
218 }
219 var request models.DeleteMapSummaryRequest
220 if err := c.BindJSON(&request); err != nil {
221 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
222 return
223 }
224 // Start database transaction
225 tx, err := database.DB.Begin()
226 if err != nil {
227 c.JSON(http.StatusInternalServerError, models.ErrorResponse(err.Error()))
228 return
229 }
230 defer tx.Rollback()
231 // Fetch route category and score count
232 var checkMapID, scoreCount, mapHistoryID int
233 sql := `SELECT m.id, mr.score_count FROM maps m INNER JOIN map_routes mr ON m.id=mr.map_id WHERE m.id = $1 AND mr.id = $2`
234 err = database.DB.QueryRow(sql, mapID, request.RouteID).Scan(&checkMapID, &scoreCount)
235 if err != nil {
236 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
237 return
238 }
239 if mapID != checkMapID {
240 c.JSON(http.StatusBadRequest, models.ErrorResponse("Map ID does not exist."))
241 return
242 }
243 sql = `SELECT mh.id FROM maps m INNER JOIN map_routes mr ON m.id=mr.map_id INNER JOIN map_history mh ON m.id=mh.map_id WHERE m.id = $1 AND mr.id = $2 AND mh.score_count = $3`
244 err = database.DB.QueryRow(sql, mapID, request.RouteID, scoreCount).Scan(&mapHistoryID)
245 if err != nil {
246 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
247 return
248 }
249 // Update database with new data
250 sql = `DELETE FROM map_routes mr WHERE mr.id = $1 `
251 _, err = tx.Exec(sql, request.RouteID)
252 if err != nil {
253 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
254 return
255 }
256 sql = `DELETE FROM map_history mh WHERE mh.id = $1`
257 _, err = tx.Exec(sql, mapHistoryID)
258 if err != nil {
259 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
260 return
261 }
262 if err = tx.Commit(); err != nil {
263 c.JSON(http.StatusInternalServerError, models.ErrorResponse(err.Error()))
264 return
265 }
266 // Return response
267 c.JSON(http.StatusOK, models.Response{
268 Success: true,
269 Message: "Successfully delete map summary.",
270 Data: request,
271 })
272}
273
274// PUT Map Image
275//
276// @Description Edit map image with specified map id.
277// @Tags maps
278// @Produce json
279// @Param Authorization header string true "JWT Token"
280// @Param id path int true "Map ID"
281// @Param request body models.EditMapImageRequest true "Body"
282// @Success 200 {object} models.Response{data=models.EditMapImageRequest}
283// @Failure 400 {object} models.Response
284// @Router /maps/{id}/image [put]
285func EditMapImage(c *gin.Context) {
286 // Check if user exists
287 user, exists := c.Get("user")
288 if !exists {
289 c.JSON(http.StatusUnauthorized, models.ErrorResponse("User not logged in."))
290 return
291 }
292 var moderator bool
293 for _, title := range user.(models.User).Titles {
294 if title == "Moderator" {
295 moderator = true
296 }
297 }
298 if !moderator {
299 c.JSON(http.StatusUnauthorized, models.ErrorResponse("Insufficient permissions."))
300 return
301 }
302 // Bind parameter and body
303 id := c.Param("id")
304 mapID, err := strconv.Atoi(id)
305 if err != nil {
306 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
307 return
308 }
309 var request models.EditMapImageRequest
310 if err := c.BindJSON(&request); err != nil {
311 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
312 return
313 }
314 // Update database with new data
315 sql := `UPDATE maps SET image = $2 WHERE id = $1`
316 _, err = database.DB.Exec(sql, mapID, request.Image)
317 if err != nil {
318 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
319 return
320 }
321 // Return response
322 c.JSON(http.StatusOK, models.Response{
323 Success: true,
324 Message: "Successfully updated map image.",
325 Data: request,
326 })
327}
diff --git a/backend/controllers/recordController.go b/backend/controllers/recordController.go
index 627be57..d1404f4 100644
--- a/backend/controllers/recordController.go
+++ b/backend/controllers/recordController.go
@@ -2,17 +2,18 @@ package controllers
2 2
3import ( 3import (
4 "context" 4 "context"
5 b64 "encoding/base64" 5 "encoding/base64"
6 "io" 6 "io"
7 "log" 7 "log"
8 "mime/multipart"
8 "net/http" 9 "net/http"
9 "os" 10 "os"
10 "strconv"
11 11
12 "github.com/gin-gonic/gin" 12 "github.com/gin-gonic/gin"
13 "github.com/google/uuid" 13 "github.com/google/uuid"
14 "github.com/pektezol/leastportals/backend/database" 14 "github.com/pektezol/leastportals/backend/database"
15 "github.com/pektezol/leastportals/backend/models" 15 "github.com/pektezol/leastportals/backend/models"
16 "github.com/pektezol/leastportals/backend/parser"
16 "golang.org/x/oauth2/google" 17 "golang.org/x/oauth2/google"
17 "golang.org/x/oauth2/jwt" 18 "golang.org/x/oauth2/jwt"
18 "google.golang.org/api/drive/v3" 19 "google.golang.org/api/drive/v3"
@@ -20,20 +21,20 @@ import (
20 21
21// POST Record 22// POST Record
22// 23//
23// @Summary Post record with demo of a specific map. 24// @Description Post record with demo of a specific map.
24// @Tags maps 25// @Tags maps
25// @Accept mpfd 26// @Accept mpfd
26// @Produce json 27// @Produce json
27// @Param Authorization header string true "JWT Token" 28// @Param id path int true "Map ID"
28// @Param demos formData []file true "Demos" 29// @Param Authorization header string true "JWT Token"
29// @Param score_count formData int true "Score Count" 30// @Param host_demo formData file true "Host Demo"
30// @Param score_time formData int true "Score Time" 31// @Param partner_demo formData file false "Partner Demo"
31// @Param is_partner_orange formData boolean true "Is Partner Orange" 32// @Param is_partner_orange formData boolean false "Is Partner Orange"
32// @Param partner_id formData string true "Partner ID" 33// @Param partner_id formData string false "Partner ID"
33// @Success 200 {object} models.Response{data=models.RecordRequest} 34// @Success 200 {object} models.Response{data=models.RecordResponse}
34// @Failure 400 {object} models.Response 35// @Failure 400 {object} models.Response
35// @Failure 401 {object} models.Response 36// @Failure 401 {object} models.Response
36// @Router /maps/{id}/record [post] 37// @Router /maps/{id}/record [post]
37func CreateRecordWithDemo(c *gin.Context) { 38func CreateRecordWithDemo(c *gin.Context) {
38 mapId := c.Param("id") 39 mapId := c.Param("id")
39 // Check if user exists 40 // Check if user exists
@@ -43,11 +44,11 @@ func CreateRecordWithDemo(c *gin.Context) {
43 return 44 return
44 } 45 }
45 // Check if map is sp or mp 46 // Check if map is sp or mp
46 var gameID int 47 var gameName string
47 var isCoop bool 48 var isCoop bool
48 var isDisabled bool 49 var isDisabled bool
49 sql := `SELECT game_id, is_disabled FROM maps WHERE id = $1` 50 sql := `SELECT g.name, m.is_disabled FROM maps m INNER JOIN games g ON m.game_id=g.id WHERE m.id = $1`
50 err := database.DB.QueryRow(sql, mapId).Scan(&gameID, &isDisabled) 51 err := database.DB.QueryRow(sql, mapId).Scan(&gameName, &isDisabled)
51 if err != nil { 52 if err != nil {
52 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 53 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
53 return 54 return
@@ -56,51 +57,26 @@ func CreateRecordWithDemo(c *gin.Context) {
56 c.JSON(http.StatusBadRequest, models.ErrorResponse("Map is not available for competitive boards.")) 57 c.JSON(http.StatusBadRequest, models.ErrorResponse("Map is not available for competitive boards."))
57 return 58 return
58 } 59 }
59 if gameID == 2 { 60 if gameName == "Portal 2 - Cooperative" {
60 isCoop = true 61 isCoop = true
61 } 62 }
62 // Get record request 63 // Get record request
63 var record models.RecordRequest 64 var record models.RecordRequest
64 score_count, err := strconv.Atoi(c.PostForm("score_count")) 65 if err := c.ShouldBind(&record); err != nil {
65 if err != nil {
66 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
67 return
68 }
69 score_time, err := strconv.Atoi(c.PostForm("score_time"))
70 if err != nil {
71 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
72 return
73 }
74 is_partner_orange, err := strconv.ParseBool(c.PostForm("is_partner_orange"))
75 if err != nil {
76 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
77 return
78 }
79 record.ScoreCount = score_count
80 record.ScoreTime = score_time
81 record.PartnerID = c.PostForm("partner_id")
82 record.IsPartnerOrange = is_partner_orange
83 if record.PartnerID == "" {
84 c.JSON(http.StatusBadRequest, models.ErrorResponse("No partner id given."))
85 return
86 }
87 // Multipart form
88 form, err := c.MultipartForm()
89 if err != nil {
90 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 66 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
91 return 67 return
92 } 68 }
93 files := form.File["demos"] 69 if isCoop && (record.PartnerDemo == nil || record.PartnerID == "") {
94 if len(files) != 2 && isCoop { 70 c.JSON(http.StatusBadRequest, models.ErrorResponse("Invalid entry for coop record submission."))
95 c.JSON(http.StatusBadRequest, models.ErrorResponse("Not enough demos for coop submission."))
96 return 71 return
97 } 72 }
98 if len(files) != 1 && !isCoop { 73 // Demo files
99 c.JSON(http.StatusBadRequest, models.ErrorResponse("Too many demos for singleplayer submission.")) 74 demoFiles := []*multipart.FileHeader{record.HostDemo}
100 return 75 if isCoop {
76 demoFiles = append(demoFiles, record.PartnerDemo)
101 } 77 }
102 var hostDemoUUID string 78 var hostDemoUUID, hostDemoFileID, partnerDemoUUID, partnerDemoFileID string
103 var partnerDemoUUID string 79 var hostDemoScoreCount, hostDemoScoreTime int
104 client := serviceAccount() 80 client := serviceAccount()
105 srv, err := drive.New(client) 81 srv, err := drive.New(client)
106 if err != nil { 82 if err != nil {
@@ -115,16 +91,16 @@ func CreateRecordWithDemo(c *gin.Context) {
115 } 91 }
116 // Defer to a rollback in case anything fails 92 // Defer to a rollback in case anything fails
117 defer tx.Rollback() 93 defer tx.Rollback()
118 fileID := "" 94 for i, header := range demoFiles {
119 for i, header := range files {
120 uuid := uuid.New().String() 95 uuid := uuid.New().String()
121 // Upload & insert into demos 96 // Upload & insert into demos
122 err = c.SaveUploadedFile(header, "docs/"+header.Filename) 97 err = c.SaveUploadedFile(header, "backend/parser/"+uuid+".dem")
123 if err != nil { 98 if err != nil {
124 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 99 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
125 return 100 return
126 } 101 }
127 f, err := os.Open("docs/" + header.Filename) 102 defer os.Remove("backend/parser/" + uuid + ".dem")
103 f, err := os.Open("backend/parser/" + uuid + ".dem")
128 if err != nil { 104 if err != nil {
129 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 105 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
130 return 106 return
@@ -135,11 +111,16 @@ func CreateRecordWithDemo(c *gin.Context) {
135 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 111 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
136 return 112 return
137 } 113 }
138 fileID = file.Id 114 hostDemoScoreCount, hostDemoScoreTime, err = parser.ProcessDemo("backend/parser/" + uuid + ".dem")
115 if err != nil {
116 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
117 return
118 }
139 if i == 0 { 119 if i == 0 {
120 hostDemoFileID = file.Id
140 hostDemoUUID = uuid 121 hostDemoUUID = uuid
141 } 122 } else if i == 1 {
142 if i == 1 { 123 partnerDemoFileID = file.Id
143 partnerDemoUUID = uuid 124 partnerDemoUUID = uuid
144 } 125 }
145 _, err = tx.Exec(`INSERT INTO demos (id,location_id) VALUES ($1,$2)`, uuid, file.Id) 126 _, err = tx.Exec(`INSERT INTO demos (id,location_id) VALUES ($1,$2)`, uuid, file.Id)
@@ -148,7 +129,6 @@ func CreateRecordWithDemo(c *gin.Context) {
148 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 129 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
149 return 130 return
150 } 131 }
151 os.Remove("docs/" + header.Filename)
152 } 132 }
153 // Insert into records 133 // Insert into records
154 if isCoop { 134 if isCoop {
@@ -163,9 +143,10 @@ func CreateRecordWithDemo(c *gin.Context) {
163 partnerID = user.(models.User).SteamID 143 partnerID = user.(models.User).SteamID
164 hostID = record.PartnerID 144 hostID = record.PartnerID
165 } 145 }
166 _, err := tx.Exec(sql, mapId, record.ScoreCount, record.ScoreTime, hostID, partnerID, hostDemoUUID, partnerDemoUUID) 146 _, err := tx.Exec(sql, mapId, hostDemoScoreCount, hostDemoScoreTime, hostID, partnerID, hostDemoUUID, partnerDemoUUID)
167 if err != nil { 147 if err != nil {
168 deleteFile(srv, fileID) 148 deleteFile(srv, hostDemoFileID)
149 deleteFile(srv, partnerDemoFileID)
169 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 150 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
170 return 151 return
171 } 152 }
@@ -180,9 +161,9 @@ func CreateRecordWithDemo(c *gin.Context) {
180 } else { 161 } else {
181 sql := `INSERT INTO records_sp(map_id,score_count,score_time,user_id,demo_id) 162 sql := `INSERT INTO records_sp(map_id,score_count,score_time,user_id,demo_id)
182 VALUES($1, $2, $3, $4, $5)` 163 VALUES($1, $2, $3, $4, $5)`
183 _, err := tx.Exec(sql, mapId, record.ScoreCount, record.ScoreTime, user.(models.User).SteamID, hostDemoUUID) 164 _, err := tx.Exec(sql, mapId, hostDemoScoreCount, hostDemoScoreTime, user.(models.User).SteamID, hostDemoUUID)
184 if err != nil { 165 if err != nil {
185 deleteFile(srv, fileID) 166 deleteFile(srv, hostDemoFileID)
186 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) 167 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
187 return 168 return
188 } 169 }
@@ -202,21 +183,20 @@ func CreateRecordWithDemo(c *gin.Context) {
202 c.JSON(http.StatusOK, models.Response{ 183 c.JSON(http.StatusOK, models.Response{
203 Success: true, 184 Success: true,
204 Message: "Successfully created record.", 185 Message: "Successfully created record.",
205 Data: record, 186 Data: models.RecordResponse{ScoreCount: hostDemoScoreCount, ScoreTime: hostDemoScoreTime},
206 }) 187 })
207 return
208} 188}
209 189
210// GET Demo 190// GET Demo
211// 191//
212// @Summary Get demo with specified demo uuid. 192// @Description Get demo with specified demo uuid.
213// @Tags demo 193// @Tags demo
214// @Accept json 194// @Accept json
215// @Produce octet-stream 195// @Produce octet-stream
216// @Param uuid query int true "Demo UUID" 196// @Param uuid query string true "Demo UUID"
217// @Success 200 {file} binary "Demo File" 197// @Success 200 {file} binary "Demo File"
218// @Failure 400 {object} models.Response 198// @Failure 400 {object} models.Response
219// @Router /demos [get] 199// @Router /demos [get]
220func DownloadDemoWithID(c *gin.Context) { 200func DownloadDemoWithID(c *gin.Context) {
221 uuid := c.Query("uuid") 201 uuid := c.Query("uuid")
222 var locationID string 202 var locationID string
@@ -260,7 +240,7 @@ func DownloadDemoWithID(c *gin.Context) {
260 240
261// Use Service account 241// Use Service account
262func serviceAccount() *http.Client { 242func serviceAccount() *http.Client {
263 privateKey, _ := b64.StdEncoding.DecodeString(os.Getenv("GOOGLE_PRIVATE_KEY_BASE64")) 243 privateKey, _ := base64.StdEncoding.DecodeString(os.Getenv("GOOGLE_PRIVATE_KEY_BASE64"))
264 config := &jwt.Config{ 244 config := &jwt.Config{
265 Email: os.Getenv("GOOGLE_CLIENT_EMAIL"), 245 Email: os.Getenv("GOOGLE_CLIENT_EMAIL"),
266 PrivateKey: []byte(privateKey), 246 PrivateKey: []byte(privateKey),
diff --git a/backend/controllers/userController.go b/backend/controllers/userController.go
index adf936b..e73b1fe 100644
--- a/backend/controllers/userController.go
+++ b/backend/controllers/userController.go
@@ -13,15 +13,15 @@ import (
13 13
14// GET Profile 14// GET Profile
15// 15//
16// @Summary Get profile page of session user. 16// @Description Get profile page of session user.
17// @Tags users 17// @Tags users
18// @Accept json 18// @Accept json
19// @Produce json 19// @Produce json
20// @Param Authorization header string true "JWT Token" 20// @Param Authorization header string true "JWT Token"
21// @Success 200 {object} models.Response{data=models.ProfileResponse} 21// @Success 200 {object} models.Response{data=models.ProfileResponse}
22// @Failure 400 {object} models.Response 22// @Failure 400 {object} models.Response
23// @Failure 401 {object} models.Response 23// @Failure 401 {object} models.Response
24// @Router /profile [get] 24// @Router /profile [get]
25func Profile(c *gin.Context) { 25func Profile(c *gin.Context) {
26 // Check if user exists 26 // Check if user exists
27 user, exists := c.Get("user") 27 user, exists := c.Get("user")
@@ -100,15 +100,15 @@ func Profile(c *gin.Context) {
100 100
101// GET User 101// GET User
102// 102//
103// @Summary Get profile page of another user. 103// @Description Get profile page of another user.
104// @Tags users 104// @Tags users
105// @Accept json 105// @Accept json
106// @Produce json 106// @Produce json
107// @Param id path int true "User ID" 107// @Param id path int true "User ID"
108// @Success 200 {object} models.Response{data=models.ProfileResponse} 108// @Success 200 {object} models.Response{data=models.ProfileResponse}
109// @Failure 400 {object} models.Response 109// @Failure 400 {object} models.Response
110// @Failure 404 {object} models.Response 110// @Failure 404 {object} models.Response
111// @Router /users/{id} [get] 111// @Router /users/{id} [get]
112func FetchUser(c *gin.Context) { 112func FetchUser(c *gin.Context) {
113 id := c.Param("id") 113 id := c.Param("id")
114 // Check if id is all numbers and 17 length 114 // Check if id is all numbers and 17 length
@@ -202,15 +202,15 @@ func FetchUser(c *gin.Context) {
202 202
203// PUT Profile 203// PUT Profile
204// 204//
205// @Summary Update profile page of session user. 205// @Description Update profile page of session user.
206// @Tags users 206// @Tags users
207// @Accept json 207// @Accept json
208// @Produce json 208// @Produce json
209// @Param Authorization header string true "JWT Token" 209// @Param Authorization header string true "JWT Token"
210// @Success 200 {object} models.Response{data=models.ProfileResponse} 210// @Success 200 {object} models.Response{data=models.ProfileResponse}
211// @Failure 400 {object} models.Response 211// @Failure 400 {object} models.Response
212// @Failure 401 {object} models.Response 212// @Failure 401 {object} models.Response
213// @Router /profile [post] 213// @Router /profile [post]
214func UpdateUser(c *gin.Context) { 214func UpdateUser(c *gin.Context) {
215 // Check if user exists 215 // Check if user exists
216 user, exists := c.Get("user") 216 user, exists := c.Get("user")
@@ -245,16 +245,16 @@ func UpdateUser(c *gin.Context) {
245 245
246// PUT Profile/CountryCode 246// PUT Profile/CountryCode
247// 247//
248// @Summary Update country code of session user. 248// @Description Update country code of session user.
249// @Tags users 249// @Tags users
250// @Accept json 250// @Accept json
251// @Produce json 251// @Produce json
252// @Param Authorization header string true "JWT Token" 252// @Param Authorization header string true "JWT Token"
253// @Param country_code query string true "Country Code [XX]" 253// @Param country_code query string true "Country Code [XX]"
254// @Success 200 {object} models.Response{data=models.ProfileResponse} 254// @Success 200 {object} models.Response
255// @Failure 400 {object} models.Response 255// @Failure 400 {object} models.Response
256// @Failure 401 {object} models.Response 256// @Failure 401 {object} models.Response
257// @Router /profile [put] 257// @Router /profile [put]
258func UpdateCountryCode(c *gin.Context) { 258func UpdateCountryCode(c *gin.Context) {
259 // Check if user exists 259 // Check if user exists
260 user, exists := c.Get("user") 260 user, exists := c.Get("user")
diff --git a/backend/database/chapters.sql b/backend/database/chapters.sql
index f01ae9c..9538bed 100644
--- a/backend/database/chapters.sql
+++ b/backend/database/chapters.sql
@@ -1,17 +1,17 @@
1INSERT INTO chapters(id, game_id, name) VALUES 1INSERT INTO chapters(id, game_id, name) VALUES
2(1, 1, 'The Coutesy Call'), 2(1, 1, 'Chapter 1 - The Coutesy Call'),
3(2, 1, 'The Cold Boot'), 3(2, 1, 'Chapter 2 - The Cold Boot'),
4(3, 1, 'The Return'), 4(3, 1, 'Chapter 3 - The Return'),
5(4, 1, 'The Surprise'), 5(4, 1, 'Chapter 4 - The Surprise'),
6(5, 1, 'The Escape'), 6(5, 1, 'Chapter 5 - The Escape'),
7(6, 1, 'The Fall'), 7(6, 1, 'Chapter 6 - The Fall'),
8(7, 1, 'The Reunion'), 8(7, 1, 'Chapter 7 - The Reunion'),
9(8, 1, 'The Itch'), 9(8, 1, 'Chapter 8 - The Itch'),
10(9, 1, 'The Part Where He Kills You'), 10(9, 1, 'Chapter 9 - The Part Where He Kills You'),
11(10, 2, 'Introduction'), 11(10, 2, 'Course 0 - Introduction'),
12(11, 2, 'Team Building'), 12(11, 2, 'Course 1 - Team Building'),
13(12, 2, 'Mass And Velocity'), 13(12, 2, 'Course 2 - Mass And Velocity'),
14(13, 2, 'Hard-Light Surfaces'), 14(13, 2, 'Course 3 - Hard-Light Surfaces'),
15(14, 2, 'Excursion Funnels'), 15(14, 2, 'Course 4 - Excursion Funnels'),
16(15, 2, 'Mobility Gels'), 16(15, 2, 'Course 5 - Mobility Gels'),
17(16, 2, 'Art Therapy'); \ No newline at end of file 17(16, 2, 'Course 6 - Art Therapy'); \ No newline at end of file
diff --git a/backend/database/games.sql b/backend/database/games.sql
index 5e2f4ee..0c2374c 100644
--- a/backend/database/games.sql
+++ b/backend/database/games.sql
@@ -1,3 +1,3 @@
1INSERT INTO games(id, name) VALUES 1INSERT INTO games(id, name, is_coop) VALUES
2(1, 'Portal 2 - Singleplayer'), 2(1, 'Portal 2 - Singleplayer', false),
3(2, 'Portal 2 - Cooperative'); \ No newline at end of file 3(2, 'Portal 2 - Cooperative', true); \ No newline at end of file
diff --git a/backend/database/history.sql b/backend/database/history.sql
index 0840bf3..320d72f 100644
--- a/backend/database/history.sql
+++ b/backend/database/history.sql
@@ -1,279 +1,279 @@
1INSERT INTO map_history(map_id,user_name,score_count,record_date) VALUES 1INSERT INTO map_history(map_id,category_id,user_name,score_count,record_date) VALUES
2-- Portal 2 Singleplayer 2-- Portal 2 Singleplayer
3-- 1 3-- 1
4(3,'slmid1995',3,'2011-10-05 00:00:00'), 4(3,1,'slmid1995',3,'2011-10-05 00:00:00'),
5(3,'LookLikeAKango',1,'2011-10-06 00:00:00'), 5(3,1,'LookLikeAKango',1,'2011-10-06 00:00:00'),
6(3,'Bananasaurus Rex',0,'2011-10-24 00:00:00'), 6(3,1,'Bananasaurus Rex',0,'2011-10-24 00:00:00'),
7(4,'Tyronis',1,'2011-10-05 00:00:00'), 7(4,1,'Tyronis',1,'2011-10-05 00:00:00'),
8(4,'Krzyhau',0,'2019-05-10 00:00:00'), 8(4,1,'Krzyhau',0,'2019-05-10 00:00:00'),
9(5,'LookLikeAKango',2,'2011-10-05 00:00:00'), 9(5,1,'LookLikeAKango',2,'2011-10-05 00:00:00'),
10(5,'Jetwash',1,'2013-12-03 00:00:00'), 10(5,1,'Jetwash',1,'2013-12-03 00:00:00'),
11(6,'Stimich',4,'2011-10-08 00:00:00'), 11(6,1,'Stimich',4,'2011-10-08 00:00:00'),
12(6,'aepaePolakrn',3,'2011-10-19 00:00:00'), 12(6,1,'aepaePolakrn',3,'2011-10-19 00:00:00'),
13(6,'Krzyhau',2,'2020-10-10 00:00:00'), 13(6,1,'Krzyhau',2,'2020-10-10 00:00:00'),
14(9,'slmid1995',4,'2011-10-05 00:00:00'), 14(9,1,'slmid1995',4,'2011-10-05 00:00:00'),
15(9,'Jokie',3,'2011-10-05 00:00:00'), 15(9,1,'Jokie',3,'2011-10-05 00:00:00'),
16(9,'Tyronis',2,'2011-10-05 00:00:00'), 16(9,1,'Tyronis',2,'2011-10-05 00:00:00'),
17(9,'sicklebrick',0,'2013-03-13 00:00:00'), 17(9,1,'sicklebrick',0,'2013-03-13 00:00:00'),
18-- 2 18-- 2
19(10,'Paraxade0',2,'2011-04-21 00:00:00'), 19(10,1,'Paraxade0',2,'2011-04-21 00:00:00'),
20(10,'PerOculos',0,'2011-04-21 00:00:00'), 20(10,1,'PerOculos',0,'2011-04-21 00:00:00'),
21(11,'Tyronis',2,'2011-10-05 00:00:00'), 21(11,1,'Tyronis',2,'2011-10-05 00:00:00'),
22(11,'Krzyhau',0,'2018-06-09 00:00:00'), 22(11,1,'Krzyhau',0,'2018-06-09 00:00:00'),
23(12,'slmid1995',2,'2011-10-04 00:00:00'), 23(12,1,'slmid1995',2,'2011-10-04 00:00:00'),
24(13,'LookLikeAKango',3,'2011-10-05 00:00:00'), 24(13,1,'LookLikeAKango',3,'2011-10-05 00:00:00'),
25(13,'Imanex',2,'2011-12-08 00:00:00'), 25(13,1,'Imanex',2,'2011-12-08 00:00:00'),
26(13,'jyjey',0,'2012-08-22 00:00:00'), 26(13,1,'jyjey',0,'2012-08-22 00:00:00'),
27(15,'Tyronis',2,'2011-10-05 00:00:00'), 27(15,1,'Tyronis',2,'2011-10-05 00:00:00'),
28(16,'LookLikeAKango',2,'2011-10-05 00:00:00'), 28(16,1,'LookLikeAKango',2,'2011-10-05 00:00:00'),
29(16,'jyjey',0,'2012-08-25 00:00:00'), 29(16,1,'jyjey',0,'2012-08-25 00:00:00'),
30(17,'rocoty',0,'2011-10-05 00:00:00'), 30(17,1,'rocoty',2,'2011-10-05 00:00:00'),
31(17,'Nidboj132',0,'2023-02-05 00:00:00'), 31(17,1,'Nidboj132',0,'2023-02-05 00:00:00'),
32-- 3 32-- 3
33(18,'The Last Tofus',5,'2011-05-08 00:00:00'), 33(18,1,'The Last Tofus',5,'2011-05-08 00:00:00'),
34(18,'Schlepian',4,'2011-10-08 00:00:00'), 34(18,1,'Schlepian',4,'2011-10-08 00:00:00'),
35(18,'szeimartin',3,'2013-10-08 00:00:00'), 35(18,1,'szeimartin',3,'2013-10-08 00:00:00'),
36(18,'Krzyhau',2,'2020-05-15 00:00:00'), 36(18,1,'Krzyhau',2,'2020-05-15 00:00:00'),
37(18,'Krzyhau',0,'2022-07-02 00:00:00'), 37(18,1,'Krzyhau',0,'2022-07-02 00:00:00'),
38(19,'LookLikeAKango',2,'2011-10-06 00:00:00'), 38(19,1,'LookLikeAKango',2,'2011-10-06 00:00:00'),
39(20,'Djinndrache',5,'2011-10-20 00:00:00'), 39(20,1,'Djinndrache',5,'2011-10-20 00:00:00'),
40(20,'Schlepian',4,'2011-10-30 00:00:00'), 40(20,1,'Schlepian',4,'2011-10-30 00:00:00'),
41(20,'Jetwash',3,'2014-09-04 00:00:00'), 41(20,1,'Jetwash',3,'2014-09-04 00:00:00'),
42(20,'Krzyhau',2,'2022-04-24 00:00:00'), 42(20,1,'Krzyhau',2,'2022-04-24 00:00:00'),
43(21,'LookLikeAKango',4,'2011-10-06 00:00:00'), 43(21,1,'LookLikeAKango',4,'2011-10-06 00:00:00'),
44(21,'ncla',2,'2011-10-30 00:00:00'), 44(21,1,'ncla',2,'2011-10-30 00:00:00'),
45(21,'PerOculos',0,'2019-07-08 00:00:00'), 45(21,1,'PerOculos',0,'2019-07-08 00:00:00'),
46(22,'Tyronis',0,'2011-10-05 00:00:00'), 46(22,1,'Tyronis',0,'2011-10-05 00:00:00'),
47(23,'LookLikeAKango',2,'2011-10-06 00:00:00'), 47(23,1,'LookLikeAKango',2,'2011-10-06 00:00:00'),
48(23,'Krzyhau',0,'2018-08-01 00:00:00'), 48(23,1,'Krzyhau',0,'2018-08-01 00:00:00'),
49(24,'LeviHB',0,'2011-04-30 00:00:00'), 49(24,1,'LeviHB',0,'2011-04-30 00:00:00'),
50(25,'Tyronis',0,'2011-10-06 00:00:00'), 50(25,1,'Tyronis',0,'2011-10-06 00:00:00'),
51(26,'Schlepian',3,'2011-10-30 00:00:00'), 51(26,1,'Schlepian',3,'2011-10-30 00:00:00'),
52(26,'Tyronis',2,'2012-01-08 00:00:00'), 52(26,1,'Tyronis',2,'2012-01-08 00:00:00'),
53(26,'PerOculos',0,'2016-06-08 00:00:00'), 53(26,1,'PerOculos',0,'2016-06-08 00:00:00'),
54-- 4 54-- 4
55(27,'LeviHB',2,'2011-05-01 00:00:00'), 55(27,1,'LeviHB',2,'2011-05-01 00:00:00'),
56(27,'PerOculos',0,'2020-07-13 00:00:00'), 56(27,1,'PerOculos',0,'2020-07-13 00:00:00'),
57(28,'LeviHB',7,'2011-05-01 00:00:00'), 57(28,1,'LeviHB',7,'2011-05-01 00:00:00'),
58(28,'Andy M.J.',2,'2011-10-07 00:00:00'), 58(28,1,'Andy M.J.',2,'2011-10-07 00:00:00'),
59(28,'Krzyhau',0,'2018-05-19 00:00:00'), 59(28,1,'Krzyhau',0,'2018-05-19 00:00:00'),
60(29,'LeviHB',0,'2011-05-01 00:00:00'), 60(29,1,'LeviHB',0,'2011-05-01 00:00:00'),
61(30,'Schlepian',2,'2011-10-30 00:00:00'), 61(30,1,'Schlepian',2,'2011-10-30 00:00:00'),
62(31,'Tyronis',0,'2011-10-06 00:00:00'), 62(31,1,'Tyronis',0,'2011-10-06 00:00:00'),
63-- 5 63-- 5
64(32,'Tyronis',6,'2011-10-21 00:00:00'), 64(32,1,'Tyronis',6,'2011-10-21 00:00:00'),
65(32,'Nidboj132',5,'2022-04-24 00:00:00'), 65(32,1,'Nidboj132',5,'2022-04-24 00:00:00'),
66(33,'Tyronis',7,'2011-10-06 00:00:00'), 66(33,1,'Tyronis',7,'2011-10-06 00:00:00'),
67(33,'ISimmo',5,'2011-11-02 00:00:00'), 67(33,1,'ISimmo',5,'2011-11-02 00:00:00'),
68(33,'PerOculos',4,'2017-05-30 00:00:00'), 68(33,1,'PerOculos',4,'2017-05-30 00:00:00'),
69(34,'Schlepian',3,'2011-11-01 00:00:00'), 69(34,1,'Schlepian',3,'2011-11-01 00:00:00'),
70(34,'Krzyhau',2,'2020-10-14 00:00:00'), 70(34,1,'Krzyhau',2,'2020-10-14 00:00:00'),
71(34,'zach',0,'2022-11-02 00:00:00'), 71(34,1,'zach',0,'2022-11-02 00:00:00'),
72(35,'Krank',2,'2012-07-28 00:00:00'), 72(35,1,'Krank',2,'2012-07-28 00:00:00'),
73-- 6 73-- 6
74(36,'Tyronis',6,'2011-10-06 00:00:00'), 74(36,1,'Tyronis',6,'2011-10-06 00:00:00'),
75(36,'CalmlyFrenetic',5,'2011-10-09 00:00:00'), 75(36,1,'CalmlyFrenetic',5,'2011-10-09 00:00:00'),
76(36,'sicklebrick',4,'2012-09-13 00:00:00'), 76(36,1,'sicklebrick',4,'2012-09-13 00:00:00'),
77(36,'Nidboj132',2,'2023-03-04 00:00:00'), 77(36,1,'Nidboj132',2,'2023-03-04 00:00:00'),
78(37,'LookLikeAKango',7,'2011-10-06 00:00:00'), 78(37,1,'LookLikeAKango',7,'2011-10-06 00:00:00'),
79(37,'Schlepian',6,'2011-11-01 00:00:00'), 79(37,1,'Schlepian',6,'2011-11-01 00:00:00'),
80(37,'Tyronis',5,'2012-01-28 00:00:00'), 80(37,1,'Tyronis',5,'2012-01-28 00:00:00'),
81(37,'Nidboj132',4,'2021-08-22 00:00:00'), 81(37,1,'Nidboj132',4,'2021-08-22 00:00:00'),
82(38,'Andy M.J.',2,'2011-10-06 00:00:00'), 82(38,1,'Andy M.J.',2,'2011-10-06 00:00:00'),
83(38,'Sanguine Dagger',0,'2012-03-19 00:00:00'), 83(38,1,'Sanguine Dagger',0,'2012-03-19 00:00:00'),
84(39,'Lambda Core',6,'2011-05-13 00:00:00'), 84(39,1,'Lambda Core',6,'2011-05-13 00:00:00'),
85(39,'The Last Tofus',5,'2011-05-13 00:00:00'), 85(39,1,'The Last Tofus',5,'2011-05-13 00:00:00'),
86(39,'LookLikeAKango',4,'2011-10-16 00:00:00'), 86(39,1,'LookLikeAKango',4,'2011-10-16 00:00:00'),
87(39,'Kittaye',3,'2013-03-25 00:00:00'), 87(39,1,'Kittaye',3,'2013-03-25 00:00:00'),
88(40,'LookLikeAKango',7,'2011-10-07 00:00:00'), 88(40,1,'LookLikeAKango',7,'2011-10-07 00:00:00'),
89(40,'Schlepian',6,'2011-11-05 00:00:00'), 89(40,1,'Schlepian',6,'2011-11-05 00:00:00'),
90(40,'Kittaye',4,'2013-04-01 00:00:00'), 90(40,1,'Kittaye',4,'2013-04-01 00:00:00'),
91(40,'Kittaye',3,'2014-09-13 00:00:00'), 91(40,1,'Kittaye',3,'2014-09-13 00:00:00'),
92(40,'szeimartin',2,'2014-09-13 00:00:00'), 92(40,1,'szeimartin',2,'2014-09-13 00:00:00'),
93(40,'Kittaye',0,'2014-09-15 00:00:00'), 93(40,1,'Kittaye',0,'2014-09-15 00:00:00'),
94(41,'CalmlyFrenetic',7,'2011-10-09 00:00:00'), 94(41,1,'CalmlyFrenetic',7,'2011-10-09 00:00:00'),
95(41,'Jaso',6,'2011-10-11 00:00:00'), 95(41,1,'Jaso',6,'2011-10-11 00:00:00'),
96(41,'Krank',5,'2012-07-17 00:00:00'), 96(41,1,'Krank',5,'2012-07-17 00:00:00'),
97-- 7 97-- 7
98(42,'LookLikeAKango',4,'2011-05-17 00:00:00'), 98(42,1,'LookLikeAKango',4,'2011-05-17 00:00:00'),
99(42,'ISimmo',2,'2011-11-07 00:00:00'), 99(42,1,'ISimmo',2,'2011-11-07 00:00:00'),
100(43,'lmao4ever',5,'2011-10-30 00:00:00'), 100(43,1,'lmao4ever',5,'2011-10-30 00:00:00'),
101(43,'Jaso',2,'2011-11-09 00:00:00'), 101(43,1,'Jaso',2,'2011-11-09 00:00:00'),
102(43,'feliser',0,'2022-06-26 00:00:00'), 102(43,1,'feliser',0,'2022-06-26 00:00:00'),
103(44,'LookLikeAKango',18,'2011-10-07 00:00:00'), 103(44,1,'LookLikeAKango',18,'2011-10-07 00:00:00'),
104(44,'Tyronis',13,'2011-10-30 00:00:00'), 104(44,1,'Tyronis',13,'2011-10-30 00:00:00'),
105(44,'Tyronis',12,'2011-11-10 00:00:00'), 105(44,1,'Tyronis',12,'2011-11-10 00:00:00'),
106(44,'Jetwash',11,'2017-06-12 00:00:00'), 106(44,1,'Jetwash',11,'2017-06-12 00:00:00'),
107(44,'Krzyhau',9,'2022-01-02 00:00:00'), 107(44,1,'Krzyhau',9,'2022-01-02 00:00:00'),
108(45,'LookLikeAKango',23,'2011-10-08 00:00:00'), 108(45,1,'LookLikeAKango',23,'2011-10-08 00:00:00'),
109(45,'CalmlyFrenetic',22,'2011-10-09 00:00:00'), 109(45,1,'CalmlyFrenetic',22,'2011-10-09 00:00:00'),
110(45,'cgreactor',17,'2011-10-09 00:00:00'), 110(45,1,'cgreactor',17,'2011-10-09 00:00:00'),
111(45,'CalmlyFrenetic',16,'2011-10-10 00:00:00'), 111(45,1,'CalmlyFrenetic',16,'2011-10-10 00:00:00'),
112(45,'LookLikeAKango',15,'2011-10-19 00:00:00'), 112(45,1,'LookLikeAKango',15,'2011-10-19 00:00:00'),
113(45,'Jaso',12,'2012-07-19 00:00:00'), 113(45,1,'Jaso',12,'2012-07-19 00:00:00'),
114(45,'Krank',10,'2013-01-31 00:00:00'), 114(45,1,'Krank',10,'2013-01-31 00:00:00'),
115(45,'Kittaye',7,'2013-04-04 00:00:00'), 115(45,1,'Kittaye',7,'2013-04-04 00:00:00'),
116(45,'PerOculos',4,'2014-09-13 00:00:00'), 116(45,1,'PerOculos',4,'2014-09-13 00:00:00'),
117-- 8 117-- 8
118(46,'sparkle1princess',6,'2012-03-24 00:00:00'), 118(46,1,'sparkle1princess',6,'2012-03-24 00:00:00'),
119(46,'Krzyhau',2,'2019-11-21 00:00:00'), 119(46,1,'Krzyhau',2,'2019-11-21 00:00:00'),
120(47,'holydevel',2,'2011-10-06 00:00:00'), 120(47,1,'holydevel',2,'2011-10-06 00:00:00'),
121(47,'JesusCatFace',0,'2015-01-16 00:00:00'), 121(47,1,'JesusCatFace',0,'2015-01-16 00:00:00'),
122(48,'LookLikeAKango',5,'2011-10-08 00:00:00'), 122(48,1,'LookLikeAKango',5,'2011-10-08 00:00:00'),
123(48,'Tyronis',2,'2011-10-08 00:00:00'), 123(48,1,'Tyronis',2,'2011-10-08 00:00:00'),
124(48,'adzicents',0,'2011-10-09 00:00:00'), 124(48,1,'adzicents',0,'2011-10-09 00:00:00'),
125(49,'adzicents',4,'2011-10-07 00:00:00'), 125(49,1,'adzicents',4,'2011-10-07 00:00:00'),
126(49,'Schlepian',2,'2011-10-08 00:00:00'), 126(49,1,'Schlepian',2,'2011-10-08 00:00:00'),
127(49,'Nidboj132',0,'2022-09-26 00:00:00'), 127(49,1,'Nidboj132',0,'2022-09-26 00:00:00'),
128(50,'LookLikeAKango',4,'2011-10-08 00:00:00'), 128(50,1,'LookLikeAKango',4,'2011-10-08 00:00:00'),
129(50,'Tyronis',2,'2011-10-11 00:00:00'), 129(50,1,'Tyronis',2,'2011-10-11 00:00:00'),
130(50,'sicklebrick',0,'2013-03-20 00:00:00'), 130(50,1,'sicklebrick',0,'2013-03-20 00:00:00'),
131(51,'Andy M.J.',3,'2011-10-08 00:00:00'), 131(51,1,'Andy M.J.',3,'2011-10-08 00:00:00'),
132(51,'LookLikeAKango',2,'2011-10-20 00:00:00'), 132(51,1,'LookLikeAKango',2,'2011-10-20 00:00:00'),
133(52,'Jaso',0,'2011-10-10 00:00:00'), 133(52,1,'Jaso',0,'2011-10-10 00:00:00'),
134(53,'LookLikeAKango',9,'2011-10-08 00:00:00'), 134(53,1,'LookLikeAKango',9,'2011-10-08 00:00:00'),
135(53,'LookLikeAKango',2,'2011-10-20 00:00:00'), 135(53,1,'LookLikeAKango',2,'2011-10-20 00:00:00'),
136(53,'Schlepian',0,'2011-11-06 00:00:00'), 136(53,1,'Schlepian',0,'2011-11-06 00:00:00'),
137(54,'LookLikeAKango',7,'2011-06-01 00:00:00'), 137(54,1,'LookLikeAKango',7,'2011-06-01 00:00:00'),
138(54,'Jaso',6,'2011-10-09 00:00:00'), 138(54,1,'Jaso',6,'2011-10-09 00:00:00'),
139(54,'Schlepian',5,'2011-11-06 00:00:00'), 139(54,1,'Schlepian',5,'2011-11-06 00:00:00'),
140(54,'Spyrunite',4,'2012-08-30 00:00:00'), 140(54,1,'Spyrunite',4,'2012-08-30 00:00:00'),
141(54,'Krzyhau',3,'2019-04-22 00:00:00'), 141(54,1,'Krzyhau',3,'2019-04-22 00:00:00'),
142(55,'LookLikeAKango',7,'2011-10-08 00:00:00'), 142(55,1,'LookLikeAKango',7,'2011-10-08 00:00:00'),
143(55,'CalmlyFrenetic',3,'2011-10-09 00:00:00'), 143(55,1,'CalmlyFrenetic',3,'2011-10-09 00:00:00'),
144(55,'Jaso',2,'2011-11-26 00:00:00'), 144(55,1,'Jaso',2,'2011-11-26 00:00:00'),
145(55,'PerOculos',0,'2021-02-06 00:00:00'), 145(55,1,'PerOculos',0,'2021-02-06 00:00:00'),
146(56,'CalmlyFrenetic',9,'2011-10-08 00:00:00'), 146(56,1,'CalmlyFrenetic',9,'2011-10-08 00:00:00'),
147(56,'LookLikeAKango',5,'2011-10-09 00:00:00'), 147(56,1,'LookLikeAKango',5,'2011-10-09 00:00:00'),
148(56,'CalmlyFrenetic',4,'2011-10-09 00:00:00'), 148(56,1,'CalmlyFrenetic',4,'2011-10-09 00:00:00'),
149(56,'Jetwash',2,'2014-09-05 00:00:00'), 149(56,1,'Jetwash',2,'2014-09-05 00:00:00'),
150-- 9 150-- 9
151(57,'JNS',7,'2011-07-21 00:00:00'), 151(57,1,'JNS',7,'2011-07-21 00:00:00'),
152(57,'Krank',5,'2012-07-29 00:00:00'), 152(57,1,'Krank',5,'2012-07-29 00:00:00'),
153(57,'Krzyhau',0,'2017-10-29 00:00:00'), 153(57,1,'Krzyhau',0,'2017-10-29 00:00:00'),
154(58,'Stimich',2,'2011-10-11 00:00:00'), 154(58,1,'Stimich',2,'2011-10-11 00:00:00'),
155(59,'Isimmo',7,'2011-11-04 00:00:00'), 155(59,1,'Isimmo',7,'2011-11-04 00:00:00'),
156(59,'sicklebrick',6,'2013-03-20 00:00:00'), 156(59,1,'sicklebrick',6,'2013-03-20 00:00:00'),
157(60,'CalmlyFrenetic',7,'2011-10-19 00:00:00'), 157(60,1,'CalmlyFrenetic',7,'2011-10-19 00:00:00'),
158(60,'Tyronis',6,'2011-11-01 00:00:00'), 158(60,1,'Tyronis',6,'2011-11-01 00:00:00'),
159-- Portal 2 Cooperative 159-- Portal 2 Cooperative
160-- 1 160-- 1
161(63,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'), 161(63,1,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'),
162(64,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'), 162(64,1,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'),
163(64,'Chubfish & Exhale',2,'2011-11-01 00:00:00'), 163(64,1,'Chubfish & Exhale',2,'2011-11-01 00:00:00'),
164(65,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'), 164(65,1,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'),
165(65,'Nidboj132 & Oryn',3,'2022-02-03 00:00:00'), 165(65,1,'Nidboj132 & Oryn',3,'2022-02-03 00:00:00'),
166(66,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'), 166(66,1,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'),
167(66,'Schlepian & Chubfish',2,'2011-10-01 00:00:00'), 167(66,1,'Schlepian & Chubfish',2,'2011-10-01 00:00:00'),
168(67,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'), 168(67,1,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'),
169(68,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'), 169(68,1,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'),
170-- 2 170-- 2
171(69,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'), 171(69,1,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'),
172(70,'Mathias123961 & Sir Spawn Alot',6,'2011-08-01 00:00:00'), 172(70,1,'Mathias123961 & Sir Spawn Alot',6,'2011-08-01 00:00:00'),
173(70,'Schlepian & Chubfish',4,'2011-10-01 00:00:00'), 173(70,1,'Schlepian & Chubfish',4,'2011-10-01 00:00:00'),
174(70,'Gocnak & z1mb0bw4y',2,'2012-08-03 00:00:00'), 174(70,1,'Gocnak & z1mb0bw4y',2,'2012-08-03 00:00:00'),
175(70,'DM_ & VEGA',0,'2017-10-01 00:00:00'), 175(70,1,'DM_ & VEGA',0,'2017-10-01 00:00:00'),
176(71,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'), 176(71,1,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'),
177(71,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'), 177(71,1,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'),
178(72,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'), 178(72,1,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'),
179(72,'Schlepian & LongJohnDickWeed',2,'2011-10-01 00:00:00'), 179(72,1,'Schlepian & LongJohnDickWeed',2,'2011-10-01 00:00:00'),
180(73,'Stimich & HiTMaRkS',9,'2011-05-09 00:00:00'), 180(73,1,'Stimich & HiTMaRkS',9,'2011-05-09 00:00:00'),
181(73,'Mathias123961 & Sir Spawn Alot',8,'2011-08-01 00:00:00'), 181(73,1,'Mathias123961 & Sir Spawn Alot',8,'2011-08-01 00:00:00'),
182(73,'Schlepian & Lemonsunshine',7,'2011-11-01 00:00:00'), 182(73,1,'Schlepian & Lemonsunshine',7,'2011-11-01 00:00:00'),
183(73,'DM_ & LsDK_',6,'2018-01-01 00:00:00'), 183(73,1,'DM_ & LsDK_',6,'2018-01-01 00:00:00'),
184(73,'Krzyhau & Klooger',4,'2018-11-01 00:00:00'), 184(73,1,'Krzyhau & Klooger',4,'2018-11-01 00:00:00'),
185(74,'Mathias123961 & Sir Spawn Alot',5,'2011-08-01 00:00:00'), 185(74,1,'Mathias123961 & Sir Spawn Alot',5,'2011-08-01 00:00:00'),
186(74,'Stimich & Pitkakorva',7,'2011-10-11 00:00:00'), 186(74,1,'Stimich & Pitkakorva',7,'2011-10-11 00:00:00'),
187(74,'Schlepian & Isimmo',3,'2011-10-28 00:00:00'), 187(74,1,'Schlepian & Isimmo',3,'2011-10-28 00:00:00'),
188(74,'Zypeh & szeimartin',2,'2013-11-01 00:00:00'), 188(74,1,'Zypeh & szeimartin',2,'2013-11-01 00:00:00'),
189(75,'Mathias123961 & Sir Spawn Alot',5,'2011-08-01 00:00:00'), 189(75,1,'Mathias123961 & Sir Spawn Alot',5,'2011-08-01 00:00:00'),
190(75,'Schlepian & Urination',4,'2011-10-01 00:00:00'), 190(75,1,'Schlepian & Urination',4,'2011-10-01 00:00:00'),
191(75,'Schlepian & Lemonsunshine',2,'2012-02-01 00:00:00'), 191(75,1,'Schlepian & Lemonsunshine',2,'2012-02-01 00:00:00'),
192(75,'DM_ & follon',0,'2015-04-01 00:00:00'), 192(75,1,'DM_ & follon',0,'2015-04-01 00:00:00'),
193(76,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'), 193(76,1,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'),
194(76,'Chubfish & Exhale',0,'2011-12-01 00:00:00'), 194(76,1,'Chubfish & Exhale',0,'2011-12-01 00:00:00'),
195-- 3 195-- 3
196(77,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'), 196(77,1,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'),
197(78,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'), 197(78,1,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'),
198(78,'DM_ & marK',3,'2016-11-01 00:00:00'), 198(78,1,'DM_ & marK',3,'2016-11-01 00:00:00'),
199(78,'Nidboj132 & Oryn',2,'2021-09-04 00:00:00'), 199(78,1,'Nidboj132 & Oryn',2,'2021-09-04 00:00:00'),
200(79,'ganonscrub & ?',5,'2011-04-01 00:00:00'), 200(79,1,'ganonscrub & ?',5,'2011-04-01 00:00:00'),
201(79,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'), 201(79,1,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'),
202(79,'Chubfish & Exhale',2,'2012-08-04 00:00:00'), 202(79,1,'Chubfish & Exhale',2,'2012-08-04 00:00:00'),
203(80,'Mathias123961 & Sir Spawn Alot',5,'2011-08-01 00:00:00'), 203(80,1,'Mathias123961 & Sir Spawn Alot',5,'2011-08-01 00:00:00'),
204(80,'Chubfish & Exhale',4,'2011-12-01 00:00:00'), 204(80,1,'Chubfish & Exhale',4,'2011-12-01 00:00:00'),
205(81,'Mathias123961 & Sir Spawn Alot',7,'2011-08-01 00:00:00'), 205(81,1,'Mathias123961 & Sir Spawn Alot',7,'2011-08-01 00:00:00'),
206(81,'Schlepian & Lemonsunshine',6,'2011-10-01 00:00:00'), 206(81,1,'Schlepian & Lemonsunshine',6,'2011-10-01 00:00:00'),
207(81,'takz & dawn',5,'2011-11-01 00:00:00'), 207(81,1,'takz & dawn',5,'2011-11-01 00:00:00'),
208(81,'Nidboj132 & Oryn',4,'2021-03-25 00:00:00'), 208(81,1,'Nidboj132 & Oryn',4,'2021-03-25 00:00:00'),
209(82,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'), 209(82,1,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'),
210(83,'Mathias123961 & Sir Spawn Alot',5,'2011-08-01 00:00:00'), 210(83,1,'Mathias123961 & Sir Spawn Alot',5,'2011-08-01 00:00:00'),
211(83,'Schlepian & Lemonsunshine',2,'2011-10-01 00:00:00'), 211(83,1,'Schlepian & Lemonsunshine',2,'2011-10-01 00:00:00'),
212(83,'Chubfish & Exhale',0,'2011-12-01 00:00:00'), 212(83,1,'Chubfish & Exhale',0,'2011-12-01 00:00:00'),
213(84,'Mathias123961 & Sir Spawn Alot',6,'2011-08-01 00:00:00'), 213(84,1,'Mathias123961 & Sir Spawn Alot',6,'2011-08-01 00:00:00'),
214(84,'Schlepian & Chubfish',4,'2011-10-01 00:00:00'), 214(84,1,'Schlepian & Chubfish',4,'2011-10-01 00:00:00'),
215(84,'Chubfish & Exhale',2,'2012-01-01 00:00:00'), 215(84,1,'Chubfish & Exhale',2,'2012-01-01 00:00:00'),
216(84,'DM_ & wS',0,'2015-05-01 00:00:00'), 216(84,1,'DM_ & wS',0,'2015-05-01 00:00:00'),
217-- 4 217-- 4
218(85,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'), 218(85,1,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'),
219(85,'Chubfish & Exhale',0,'2011-10-01 00:00:00'), 219(85,1,'Chubfish & Exhale',0,'2011-10-01 00:00:00'),
220(86,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'), 220(86,1,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'),
221(86,'Chubfish & Exhale',0,'2011-12-01 00:00:00'), 221(86,1,'Chubfish & Exhale',0,'2011-12-01 00:00:00'),
222(87,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'), 222(87,1,'Mathias123961 & Sir Spawn Alot',3,'2011-08-01 00:00:00'),
223(87,'Schlepian & Gopherdude',2,'2011-10-01 00:00:00'), 223(87,1,'Schlepian & Gopherdude',2,'2011-10-01 00:00:00'),
224(87,'DM_ & follon',0,'2015-04-01 00:00:00'), 224(87,1,'DM_ & follon',0,'2015-04-01 00:00:00'),
225(88,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'), 225(88,1,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'),
226(88,'Schlepian & Gopherdude',0,'2011-10-01 00:00:00'), 226(88,1,'Schlepian & Gopherdude',0,'2011-10-01 00:00:00'),
227(89,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'), 227(89,1,'Mathias123961 & Sir Spawn Alot',0,'2011-08-01 00:00:00'),
228(90,'Mathias123961 & Sir Spawn Alot',4,'2011-09-01 00:00:00'), 228(90,1,'Mathias123961 & Sir Spawn Alot',4,'2011-09-01 00:00:00'),
229(90,'Schlepian & Urination',2,'2011-10-01 00:00:00'), 229(90,1,'Schlepian & Urination',2,'2011-10-01 00:00:00'),
230(90,'Klooger & Jetwash',0,'2016-08-01 00:00:00'), 230(90,1,'Klooger & Jetwash',0,'2016-08-01 00:00:00'),
231(91,'Mathias123961 & Sir Spawn Alot',2,'2011-08-01 00:00:00'), 231(91,1,'Mathias123961 & Sir Spawn Alot',2,'2011-08-01 00:00:00'),
232(91,'Undead & Zypeh',0,'2013-05-19 00:00:00'), 232(91,1,'Undead & Zypeh',0,'2013-05-19 00:00:00'),
233(92,'txx478 & ?',5,'2011-05-01 00:00:00'), 233(92,1,'txx478 & ?',5,'2011-05-01 00:00:00'),
234(92,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'), 234(92,1,'Mathias123961 & Sir Spawn Alot',4,'2011-08-01 00:00:00'),
235(92,'Schlepian & Gopherdude',2,'2011-10-01 00:00:00'), 235(92,1,'Schlepian & Gopherdude',2,'2011-10-01 00:00:00'),
236(92,'ncla & takz',0,'2012-02-01 00:00:00'), 236(92,1,'ncla & takz',0,'2012-02-01 00:00:00'),
237(93,'Mathias123961 & Sir Spawn Alot',2,'2011-08-01 00:00:00'), 237(93,1,'Mathias123961 & Sir Spawn Alot',2,'2011-08-01 00:00:00'),
238(93,'Schlepian & Gopherdude',0,'2011-10-01 00:00:00'), 238(93,1,'Schlepian & Gopherdude',0,'2011-10-01 00:00:00'),
239-- 5 239-- 5
240(94,'Chubfish & Exhale',2,'2011-10-01 00:00:00'), 240(94,1,'Chubfish & Exhale',2,'2011-10-01 00:00:00'),
241(94,'Klooger & Imanex',0,'2013-08-01 00:00:00'), 241(94,1,'Klooger & Imanex',0,'2013-08-01 00:00:00'),
242(95,'Schlepian & Issimoi',2,'2011-10-01 00:00:00'), 242(95,1,'Schlepian & Issimoi',2,'2011-10-01 00:00:00'),
243(96,'ThePortalPatrol & ?',4,'2011-04-01 00:00:00'), 243(96,1,'ThePortalPatrol & ?',4,'2011-04-01 00:00:00'),
244(96,'sparkle1princess & Zypeh',2,'2014-01-01 00:00:00'), 244(96,1,'sparkle1princess & Zypeh',2,'2014-01-01 00:00:00'),
245(97,'Stimich & HiTMaRkS',7,'2011-05-13 00:00:00'), 245(97,1,'Stimich & HiTMaRkS',7,'2011-05-13 00:00:00'),
246(97,'Schlepian & Lemonsunshine',4,'2011-10-01 00:00:00'), 246(97,1,'Schlepian & Lemonsunshine',4,'2011-10-01 00:00:00'),
247(97,'DM_ & wS',2,'2014-05-01 00:00:00'), 247(97,1,'DM_ & wS',2,'2014-05-01 00:00:00'),
248(98,'Imanex & 00svo',0,'2011-11-01 00:00:00'), 248(98,1,'Imanex & 00svo',0,'2011-11-01 00:00:00'),
249(99,'Schlepian & Gopherdude',3,'2011-10-01 00:00:00'), 249(99,1,'Schlepian & Gopherdude',3,'2011-10-01 00:00:00'),
250(99,'Imanex & Klooger',2,'2013-08-01 00:00:00'), 250(99,1,'Imanex & Klooger',2,'2013-08-01 00:00:00'),
251(99,'DM_ & wS',0,'2015-05-01 00:00:00'), 251(99,1,'DM_ & wS',0,'2015-05-01 00:00:00'),
252(100,'Schlepian & Bananasaurus Rex',0,'2011-10-01 00:00:00'), 252(100,1,'Schlepian & Bananasaurus Rex',0,'2011-10-01 00:00:00'),
253(101,'Chubfish & Exhale',2,'2011-12-01 00:00:00'), 253(101,1,'Chubfish & Exhale',2,'2011-12-01 00:00:00'),
254(101,'DM_ & follon',0,'2015-04-01 00:00:00'), 254(101,1,'DM_ & follon',0,'2015-04-01 00:00:00'),
255-- 6 255-- 6
256(102,'dawn & takz',3,'2011-11-18 00:00:00'), 256(102,1,'dawn & takz',3,'2011-11-18 00:00:00'),
257(102,'Chubfish & Exhale',2,'2012-01-01 00:00:00'), 257(102,1,'Chubfish & Exhale',2,'2012-01-01 00:00:00'),
258(102,'Imanex & Klooger',0,'2013-08-01 00:00:00'), 258(102,1,'Imanex & Klooger',0,'2013-08-01 00:00:00'),
259(103,'Schlepian & Lemonsunshine',0,'2011-10-01 00:00:00'), 259(103,1,'Schlepian & Lemonsunshine',0,'2011-10-01 00:00:00'),
260(104,'Schlepian & Lemonsunshine',0,'2011-10-01 00:00:00'), 260(104,1,'Schlepian & Lemonsunshine',0,'2011-10-01 00:00:00'),
261(105,'Blaizerazer & ?',8,'2011-10-01 00:00:00'), 261(105,1,'Blaizerazer & ?',8,'2011-10-01 00:00:00'),
262(105,'Schlepian & Lemonsunshine',5,'2011-11-01 00:00:00'), 262(105,1,'Schlepian & Lemonsunshine',5,'2011-11-01 00:00:00'),
263(105,'Imanex & Klooger',4,'2013-08-01 00:00:00'), 263(105,1,'Imanex & Klooger',4,'2013-08-01 00:00:00'),
264(105,'DM_ & wS',3,'2014-05-01 00:00:00'), 264(105,1,'DM_ & wS',3,'2014-05-01 00:00:00'),
265(105,'DM_ & follon',2,'2015-04-01 00:00:00'), 265(105,1,'DM_ & follon',2,'2015-04-01 00:00:00'),
266(106,'Schlepian & Bananasaurus Rex',4,'2011-10-01 00:00:00'), 266(106,1,'Schlepian & Bananasaurus Rex',4,'2011-10-01 00:00:00'),
267(106,'Gig & takz',3,'2012-06-01 00:00:00'), 267(106,1,'Gig & takz',3,'2012-06-01 00:00:00'),
268(106,'Imanex & Klooger',0,'2013-06-01 00:00:00'), 268(106,1,'Imanex & Klooger',0,'2013-06-01 00:00:00'),
269(107,'Chubfish & Exhale',2,'2011-10-01 00:00:00'), 269(107,1,'Chubfish & Exhale',2,'2011-10-01 00:00:00'),
270(107,'DM_ & follon',0,'2015-04-01 00:00:00'), 270(107,1,'DM_ & follon',0,'2015-04-01 00:00:00'),
271(108,'DaFox & P',0,'2011-12-01 00:00:00'), 271(108,1,'DaFox & P',0,'2011-12-01 00:00:00'),
272(109,'Schlepian & Tyronis',5,'2011-10-01 00:00:00'), 272(109,1,'Schlepian & Tyronis',5,'2011-10-01 00:00:00'),
273(109,'Chubfish & Exhale',0,'2011-12-01 00:00:00'), 273(109,1,'Chubfish & Exhale',0,'2011-12-01 00:00:00'),
274(110,'Tyronis & mr.bob806',15,'2011-10-01 00:00:00'), 274(110,1,'Tyronis & mr.bob806',15,'2011-10-01 00:00:00'),
275(110,'Schlepian & Chubfish',6,'2011-11-01 00:00:00'), 275(110,1,'Schlepian & Chubfish',6,'2011-11-01 00:00:00'),
276(110,'00svo & z1mb0bw4y',5,'2012-08-08 00:00:00'), 276(110,1,'00svo & z1mb0bw4y',5,'2012-08-08 00:00:00'),
277(110,'00svo & z1mb0bw4y',4,'2012-08-10 00:00:00'), 277(110,1,'00svo & z1mb0bw4y',4,'2012-08-10 00:00:00'),
278(110,'Klooger & z1mb0bw4y',2,'2014-02-01 00:00:00'), 278(110,1,'Klooger & z1mb0bw4y',2,'2014-02-01 00:00:00'),
279(110,'DM_ & follon',0,'2015-04-01 00:00:00'); \ No newline at end of file 279(110,1,'DM_ & follon',0,'2015-04-01 00:00:00'); \ No newline at end of file
diff --git a/backend/database/init.sql b/backend/database/init.sql
index 51a4881..50e7c15 100644
--- a/backend/database/init.sql
+++ b/backend/database/init.sql
@@ -9,13 +9,14 @@ CREATE TABLE users (
9); 9);
10 10
11CREATE TABLE games ( 11CREATE TABLE games (
12 id SMALLSERIAL, 12 id SERIAL,
13 name TEXT NOT NULL, 13 name TEXT NOT NULL,
14 is_coop BOOLEAN NOT NULL,
14 PRIMARY KEY (id) 15 PRIMARY KEY (id)
15); 16);
16 17
17CREATE TABLE chapters ( 18CREATE TABLE chapters (
18 id SMALLSERIAL, 19 id SERIAL,
19 game_id SMALLINT NOT NULL, 20 game_id SMALLINT NOT NULL,
20 name TEXT NOT NULL, 21 name TEXT NOT NULL,
21 PRIMARY KEY (id), 22 PRIMARY KEY (id),
@@ -23,52 +24,57 @@ CREATE TABLE chapters (
23); 24);
24 25
25CREATE TABLE categories ( 26CREATE TABLE categories (
26 id SMALLSERIAL, 27 id SERIAL,
27 name TEXT NOT NULL, 28 name TEXT NOT NULL,
28 PRIMARY KEY (id) 29 PRIMARY KEY (id)
29); 30);
30 31
31CREATE TABLE maps ( 32CREATE TABLE maps (
32 id SMALLSERIAL, 33 id SERIAL,
33 game_id SMALLINT NOT NULL, 34 game_id SMALLINT NOT NULL,
34 chapter_id SMALLINT NOT NULL, 35 chapter_id SMALLINT NOT NULL,
35 name TEXT NOT NULL, 36 name TEXT NOT NULL,
36 description TEXT NOT NULL,
37 showcase TEXT NOT NULL,
38 is_disabled BOOLEAN NOT NULL DEFAULT false, 37 is_disabled BOOLEAN NOT NULL DEFAULT false,
39 PRIMARY KEY (id), 38 PRIMARY KEY (id),
40 FOREIGN KEY (game_id) REFERENCES games(id), 39 FOREIGN KEY (game_id) REFERENCES games(id),
41 FOREIGN KEY (chapter_id) REFERENCES chapters(id) 40 FOREIGN KEY (chapter_id) REFERENCES chapters(id)
42); 41);
43 42
43CREATE TABLE map_routes (
44 id SERIAL,
45 map_id SMALLINT NOT NULL,
46 category_id SMALLINT NOT NULL,
47 score_count SMALLINT NOT NULL,
48 description TEXT NOT NULL,
49 showcase TEXT NOT NULL,
50 PRIMARY KEY (id),
51 FOREIGN KEY (map_id) REFERENCES maps(id),
52 FOREIGN KEY (category_id) REFERENCES categories(id),
53 UNIQUE (map_id, category_id, score_count)
54);
55
44CREATE TABLE map_history ( 56CREATE TABLE map_history (
45 id SMALLSERIAL, 57 id SERIAL,
46 map_id SMALLINT NOT NULL, 58 map_id SMALLINT NOT NULL,
59 category_id SMALLINT NOT NULL,
47 user_name TEXT NOT NULL, 60 user_name TEXT NOT NULL,
48 score_count SMALLINT NOT NULL, 61 score_count SMALLINT NOT NULL,
49 record_date TIMESTAMP NOT NULL, 62 record_date TIMESTAMP NOT NULL,
50 PRIMARY KEY (id), 63 PRIMARY KEY (id),
51 FOREIGN KEY (map_id) REFERENCES maps(id) 64 FOREIGN KEY (category_id) REFERENCES categories(id),
65 FOREIGN KEY (map_id) REFERENCES maps(id),
66 UNIQUE (map_id, category_id, score_count)
52); 67);
53 68
54CREATE TABLE map_ratings ( 69CREATE TABLE map_ratings (
55 id SERIAL, 70 id SERIAL,
56 map_id SMALLINT NOT NULL, 71 map_id SMALLINT NOT NULL,
72 category_id SMALLINT NOT NULL,
57 user_id TEXT NOT NULL, 73 user_id TEXT NOT NULL,
58 rating SMALLINT NOT NULL, 74 rating SMALLINT NOT NULL,
59 PRIMARY KEY (id), 75 PRIMARY KEY (id),
60 FOREIGN KEY (map_id) REFERENCES maps(id), 76 FOREIGN KEY (map_id) REFERENCES maps(id),
61 FOREIGN KEY (user_id) REFERENCES users(steam_id) 77 FOREIGN KEY (category_id) REFERENCES categories(id),
62);
63
64CREATE TABLE map_routers (
65 id SMALLSERIAL,
66 map_id SMALLINT NOT NULL,
67 user_id TEXT,
68 user_name TEXT NOT NULL,
69 score_count SMALLINT NOT NULL,
70 PRIMARY KEY (id),
71 FOREIGN KEY (map_id) REFERENCES maps(id),
72 FOREIGN KEY (user_id) REFERENCES users(steam_id) 78 FOREIGN KEY (user_id) REFERENCES users(steam_id)
73); 79);
74 80
diff --git a/backend/database/route.sql b/backend/database/route.sql
new file mode 100644
index 0000000..f26180e
--- /dev/null
+++ b/backend/database/route.sql
@@ -0,0 +1,279 @@
1INSERT INTO map_routes(map_id,category_id,score_count,description,showcase) VALUES
2-- Portal 2 Singleplayer
3-- 1
4(3,1,3,'',''),
5(3,1,1,'',''),
6(3,1,0,'',''),
7(4,1,1,'',''),
8(4,1,0,'',''),
9(5,1,2,'',''),
10(5,1,1,'',''),
11(6,1,4,'',''),
12(6,1,3,'',''),
13(6,1,2,'',''),
14(9,1,4,'',''),
15(9,1,3,'',''),
16(9,1,2,'',''),
17(9,1,0,'',''),
18-- 2
19(10,1,2,'',''),
20(10,1,0,'',''),
21(11,1,2,'',''),
22(11,1,0,'',''),
23(12,1,2,'',''),
24(13,1,3,'',''),
25(13,1,2,'',''),
26(13,1,0,'',''),
27(15,1,2,'',''),
28(16,1,2,'',''),
29(16,1,0,'',''),
30(17,1,2,'',''),
31(17,1,0,'',''),
32-- 3
33(18,1,5,'',''),
34(18,1,4,'',''),
35(18,1,3,'',''),
36(18,1,2,'',''),
37(18,1,0,'',''),
38(19,1,2,'',''),
39(20,1,5,'',''),
40(20,1,4,'',''),
41(20,1,3,'',''),
42(20,1,2,'',''),
43(21,1,4,'',''),
44(21,1,2,'',''),
45(21,1,0,'',''),
46(22,1,0,'',''),
47(23,1,2,'',''),
48(23,1,0,'',''),
49(24,1,0,'',''),
50(25,1,0,'',''),
51(26,1,3,'',''),
52(26,1,2,'',''),
53(26,1,0,'',''),
54-- 4
55(27,1,2,'',''),
56(27,1,0,'',''),
57(28,1,7,'',''),
58(28,1,2,'',''),
59(28,1,0,'',''),
60(29,1,0,'',''),
61(30,1,2,'',''),
62(31,1,0,'',''),
63-- 5
64(32,1,6,'',''),
65(32,1,5,'',''),
66(33,1,7,'',''),
67(33,1,5,'',''),
68(33,1,4,'',''),
69(34,1,3,'',''),
70(34,1,2,'',''),
71(34,1,0,'',''),
72(35,1,2,'',''),
73-- 6
74(36,1,6,'',''),
75(36,1,5,'',''),
76(36,1,4,'',''),
77(36,1,2,'',''),
78(37,1,7,'',''),
79(37,1,6,'',''),
80(37,1,5,'',''),
81(37,1,4,'',''),
82(38,1,2,'',''),
83(38,1,0,'',''),
84(39,1,6,'',''),
85(39,1,5,'',''),
86(39,1,4,'',''),
87(39,1,3,'',''),
88(40,1,7,'',''),
89(40,1,6,'',''),
90(40,1,4,'',''),
91(40,1,3,'',''),
92(40,1,2,'',''),
93(40,1,0,'',''),
94(41,1,7,'',''),
95(41,1,6,'',''),
96(41,1,5,'',''),
97-- 7
98(42,1,4,'',''),
99(42,1,2,'',''),
100(43,1,5,'',''),
101(43,1,2,'',''),
102(43,1,0,'',''),
103(44,1,18,'',''),
104(44,1,13,'',''),
105(44,1,12,'',''),
106(44,1,11,'',''),
107(44,1,9,'',''),
108(45,1,23,'',''),
109(45,1,22,'',''),
110(45,1,17,'',''),
111(45,1,16,'',''),
112(45,1,15,'',''),
113(45,1,12,'',''),
114(45,1,10,'',''),
115(45,1,7,'',''),
116(45,1,4,'',''),
117-- 8
118(46,1,6,'',''),
119(46,1,2,'',''),
120(47,1,2,'',''),
121(47,1,0,'',''),
122(48,1,5,'',''),
123(48,1,2,'',''),
124(48,1,0,'',''),
125(49,1,4,'',''),
126(49,1,2,'',''),
127(49,1,0,'',''),
128(50,1,4,'',''),
129(50,1,2,'',''),
130(50,1,0,'',''),
131(51,1,3,'',''),
132(51,1,2,'',''),
133(52,1,0,'',''),
134(53,1,9,'',''),
135(53,1,2,'',''),
136(53,1,0,'',''),
137(54,1,7,'',''),
138(54,1,6,'',''),
139(54,1,5,'',''),
140(54,1,4,'',''),
141(54,1,3,'',''),
142(55,1,7,'',''),
143(55,1,3,'',''),
144(55,1,2,'',''),
145(55,1,0,'',''),
146(56,1,9,'',''),
147(56,1,5,'',''),
148(56,1,4,'',''),
149(56,1,2,'',''),
150-- 9
151(57,1,7,'',''),
152(57,1,5,'',''),
153(57,1,0,'',''),
154(58,1,2,'',''),
155(59,1,7,'',''),
156(59,1,6,'',''),
157(60,1,7,'',''),
158(60,1,6,'',''),
159-- Portal 2 Cooperative
160-- 1
161(63,1,0,'',''),
162(64,1,3,'',''),
163(64,1,2,'',''),
164(65,1,4,'',''),
165(65,1,3,'',''),
166(66,1,3,'',''),
167(66,1,2,'',''),
168(67,1,0,'',''),
169(68,1,0,'',''),
170-- 2
171(69,1,4,'',''),
172(70,1,6,'',''),
173(70,1,4,'',''),
174(70,1,2,'',''),
175(70,1,0,'',''),
176(71,1,3,'',''),
177(71,1,0,'',''),
178(72,1,4,'',''),
179(72,1,2,'',''),
180(73,1,9,'',''),
181(73,1,8,'',''),
182(73,1,7,'',''),
183(73,1,6,'',''),
184(73,1,4,'',''),
185(74,1,5,'',''),
186(74,1,7,'',''),
187(74,1,3,'',''),
188(74,1,2,'',''),
189(75,1,5,'',''),
190(75,1,4,'',''),
191(75,1,2,'',''),
192(75,1,0,'',''),
193(76,1,3,'',''),
194(76,1,0,'',''),
195-- 3
196(77,1,3,'',''),
197(78,1,4,'',''),
198(78,1,3,'',''),
199(78,1,2,'',''),
200(79,1,5,'',''),
201(79,1,4,'',''),
202(79,1,2,'',''),
203(80,1,5,'',''),
204(80,1,4,'',''),
205(81,1,7,'',''),
206(81,1,6,'',''),
207(81,1,5,'',''),
208(81,1,4,'',''),
209(82,1,4,'',''),
210(83,1,5,'',''),
211(83,1,2,'',''),
212(83,1,0,'',''),
213(84,1,6,'',''),
214(84,1,4,'',''),
215(84,1,2,'',''),
216(84,1,0,'',''),
217-- 4
218(85,1,3,'',''),
219(85,1,0,'',''),
220(86,1,3,'',''),
221(86,1,0,'',''),
222(87,1,3,'',''),
223(87,1,2,'',''),
224(87,1,0,'',''),
225(88,1,4,'',''),
226(88,1,0,'',''),
227(89,1,0,'',''),
228(90,1,4,'',''),
229(90,1,2,'',''),
230(90,1,0,'',''),
231(91,1,2,'',''),
232(91,1,0,'',''),
233(92,1,5,'',''),
234(92,1,4,'',''),
235(92,1,2,'',''),
236(92,1,0,'',''),
237(93,1,2,'',''),
238(93,1,0,'',''),
239-- 5
240(94,1,2,'',''),
241(94,1,0,'',''),
242(95,1,2,'',''),
243(96,1,4,'',''),
244(96,1,2,'',''),
245(97,1,7,'',''),
246(97,1,4,'',''),
247(97,1,2,'',''),
248(98,1,0,'',''),
249(99,1,3,'',''),
250(99,1,2,'',''),
251(99,1,0,'',''),
252(100,1,0,'',''),
253(101,1,2,'',''),
254(101,1,0,'',''),
255-- 6
256(102,1,3,'',''),
257(102,1,2,'',''),
258(102,1,0,'',''),
259(103,1,0,'',''),
260(104,1,0,'',''),
261(105,1,8,'',''),
262(105,1,5,'',''),
263(105,1,4,'',''),
264(105,1,3,'',''),
265(105,1,2,'',''),
266(106,1,4,'',''),
267(106,1,3,'',''),
268(106,1,0,'',''),
269(107,1,2,'',''),
270(107,1,0,'',''),
271(108,1,0,'',''),
272(109,1,5,'',''),
273(109,1,0,'',''),
274(110,1,15,'',''),
275(110,1,6,'',''),
276(110,1,5,'',''),
277(110,1,4,'',''),
278(110,1,2,'',''),
279(110,1,0,'',''); \ No newline at end of file
diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go
index 14a0b78..0698f0a 100644
--- a/backend/middleware/auth.go
+++ b/backend/middleware/auth.go
@@ -36,13 +36,21 @@ func CheckAuth(c *gin.Context) {
36 } 36 }
37 // Get user from DB 37 // Get user from DB
38 var user models.User 38 var user models.User
39 database.DB.QueryRow(`SELECT * FROM users WHERE steam_id = $1`, claims["sub"]).Scan( 39 database.DB.QueryRow(`SELECT u.steam_id, u.user_name, u.avatar_link, u.country_code, u.created_at, u.updated_at FROM users u WHERE steam_id = $1`, claims["sub"]).Scan(
40 &user.SteamID, &user.UserName, &user.AvatarLink, 40 &user.SteamID, &user.UserName, &user.AvatarLink,
41 &user.CountryCode, &user.CreatedAt, &user.UpdatedAt) 41 &user.CountryCode, &user.CreatedAt, &user.UpdatedAt)
42 if user.SteamID == "" { 42 if user.SteamID == "" {
43 c.Next() 43 c.Next()
44 return 44 return
45 } 45 }
46 // Get user titles from DB
47 user.Titles = []string{}
48 rows, _ := database.DB.Query(`SELECT t.title_name FROM titles t WHERE t.user_id = $1`, user.SteamID)
49 for rows.Next() {
50 var title string
51 rows.Scan(&title)
52 user.Titles = append(user.Titles, title)
53 }
46 c.Set("user", user) 54 c.Set("user", user)
47 c.Next() 55 c.Next()
48 } else { 56 } else {
diff --git a/backend/models/models.go b/backend/models/models.go
index 6f5173a..1231cb1 100644
--- a/backend/models/models.go
+++ b/backend/models/models.go
@@ -4,16 +4,6 @@ import (
4 "time" 4 "time"
5) 5)
6 6
7type Response struct {
8 Success bool `json:"success"`
9 Message string `json:"message"`
10 Data any `json:"data"`
11}
12
13type LoginResponse struct {
14 Token string `json:"token"`
15}
16
17type User struct { 7type User struct {
18 SteamID string `json:"steam_id"` 8 SteamID string `json:"steam_id"`
19 UserName string `json:"user_name"` 9 UserName string `json:"user_name"`
@@ -21,6 +11,12 @@ type User struct {
21 CountryCode string `json:"country_code"` 11 CountryCode string `json:"country_code"`
22 CreatedAt time.Time `json:"created_at"` 12 CreatedAt time.Time `json:"created_at"`
23 UpdatedAt time.Time `json:"updated_at"` 13 UpdatedAt time.Time `json:"updated_at"`
14 Titles []string `json:"titles"`
15}
16
17type UserShort struct {
18 SteamID string `json:"steam_id"`
19 UserName string `json:"user_name"`
24} 20}
25 21
26type Map struct { 22type Map struct {
@@ -28,33 +24,58 @@ type Map struct {
28 GameName string `json:"game_name"` 24 GameName string `json:"game_name"`
29 ChapterName string `json:"chapter_name"` 25 ChapterName string `json:"chapter_name"`
30 MapName string `json:"map_name"` 26 MapName string `json:"map_name"`
31 Data any `json:"data"` 27 Image string `json:"image"`
28 IsCoop bool `json:"is_coop"`
29}
30
31type MapShort struct {
32 ID int `json:"id"`
33 Name string `json:"name"`
32} 34}
33 35
34type MapSummary struct { 36type MapSummary struct {
35 Description string `json:"description"` 37 Routes []MapRoute `json:"routes"`
36 Showcase string `json:"showcase"`
37 CategoryScores MapCategoryScores `json:"category_scores"`
38 Rating float32 `json:"rating"`
39 Routers []string `json:"routers"`
40 History []MapHistory `json:"history"`
41} 38}
42 39
43type MapCategoryScores struct { 40type MapHistory struct {
44 CM int `json:"cm"` 41 RunnerName string `json:"runner_name"`
45 NoSLA int `json:"no_sla"` 42 ScoreCount int `json:"score_count"`
46 InboundsSLA int `json:"inbounds_sla"` 43 Date time.Time `json:"date"`
47 Any int `json:"any"` 44}
45
46type MapRoute struct {
47 RouteID int `json:"route_id"`
48 Category Category `json:"category"`
49 History MapHistory `json:"history"`
50 Rating float32 `json:"rating"`
51 Description string `json:"description"`
52 Showcase string `json:"showcase"`
48} 53}
49 54
50type MapRecords struct { 55type MapRecords struct {
51 Records any `json:"records"` 56 Records any `json:"records"`
52} 57}
53 58
54type MapHistory struct { 59type UserRanking struct {
55 RunnerName string `json:"runner_name"` 60 UserID string `json:"user_id"`
56 ScoreCount int `json:"score_count"` 61 UserName string `json:"user_name"`
57 Date time.Time `json:"date"` 62 TotalScore int `json:"total_score"`
63}
64
65type Game struct {
66 ID int `json:"id"`
67 Name string `json:"name"`
68 IsCoop bool `json:"is_coop"`
69}
70
71type Chapter struct {
72 ID int `json:"id"`
73 Name string `json:"name"`
74}
75
76type Category struct {
77 ID int `json:"id"`
78 Name string `json:"name"`
58} 79}
59 80
60type RecordSP struct { 81type RecordSP struct {
@@ -85,50 +106,6 @@ type RecordMP struct {
85 RecordDate time.Time `json:"record_date"` 106 RecordDate time.Time `json:"record_date"`
86} 107}
87 108
88type RecordRequest struct {
89 ScoreCount int `json:"score_count" form:"score_count" binding:"required"`
90 ScoreTime int `json:"score_time" form:"score_time" binding:"required"`
91 PartnerID string `json:"partner_id" form:"partner_id" binding:"required"`
92 IsPartnerOrange bool `json:"is_partner_orange" form:"is_partner_orange" binding:"required"`
93}
94
95type UserRanking struct {
96 UserID string `json:"user_id"`
97 UserName string `json:"user_name"`
98 TotalScore int `json:"total_score"`
99}
100
101type RankingsResponse struct {
102 RankingsSP []UserRanking `json:"rankings_sp"`
103 RankingsMP []UserRanking `json:"rankings_mp"`
104}
105
106type ProfileResponse struct {
107 Profile bool `json:"profile"`
108 SteamID string `json:"steam_id"`
109 UserName string `json:"user_name"`
110 AvatarLink string `json:"avatar_link"`
111 CountryCode string `json:"country_code"`
112 ScoresSP []ScoreResponse `json:"scores_sp"`
113 ScoresMP []ScoreResponse `json:"scores_mp"`
114}
115
116type ScoreResponse struct {
117 MapID int `json:"map_id"`
118 Records any `json:"records"`
119}
120
121type SearchResponse struct {
122 Players []struct {
123 SteamID string `json:"steam_id"`
124 UserName string `json:"user_name"`
125 } `json:"players"`
126 Maps []struct {
127 ID int `json:"id"`
128 Name string `json:"name"`
129 } `json:"maps"`
130}
131
132type PlayerSummaries struct { 109type PlayerSummaries struct {
133 SteamId string `json:"steamid"` 110 SteamId string `json:"steamid"`
134 CommunityVisibilityState int `json:"communityvisibilitystate"` 111 CommunityVisibilityState int `json:"communityvisibilitystate"`
@@ -152,36 +129,3 @@ type PlayerSummaries struct {
152 GameExtraInfo string `json:"gameextrainfo"` 129 GameExtraInfo string `json:"gameextrainfo"`
153 GameServerIp string `json:"gameserverip"` 130 GameServerIp string `json:"gameserverip"`
154} 131}
155
156type Game struct {
157 ID int `json:"id"`
158 Name string `json:"name"`
159}
160
161type ChaptersResponse struct {
162 Game Game `json:"game"`
163 Chapters []Chapter `json:"chapters"`
164}
165
166type Chapter struct {
167 ID int `json:"id"`
168 Name string `json:"name"`
169}
170
171type MapShort struct {
172 ID int `json:"id"`
173 Name string `json:"name"`
174}
175
176type ChapterMapsResponse struct {
177 Chapter Chapter `json:"chapter"`
178 Maps []MapShort `json:"maps"`
179}
180
181func ErrorResponse(message string) Response {
182 return Response{
183 Success: false,
184 Message: message,
185 Data: nil,
186 }
187}
diff --git a/backend/models/requests.go b/backend/models/requests.go
new file mode 100644
index 0000000..0113597
--- /dev/null
+++ b/backend/models/requests.go
@@ -0,0 +1,39 @@
1package models
2
3import (
4 "mime/multipart"
5 "time"
6)
7
8type CreateMapSummaryRequest struct {
9 CategoryID int `json:"category_id" binding:"required"`
10 Description string `json:"description" binding:"required"`
11 Showcase string `json:"showcase"`
12 UserName string `json:"user_name" binding:"required"`
13 ScoreCount *int `json:"score_count" binding:"required"`
14 RecordDate time.Time `json:"record_date" binding:"required"`
15}
16
17type EditMapSummaryRequest struct {
18 RouteID int `json:"route_id" binding:"required"`
19 Description string `json:"description" binding:"required"`
20 Showcase string `json:"showcase"`
21 UserName string `json:"user_name" binding:"required"`
22 ScoreCount *int `json:"score_count" binding:"required"`
23 RecordDate time.Time `json:"record_date" binding:"required"`
24}
25
26type DeleteMapSummaryRequest struct {
27 RouteID int `json:"route_id" binding:"required"`
28}
29
30type EditMapImageRequest struct {
31 Image string `json:"image" binding:"required"`
32}
33
34type RecordRequest struct {
35 HostDemo *multipart.FileHeader `json:"host_demo" form:"host_demo" binding:"required" swaggerignore:"true"`
36 PartnerDemo *multipart.FileHeader `json:"partner_demo" form:"partner_demo" swaggerignore:"true"`
37 IsPartnerOrange bool `json:"is_partner_orange" form:"is_partner_orange"`
38 PartnerID string `json:"partner_id" form:"partner_id"`
39}
diff --git a/backend/models/responses.go b/backend/models/responses.go
new file mode 100644
index 0000000..459911c
--- /dev/null
+++ b/backend/models/responses.go
@@ -0,0 +1,64 @@
1package models
2
3type Response struct {
4 Success bool `json:"success"`
5 Message string `json:"message"`
6 Data any `json:"data"`
7}
8
9type LoginResponse struct {
10 Token string `json:"token"`
11}
12
13type RankingsResponse struct {
14 RankingsSP []UserRanking `json:"rankings_sp"`
15 RankingsMP []UserRanking `json:"rankings_mp"`
16}
17
18type ProfileResponse struct {
19 Profile bool `json:"profile"`
20 SteamID string `json:"steam_id"`
21 UserName string `json:"user_name"`
22 AvatarLink string `json:"avatar_link"`
23 CountryCode string `json:"country_code"`
24 ScoresSP []ScoreResponse `json:"scores_sp"`
25 ScoresMP []ScoreResponse `json:"scores_mp"`
26}
27
28type ScoreResponse struct {
29 MapID int `json:"map_id"`
30 Records any `json:"records"`
31}
32
33type MapSummaryResponse struct {
34 Map Map `json:"map"`
35 Summary MapSummary `json:"summary"`
36}
37
38type SearchResponse struct {
39 Players []UserShort `json:"players"`
40 Maps []MapShort `json:"maps"`
41}
42
43type ChaptersResponse struct {
44 Game Game `json:"game"`
45 Chapters []Chapter `json:"chapters"`
46}
47
48type ChapterMapsResponse struct {
49 Chapter Chapter `json:"chapter"`
50 Maps []MapShort `json:"maps"`
51}
52
53type RecordResponse struct {
54 ScoreCount int `json:"score_count"`
55 ScoreTime int `json:"score_time"`
56}
57
58func ErrorResponse(message string) Response {
59 return Response{
60 Success: false,
61 Message: message,
62 Data: nil,
63 }
64}
diff --git a/backend/parser/parser.REMOVED.git-id b/backend/parser/parser.REMOVED.git-id
new file mode 100644
index 0000000..b09611f
--- /dev/null
+++ b/backend/parser/parser.REMOVED.git-id
@@ -0,0 +1 @@
72bd63d0667a78d35f643f1f8fcf71d9269d2719 \ No newline at end of file
diff --git a/backend/parser/parser.go b/backend/parser/parser.go
new file mode 100644
index 0000000..562b8c0
--- /dev/null
+++ b/backend/parser/parser.go
@@ -0,0 +1,43 @@
1package parser
2
3import (
4 "bufio"
5 "fmt"
6 "log"
7 "os/exec"
8 "strconv"
9 "strings"
10)
11
12func ProcessDemo(demoPath string) (int, int, error) {
13 cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "FEXBash" && ./backend/parser/parser %s`, demoPath))
14 stdout, err := cmd.StdoutPipe()
15 if err != nil {
16 log.Println(err)
17 return 0, 0, err
18 }
19 cmd.Start()
20 scanner := bufio.NewScanner(stdout)
21 var cmTicks, portalCount int
22 for scanner.Scan() {
23 line := scanner.Text()
24 if strings.Contains(line, "CM ticks") {
25 cmTicksStr := strings.TrimSpace(strings.Split(line, ":")[1])
26 cmTicks, err = strconv.Atoi(cmTicksStr)
27 if err != nil {
28 return 0, 0, err
29 }
30 }
31 if strings.Contains(line, "Portal count") {
32 portalCountStr := strings.TrimSpace(strings.Split(line, ":")[1])
33 portalCount, err = strconv.Atoi(portalCountStr)
34 if err != nil {
35 return 0, 0, err
36 }
37 }
38 }
39 cmd.Wait()
40 // We don't check for error in wait, since FEXBash always gives segmentation fault
41 // Wanted output is retrieved, so it's okay (i think)
42 return portalCount, cmTicks, nil
43}
diff --git a/backend/routes/routes.go b/backend/routes/routes.go
index 96da1ce..1377d32 100644
--- a/backend/routes/routes.go
+++ b/backend/routes/routes.go
@@ -25,11 +25,15 @@ func InitRoutes(router *gin.Engine) {
25 v1.POST("/profile", middleware.CheckAuth, controllers.UpdateUser) 25 v1.POST("/profile", middleware.CheckAuth, controllers.UpdateUser)
26 v1.GET("/users/:id", middleware.CheckAuth, controllers.FetchUser) 26 v1.GET("/users/:id", middleware.CheckAuth, controllers.FetchUser)
27 v1.GET("/demos", controllers.DownloadDemoWithID) 27 v1.GET("/demos", controllers.DownloadDemoWithID)
28 v1.GET("/maps/:id/summary", middleware.CheckAuth, controllers.FetchMapSummary) 28 v1.GET("/maps/:id/summary", controllers.FetchMapSummary)
29 v1.GET("/maps/:id/leaderboards", middleware.CheckAuth, controllers.FetchMapLeaderboards) 29 v1.POST("/maps/:id/summary", middleware.CheckAuth, controllers.CreateMapSummary)
30 v1.PUT("/maps/:id/summary", middleware.CheckAuth, controllers.EditMapSummary)
31 v1.DELETE("/maps/:id/summary", middleware.CheckAuth, controllers.DeleteMapSummary)
32 v1.PUT("/maps/:id/image", middleware.CheckAuth, controllers.EditMapImage)
33 v1.GET("/maps/:id/leaderboards", controllers.FetchMapLeaderboards)
30 v1.POST("/maps/:id/record", middleware.CheckAuth, controllers.CreateRecordWithDemo) 34 v1.POST("/maps/:id/record", middleware.CheckAuth, controllers.CreateRecordWithDemo)
31 v1.GET("/rankings", middleware.CheckAuth, controllers.Rankings) 35 v1.GET("/rankings", controllers.Rankings)
32 v1.GET("/search", controllers.Search) 36 v1.GET("/search", controllers.SearchWithQuery)
33 v1.GET("/games", controllers.FetchGames) 37 v1.GET("/games", controllers.FetchGames)
34 v1.GET("/games/:id", controllers.FetchChapters) 38 v1.GET("/games/:id", controllers.FetchChapters)
35 v1.GET("/chapters/:id", controllers.FetchChapterMaps) 39 v1.GET("/chapters/:id", controllers.FetchChapterMaps)
diff --git a/docs/docs.go b/docs/docs.go
index d39fd1c..423afad 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -22,13 +22,13 @@ const docTemplate = `{
22 "paths": { 22 "paths": {
23 "/chapters/{id}": { 23 "/chapters/{id}": {
24 "get": { 24 "get": {
25 "description": "Get maps from the specified chapter id.",
25 "produces": [ 26 "produces": [
26 "application/json" 27 "application/json"
27 ], 28 ],
28 "tags": [ 29 "tags": [
29 "games \u0026 chapters" 30 "games \u0026 chapters"
30 ], 31 ],
31 "summary": "Get maps from the specified chapter id.",
32 "parameters": [ 32 "parameters": [
33 { 33 {
34 "type": "integer", 34 "type": "integer",
@@ -66,45 +66,9 @@ const docTemplate = `{
66 } 66 }
67 } 67 }
68 }, 68 },
69 "/demo": {
70 "get": {
71 "produces": [
72 "application/json"
73 ],
74 "tags": [
75 "rankings"
76 ],
77 "summary": "Get rankings of every player.",
78 "responses": {
79 "200": {
80 "description": "OK",
81 "schema": {
82 "allOf": [
83 {
84 "$ref": "#/definitions/models.Response"
85 },
86 {
87 "type": "object",
88 "properties": {
89 "data": {
90 "$ref": "#/definitions/models.RankingsResponse"
91 }
92 }
93 }
94 ]
95 }
96 },
97 "400": {
98 "description": "Bad Request",
99 "schema": {
100 "$ref": "#/definitions/models.Response"
101 }
102 }
103 }
104 }
105 },
106 "/demos": { 69 "/demos": {
107 "get": { 70 "get": {
71 "description": "Get demo with specified demo uuid.",
108 "consumes": [ 72 "consumes": [
109 "application/json" 73 "application/json"
110 ], 74 ],
@@ -114,10 +78,9 @@ const docTemplate = `{
114 "tags": [ 78 "tags": [
115 "demo" 79 "demo"
116 ], 80 ],
117 "summary": "Get demo with specified demo uuid.",
118 "parameters": [ 81 "parameters": [
119 { 82 {
120 "type": "integer", 83 "type": "string",
121 "description": "Demo UUID", 84 "description": "Demo UUID",
122 "name": "uuid", 85 "name": "uuid",
123 "in": "query", 86 "in": "query",
@@ -142,13 +105,13 @@ const docTemplate = `{
142 }, 105 },
143 "/games": { 106 "/games": {
144 "get": { 107 "get": {
108 "description": "Get games from the leaderboards.",
145 "produces": [ 109 "produces": [
146 "application/json" 110 "application/json"
147 ], 111 ],
148 "tags": [ 112 "tags": [
149 "games \u0026 chapters" 113 "games \u0026 chapters"
150 ], 114 ],
151 "summary": "Get games from the leaderboards.",
152 "responses": { 115 "responses": {
153 "200": { 116 "200": {
154 "description": "OK", 117 "description": "OK",
@@ -182,13 +145,13 @@ const docTemplate = `{
182 }, 145 },
183 "/games/{id}": { 146 "/games/{id}": {
184 "get": { 147 "get": {
148 "description": "Get chapters from the specified game id.",
185 "produces": [ 149 "produces": [
186 "application/json" 150 "application/json"
187 ], 151 ],
188 "tags": [ 152 "tags": [
189 "games \u0026 chapters" 153 "games \u0026 chapters"
190 ], 154 ],
191 "summary": "Get chapters from the specified game id.",
192 "parameters": [ 155 "parameters": [
193 { 156 {
194 "type": "integer", 157 "type": "integer",
@@ -228,6 +191,7 @@ const docTemplate = `{
228 }, 191 },
229 "/login": { 192 "/login": {
230 "get": { 193 "get": {
194 "description": "Get (redirect) login page for Steam auth.",
231 "consumes": [ 195 "consumes": [
232 "application/json" 196 "application/json"
233 ], 197 ],
@@ -237,7 +201,6 @@ const docTemplate = `{
237 "tags": [ 201 "tags": [
238 "login" 202 "login"
239 ], 203 ],
240 "summary": "Get (redirect) login page for Steam auth.",
241 "responses": { 204 "responses": {
242 "200": { 205 "200": {
243 "description": "OK", 206 "description": "OK",
@@ -266,15 +229,77 @@ const docTemplate = `{
266 } 229 }
267 } 230 }
268 }, 231 },
232 "/maps/{id}/image": {
233 "put": {
234 "description": "Edit map image with specified map id.",
235 "produces": [
236 "application/json"
237 ],
238 "tags": [
239 "maps"
240 ],
241 "parameters": [
242 {
243 "type": "string",
244 "description": "JWT Token",
245 "name": "Authorization",
246 "in": "header",
247 "required": true
248 },
249 {
250 "type": "integer",
251 "description": "Map ID",
252 "name": "id",
253 "in": "path",
254 "required": true
255 },
256 {
257 "description": "Body",
258 "name": "request",
259 "in": "body",
260 "required": true,
261 "schema": {
262 "$ref": "#/definitions/models.EditMapImageRequest"
263 }
264 }
265 ],
266 "responses": {
267 "200": {
268 "description": "OK",
269 "schema": {
270 "allOf": [
271 {
272 "$ref": "#/definitions/models.Response"
273 },
274 {
275 "type": "object",
276 "properties": {
277 "data": {
278 "$ref": "#/definitions/models.EditMapImageRequest"
279 }
280 }
281 }
282 ]
283 }
284 },
285 "400": {
286 "description": "Bad Request",
287 "schema": {
288 "$ref": "#/definitions/models.Response"
289 }
290 }
291 }
292 }
293 },
269 "/maps/{id}/leaderboards": { 294 "/maps/{id}/leaderboards": {
270 "get": { 295 "get": {
296 "description": "Get map leaderboards with specified id.",
271 "produces": [ 297 "produces": [
272 "application/json" 298 "application/json"
273 ], 299 ],
274 "tags": [ 300 "tags": [
275 "maps" 301 "maps"
276 ], 302 ],
277 "summary": "Get map leaderboards with specified id.",
278 "parameters": [ 303 "parameters": [
279 { 304 {
280 "type": "integer", 305 "type": "integer",
@@ -326,6 +351,7 @@ const docTemplate = `{
326 }, 351 },
327 "/maps/{id}/record": { 352 "/maps/{id}/record": {
328 "post": { 353 "post": {
354 "description": "Post record with demo of a specific map.",
329 "consumes": [ 355 "consumes": [
330 "multipart/form-data" 356 "multipart/form-data"
331 ], 357 ],
@@ -335,9 +361,15 @@ const docTemplate = `{
335 "tags": [ 361 "tags": [
336 "maps" 362 "maps"
337 ], 363 ],
338 "summary": "Post record with demo of a specific map.",
339 "parameters": [ 364 "parameters": [
340 { 365 {
366 "type": "integer",
367 "description": "Map ID",
368 "name": "id",
369 "in": "path",
370 "required": true
371 },
372 {
341 "type": "string", 373 "type": "string",
342 "description": "JWT Token", 374 "description": "JWT Token",
343 "name": "Authorization", 375 "name": "Authorization",
@@ -345,42 +377,29 @@ const docTemplate = `{
345 "required": true 377 "required": true
346 }, 378 },
347 { 379 {
348 "type": "array", 380 "type": "file",
349 "items": { 381 "description": "Host Demo",
350 "type": "file" 382 "name": "host_demo",
351 },
352 "description": "Demos",
353 "name": "demos",
354 "in": "formData", 383 "in": "formData",
355 "required": true 384 "required": true
356 }, 385 },
357 { 386 {
358 "type": "integer", 387 "type": "file",
359 "description": "Score Count", 388 "description": "Partner Demo",
360 "name": "score_count", 389 "name": "partner_demo",
361 "in": "formData", 390 "in": "formData"
362 "required": true
363 },
364 {
365 "type": "integer",
366 "description": "Score Time",
367 "name": "score_time",
368 "in": "formData",
369 "required": true
370 }, 391 },
371 { 392 {
372 "type": "boolean", 393 "type": "boolean",
373 "description": "Is Partner Orange", 394 "description": "Is Partner Orange",
374 "name": "is_partner_orange", 395 "name": "is_partner_orange",
375 "in": "formData", 396 "in": "formData"
376 "required": true
377 }, 397 },
378 { 398 {
379 "type": "string", 399 "type": "string",
380 "description": "Partner ID", 400 "description": "Partner ID",
381 "name": "partner_id", 401 "name": "partner_id",
382 "in": "formData", 402 "in": "formData"
383 "required": true
384 } 403 }
385 ], 404 ],
386 "responses": { 405 "responses": {
@@ -395,7 +414,7 @@ const docTemplate = `{
395 "type": "object", 414 "type": "object",
396 "properties": { 415 "properties": {
397 "data": { 416 "data": {
398 "$ref": "#/definitions/models.RecordRequest" 417 "$ref": "#/definitions/models.RecordResponse"
399 } 418 }
400 } 419 }
401 } 420 }
@@ -419,13 +438,13 @@ const docTemplate = `{
419 }, 438 },
420 "/maps/{id}/summary": { 439 "/maps/{id}/summary": {
421 "get": { 440 "get": {
441 "description": "Get map summary with specified id.",
422 "produces": [ 442 "produces": [
423 "application/json" 443 "application/json"
424 ], 444 ],
425 "tags": [ 445 "tags": [
426 "maps" 446 "maps"
427 ], 447 ],
428 "summary": "Get map summary with specified id.",
429 "parameters": [ 448 "parameters": [
430 { 449 {
431 "type": "integer", 450 "type": "integer",
@@ -447,19 +466,187 @@ const docTemplate = `{
447 "type": "object", 466 "type": "object",
448 "properties": { 467 "properties": {
449 "data": { 468 "data": {
450 "allOf": [ 469 "$ref": "#/definitions/models.MapSummaryResponse"
451 { 470 }
452 "$ref": "#/definitions/models.Map" 471 }
453 }, 472 }
454 { 473 ]
455 "type": "object", 474 }
456 "properties": { 475 },
457 "data": { 476 "400": {
458 "$ref": "#/definitions/models.MapSummary" 477 "description": "Bad Request",
459 } 478 "schema": {
460 } 479 "$ref": "#/definitions/models.Response"
461 } 480 }
462 ] 481 }
482 }
483 },
484 "put": {
485 "description": "Edit map summary with specified map id.",
486 "produces": [
487 "application/json"
488 ],
489 "tags": [
490 "maps"
491 ],
492 "parameters": [
493 {
494 "type": "string",
495 "description": "JWT Token",
496 "name": "Authorization",
497 "in": "header",
498 "required": true
499 },
500 {
501 "type": "integer",
502 "description": "Map ID",
503 "name": "id",
504 "in": "path",
505 "required": true
506 },
507 {
508 "description": "Body",
509 "name": "request",
510 "in": "body",
511 "required": true,
512 "schema": {
513 "$ref": "#/definitions/models.EditMapSummaryRequest"
514 }
515 }
516 ],
517 "responses": {
518 "200": {
519 "description": "OK",
520 "schema": {
521 "allOf": [
522 {
523 "$ref": "#/definitions/models.Response"
524 },
525 {
526 "type": "object",
527 "properties": {
528 "data": {
529 "$ref": "#/definitions/models.EditMapSummaryRequest"
530 }
531 }
532 }
533 ]
534 }
535 },
536 "400": {
537 "description": "Bad Request",
538 "schema": {
539 "$ref": "#/definitions/models.Response"
540 }
541 }
542 }
543 },
544 "post": {
545 "description": "Create map summary with specified map id.",
546 "produces": [
547 "application/json"
548 ],
549 "tags": [
550 "maps"
551 ],
552 "parameters": [
553 {
554 "type": "string",
555 "description": "JWT Token",
556 "name": "Authorization",
557 "in": "header",
558 "required": true
559 },
560 {
561 "type": "integer",
562 "description": "Map ID",
563 "name": "id",
564 "in": "path",
565 "required": true
566 },
567 {
568 "description": "Body",
569 "name": "request",
570 "in": "body",
571 "required": true,
572 "schema": {
573 "$ref": "#/definitions/models.CreateMapSummaryRequest"
574 }
575 }
576 ],
577 "responses": {
578 "200": {
579 "description": "OK",
580 "schema": {
581 "allOf": [
582 {
583 "$ref": "#/definitions/models.Response"
584 },
585 {
586 "type": "object",
587 "properties": {
588 "data": {
589 "$ref": "#/definitions/models.CreateMapSummaryRequest"
590 }
591 }
592 }
593 ]
594 }
595 },
596 "400": {
597 "description": "Bad Request",
598 "schema": {
599 "$ref": "#/definitions/models.Response"
600 }
601 }
602 }
603 },
604 "delete": {
605 "description": "Delete map summary with specified map id.",
606 "produces": [
607 "application/json"
608 ],
609 "tags": [
610 "maps"
611 ],
612 "parameters": [
613 {
614 "type": "string",
615 "description": "JWT Token",
616 "name": "Authorization",
617 "in": "header",
618 "required": true
619 },
620 {
621 "type": "integer",
622 "description": "Map ID",
623 "name": "id",
624 "in": "path",
625 "required": true
626 },
627 {
628 "description": "Body",
629 "name": "request",
630 "in": "body",
631 "required": true,
632 "schema": {
633 "$ref": "#/definitions/models.DeleteMapSummaryRequest"
634 }
635 }
636 ],
637 "responses": {
638 "200": {
639 "description": "OK",
640 "schema": {
641 "allOf": [
642 {
643 "$ref": "#/definitions/models.Response"
644 },
645 {
646 "type": "object",
647 "properties": {
648 "data": {
649 "$ref": "#/definitions/models.DeleteMapSummaryRequest"
463 } 650 }
464 } 651 }
465 } 652 }
@@ -477,6 +664,7 @@ const docTemplate = `{
477 }, 664 },
478 "/profile": { 665 "/profile": {
479 "get": { 666 "get": {
667 "description": "Get profile page of session user.",
480 "consumes": [ 668 "consumes": [
481 "application/json" 669 "application/json"
482 ], 670 ],
@@ -486,7 +674,6 @@ const docTemplate = `{
486 "tags": [ 674 "tags": [
487 "users" 675 "users"
488 ], 676 ],
489 "summary": "Get profile page of session user.",
490 "parameters": [ 677 "parameters": [
491 { 678 {
492 "type": "string", 679 "type": "string",
@@ -530,6 +717,7 @@ const docTemplate = `{
530 } 717 }
531 }, 718 },
532 "put": { 719 "put": {
720 "description": "Update country code of session user.",
533 "consumes": [ 721 "consumes": [
534 "application/json" 722 "application/json"
535 ], 723 ],
@@ -539,7 +727,6 @@ const docTemplate = `{
539 "tags": [ 727 "tags": [
540 "users" 728 "users"
541 ], 729 ],
542 "summary": "Update country code of session user.",
543 "parameters": [ 730 "parameters": [
544 { 731 {
545 "type": "string", 732 "type": "string",
@@ -560,19 +747,7 @@ const docTemplate = `{
560 "200": { 747 "200": {
561 "description": "OK", 748 "description": "OK",
562 "schema": { 749 "schema": {
563 "allOf": [ 750 "$ref": "#/definitions/models.Response"
564 {
565 "$ref": "#/definitions/models.Response"
566 },
567 {
568 "type": "object",
569 "properties": {
570 "data": {
571 "$ref": "#/definitions/models.ProfileResponse"
572 }
573 }
574 }
575 ]
576 } 751 }
577 }, 752 },
578 "400": { 753 "400": {
@@ -590,6 +765,7 @@ const docTemplate = `{
590 } 765 }
591 }, 766 },
592 "post": { 767 "post": {
768 "description": "Update profile page of session user.",
593 "consumes": [ 769 "consumes": [
594 "application/json" 770 "application/json"
595 ], 771 ],
@@ -599,7 +775,6 @@ const docTemplate = `{
599 "tags": [ 775 "tags": [
600 "users" 776 "users"
601 ], 777 ],
602 "summary": "Update profile page of session user.",
603 "parameters": [ 778 "parameters": [
604 { 779 {
605 "type": "string", 780 "type": "string",
@@ -643,15 +818,60 @@ const docTemplate = `{
643 } 818 }
644 } 819 }
645 }, 820 },
821 "/rankings": {
822 "get": {
823 "description": "Get rankings of every player.",
824 "produces": [
825 "application/json"
826 ],
827 "tags": [
828 "rankings"
829 ],
830 "responses": {
831 "200": {
832 "description": "OK",
833 "schema": {
834 "allOf": [
835 {
836 "$ref": "#/definitions/models.Response"
837 },
838 {
839 "type": "object",
840 "properties": {
841 "data": {
842 "$ref": "#/definitions/models.RankingsResponse"
843 }
844 }
845 }
846 ]
847 }
848 },
849 "400": {
850 "description": "Bad Request",
851 "schema": {
852 "$ref": "#/definitions/models.Response"
853 }
854 }
855 }
856 }
857 },
646 "/search": { 858 "/search": {
647 "get": { 859 "get": {
860 "description": "Get all user and map data matching to the query.",
648 "produces": [ 861 "produces": [
649 "application/json" 862 "application/json"
650 ], 863 ],
651 "tags": [ 864 "tags": [
652 "search" 865 "search"
653 ], 866 ],
654 "summary": "Get all user and map data.", 867 "parameters": [
868 {
869 "type": "string",
870 "description": "Search user or map name.",
871 "name": "q",
872 "in": "query"
873 }
874 ],
655 "responses": { 875 "responses": {
656 "200": { 876 "200": {
657 "description": "OK", 877 "description": "OK",
@@ -682,13 +902,13 @@ const docTemplate = `{
682 }, 902 },
683 "/token": { 903 "/token": {
684 "get": { 904 "get": {
905 "description": "Gets the token cookie value from the user.",
685 "produces": [ 906 "produces": [
686 "application/json" 907 "application/json"
687 ], 908 ],
688 "tags": [ 909 "tags": [
689 "auth" 910 "auth"
690 ], 911 ],
691 "summary": "Gets the token cookie value from the user.",
692 "responses": { 912 "responses": {
693 "200": { 913 "200": {
694 "description": "OK", 914 "description": "OK",
@@ -717,13 +937,13 @@ const docTemplate = `{
717 } 937 }
718 }, 938 },
719 "delete": { 939 "delete": {
940 "description": "Deletes the token cookie from the user.",
720 "produces": [ 941 "produces": [
721 "application/json" 942 "application/json"
722 ], 943 ],
723 "tags": [ 944 "tags": [
724 "auth" 945 "auth"
725 ], 946 ],
726 "summary": "Deletes the token cookie from the user.",
727 "responses": { 947 "responses": {
728 "200": { 948 "200": {
729 "description": "OK", 949 "description": "OK",
@@ -754,6 +974,7 @@ const docTemplate = `{
754 }, 974 },
755 "/users/{id}": { 975 "/users/{id}": {
756 "get": { 976 "get": {
977 "description": "Get profile page of another user.",
757 "consumes": [ 978 "consumes": [
758 "application/json" 979 "application/json"
759 ], 980 ],
@@ -763,7 +984,6 @@ const docTemplate = `{
763 "tags": [ 984 "tags": [
764 "users" 985 "users"
765 ], 986 ],
766 "summary": "Get profile page of another user.",
767 "parameters": [ 987 "parameters": [
768 { 988 {
769 "type": "integer", 989 "type": "integer",
@@ -809,6 +1029,17 @@ const docTemplate = `{
809 } 1029 }
810 }, 1030 },
811 "definitions": { 1031 "definitions": {
1032 "models.Category": {
1033 "type": "object",
1034 "properties": {
1035 "id": {
1036 "type": "integer"
1037 },
1038 "name": {
1039 "type": "string"
1040 }
1041 }
1042 },
812 "models.Chapter": { 1043 "models.Chapter": {
813 "type": "object", 1044 "type": "object",
814 "properties": { 1045 "properties": {
@@ -848,12 +1079,97 @@ const docTemplate = `{
848 } 1079 }
849 } 1080 }
850 }, 1081 },
1082 "models.CreateMapSummaryRequest": {
1083 "type": "object",
1084 "required": [
1085 "category_id",
1086 "description",
1087 "record_date",
1088 "score_count",
1089 "user_name"
1090 ],
1091 "properties": {
1092 "category_id": {
1093 "type": "integer"
1094 },
1095 "description": {
1096 "type": "string"
1097 },
1098 "record_date": {
1099 "type": "string"
1100 },
1101 "score_count": {
1102 "type": "integer"
1103 },
1104 "showcase": {
1105 "type": "string"
1106 },
1107 "user_name": {
1108 "type": "string"
1109 }
1110 }
1111 },
1112 "models.DeleteMapSummaryRequest": {
1113 "type": "object",
1114 "required": [
1115 "route_id"
1116 ],
1117 "properties": {
1118 "route_id": {
1119 "type": "integer"
1120 }
1121 }
1122 },
1123 "models.EditMapImageRequest": {
1124 "type": "object",
1125 "required": [
1126 "image"
1127 ],
1128 "properties": {
1129 "image": {
1130 "type": "string"
1131 }
1132 }
1133 },
1134 "models.EditMapSummaryRequest": {
1135 "type": "object",
1136 "required": [
1137 "description",
1138 "record_date",
1139 "route_id",
1140 "score_count",
1141 "user_name"
1142 ],
1143 "properties": {
1144 "description": {
1145 "type": "string"
1146 },
1147 "record_date": {
1148 "type": "string"
1149 },
1150 "route_id": {
1151 "type": "integer"
1152 },
1153 "score_count": {
1154 "type": "integer"
1155 },
1156 "showcase": {
1157 "type": "string"
1158 },
1159 "user_name": {
1160 "type": "string"
1161 }
1162 }
1163 },
851 "models.Game": { 1164 "models.Game": {
852 "type": "object", 1165 "type": "object",
853 "properties": { 1166 "properties": {
854 "id": { 1167 "id": {
855 "type": "integer" 1168 "type": "integer"
856 }, 1169 },
1170 "is_coop": {
1171 "type": "boolean"
1172 },
857 "name": { 1173 "name": {
858 "type": "string" 1174 "type": "string"
859 } 1175 }
@@ -873,32 +1189,20 @@ const docTemplate = `{
873 "chapter_name": { 1189 "chapter_name": {
874 "type": "string" 1190 "type": "string"
875 }, 1191 },
876 "data": {},
877 "game_name": { 1192 "game_name": {
878 "type": "string" 1193 "type": "string"
879 }, 1194 },
880 "id": { 1195 "id": {
881 "type": "integer" 1196 "type": "integer"
882 }, 1197 },
883 "map_name": { 1198 "image": {
884 "type": "string" 1199 "type": "string"
885 }
886 }
887 },
888 "models.MapCategoryScores": {
889 "type": "object",
890 "properties": {
891 "any": {
892 "type": "integer"
893 }, 1200 },
894 "cm": { 1201 "is_coop": {
895 "type": "integer" 1202 "type": "boolean"
896 },
897 "inbounds_sla": {
898 "type": "integer"
899 }, 1203 },
900 "no_sla": { 1204 "map_name": {
901 "type": "integer" 1205 "type": "string"
902 } 1206 }
903 } 1207 }
904 }, 1208 },
@@ -922,6 +1226,29 @@ const docTemplate = `{
922 "records": {} 1226 "records": {}
923 } 1227 }
924 }, 1228 },
1229 "models.MapRoute": {
1230 "type": "object",
1231 "properties": {
1232 "category": {
1233 "$ref": "#/definitions/models.Category"
1234 },
1235 "description": {
1236 "type": "string"
1237 },
1238 "history": {
1239 "$ref": "#/definitions/models.MapHistory"
1240 },
1241 "rating": {
1242 "type": "number"
1243 },
1244 "route_id": {
1245 "type": "integer"
1246 },
1247 "showcase": {
1248 "type": "string"
1249 }
1250 }
1251 },
925 "models.MapShort": { 1252 "models.MapShort": {
926 "type": "object", 1253 "type": "object",
927 "properties": { 1254 "properties": {
@@ -936,29 +1263,22 @@ const docTemplate = `{
936 "models.MapSummary": { 1263 "models.MapSummary": {
937 "type": "object", 1264 "type": "object",
938 "properties": { 1265 "properties": {
939 "category_scores": { 1266 "routes": {
940 "$ref": "#/definitions/models.MapCategoryScores"
941 },
942 "description": {
943 "type": "string"
944 },
945 "history": {
946 "type": "array",
947 "items": {
948 "$ref": "#/definitions/models.MapHistory"
949 }
950 },
951 "rating": {
952 "type": "number"
953 },
954 "routers": {
955 "type": "array", 1267 "type": "array",
956 "items": { 1268 "items": {
957 "type": "string" 1269 "$ref": "#/definitions/models.MapRoute"
958 } 1270 }
1271 }
1272 }
1273 },
1274 "models.MapSummaryResponse": {
1275 "type": "object",
1276 "properties": {
1277 "map": {
1278 "$ref": "#/definitions/models.Map"
959 }, 1279 },
960 "showcase": { 1280 "summary": {
961 "type": "string" 1281 "$ref": "#/definitions/models.MapSummary"
962 } 1282 }
963 } 1283 }
964 }, 1284 },
@@ -1011,21 +1331,9 @@ const docTemplate = `{
1011 } 1331 }
1012 } 1332 }
1013 }, 1333 },
1014 "models.RecordRequest": { 1334 "models.RecordResponse": {
1015 "type": "object", 1335 "type": "object",
1016 "required": [
1017 "is_partner_orange",
1018 "partner_id",
1019 "score_count",
1020 "score_time"
1021 ],
1022 "properties": { 1336 "properties": {
1023 "is_partner_orange": {
1024 "type": "boolean"
1025 },
1026 "partner_id": {
1027 "type": "string"
1028 },
1029 "score_count": { 1337 "score_count": {
1030 "type": "integer" 1338 "type": "integer"
1031 }, 1339 },
@@ -1061,29 +1369,13 @@ const docTemplate = `{
1061 "maps": { 1369 "maps": {
1062 "type": "array", 1370 "type": "array",
1063 "items": { 1371 "items": {
1064 "type": "object", 1372 "$ref": "#/definitions/models.MapShort"
1065 "properties": {
1066 "id": {
1067 "type": "integer"
1068 },
1069 "name": {
1070 "type": "string"
1071 }
1072 }
1073 } 1373 }
1074 }, 1374 },
1075 "players": { 1375 "players": {
1076 "type": "array", 1376 "type": "array",
1077 "items": { 1377 "items": {
1078 "type": "object", 1378 "$ref": "#/definitions/models.UserShort"
1079 "properties": {
1080 "steam_id": {
1081 "type": "string"
1082 },
1083 "user_name": {
1084 "type": "string"
1085 }
1086 }
1087 } 1379 }
1088 } 1380 }
1089 } 1381 }
@@ -1101,6 +1393,17 @@ const docTemplate = `{
1101 "type": "string" 1393 "type": "string"
1102 } 1394 }
1103 } 1395 }
1396 },
1397 "models.UserShort": {
1398 "type": "object",
1399 "properties": {
1400 "steam_id": {
1401 "type": "string"
1402 },
1403 "user_name": {
1404 "type": "string"
1405 }
1406 }
1104 } 1407 }
1105 } 1408 }
1106}` 1409}`
diff --git a/docs/swagger.json b/docs/swagger.json
index ad2a659..2e1a789 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -15,13 +15,13 @@
15 "paths": { 15 "paths": {
16 "/chapters/{id}": { 16 "/chapters/{id}": {
17 "get": { 17 "get": {
18 "description": "Get maps from the specified chapter id.",
18 "produces": [ 19 "produces": [
19 "application/json" 20 "application/json"
20 ], 21 ],
21 "tags": [ 22 "tags": [
22 "games \u0026 chapters" 23 "games \u0026 chapters"
23 ], 24 ],
24 "summary": "Get maps from the specified chapter id.",
25 "parameters": [ 25 "parameters": [
26 { 26 {
27 "type": "integer", 27 "type": "integer",
@@ -59,45 +59,9 @@
59 } 59 }
60 } 60 }
61 }, 61 },
62 "/demo": {
63 "get": {
64 "produces": [
65 "application/json"
66 ],
67 "tags": [
68 "rankings"
69 ],
70 "summary": "Get rankings of every player.",
71 "responses": {
72 "200": {
73 "description": "OK",
74 "schema": {
75 "allOf": [
76 {
77 "$ref": "#/definitions/models.Response"
78 },
79 {
80 "type": "object",
81 "properties": {
82 "data": {
83 "$ref": "#/definitions/models.RankingsResponse"
84 }
85 }
86 }
87 ]
88 }
89 },
90 "400": {
91 "description": "Bad Request",
92 "schema": {
93 "$ref": "#/definitions/models.Response"
94 }
95 }
96 }
97 }
98 },
99 "/demos": { 62 "/demos": {
100 "get": { 63 "get": {
64 "description": "Get demo with specified demo uuid.",
101 "consumes": [ 65 "consumes": [
102 "application/json" 66 "application/json"
103 ], 67 ],
@@ -107,10 +71,9 @@
107 "tags": [ 71 "tags": [
108 "demo" 72 "demo"
109 ], 73 ],
110 "summary": "Get demo with specified demo uuid.",
111 "parameters": [ 74 "parameters": [
112 { 75 {
113 "type": "integer", 76 "type": "string",
114 "description": "Demo UUID", 77 "description": "Demo UUID",
115 "name": "uuid", 78 "name": "uuid",
116 "in": "query", 79 "in": "query",
@@ -135,13 +98,13 @@
135 }, 98 },
136 "/games": { 99 "/games": {
137 "get": { 100 "get": {
101 "description": "Get games from the leaderboards.",
138 "produces": [ 102 "produces": [
139 "application/json" 103 "application/json"
140 ], 104 ],
141 "tags": [ 105 "tags": [
142 "games \u0026 chapters" 106 "games \u0026 chapters"
143 ], 107 ],
144 "summary": "Get games from the leaderboards.",
145 "responses": { 108 "responses": {
146 "200": { 109 "200": {
147 "description": "OK", 110 "description": "OK",
@@ -175,13 +138,13 @@
175 }, 138 },
176 "/games/{id}": { 139 "/games/{id}": {
177 "get": { 140 "get": {
141 "description": "Get chapters from the specified game id.",
178 "produces": [ 142 "produces": [
179 "application/json" 143 "application/json"
180 ], 144 ],
181 "tags": [ 145 "tags": [
182 "games \u0026 chapters" 146 "games \u0026 chapters"
183 ], 147 ],
184 "summary": "Get chapters from the specified game id.",
185 "parameters": [ 148 "parameters": [
186 { 149 {
187 "type": "integer", 150 "type": "integer",
@@ -221,6 +184,7 @@
221 }, 184 },
222 "/login": { 185 "/login": {
223 "get": { 186 "get": {
187 "description": "Get (redirect) login page for Steam auth.",
224 "consumes": [ 188 "consumes": [
225 "application/json" 189 "application/json"
226 ], 190 ],
@@ -230,7 +194,6 @@
230 "tags": [ 194 "tags": [
231 "login" 195 "login"
232 ], 196 ],
233 "summary": "Get (redirect) login page for Steam auth.",
234 "responses": { 197 "responses": {
235 "200": { 198 "200": {
236 "description": "OK", 199 "description": "OK",
@@ -259,15 +222,77 @@
259 } 222 }
260 } 223 }
261 }, 224 },
225 "/maps/{id}/image": {
226 "put": {
227 "description": "Edit map image with specified map id.",
228 "produces": [
229 "application/json"
230 ],
231 "tags": [
232 "maps"
233 ],
234 "parameters": [
235 {
236 "type": "string",
237 "description": "JWT Token",
238 "name": "Authorization",
239 "in": "header",
240 "required": true
241 },
242 {
243 "type": "integer",
244 "description": "Map ID",
245 "name": "id",
246 "in": "path",
247 "required": true
248 },
249 {
250 "description": "Body",
251 "name": "request",
252 "in": "body",
253 "required": true,
254 "schema": {
255 "$ref": "#/definitions/models.EditMapImageRequest"
256 }
257 }
258 ],
259 "responses": {
260 "200": {
261 "description": "OK",
262 "schema": {
263 "allOf": [
264 {
265 "$ref": "#/definitions/models.Response"
266 },
267 {
268 "type": "object",
269 "properties": {
270 "data": {
271 "$ref": "#/definitions/models.EditMapImageRequest"
272 }
273 }
274 }
275 ]
276 }
277 },
278 "400": {
279 "description": "Bad Request",
280 "schema": {
281 "$ref": "#/definitions/models.Response"
282 }
283 }
284 }
285 }
286 },
262 "/maps/{id}/leaderboards": { 287 "/maps/{id}/leaderboards": {
263 "get": { 288 "get": {
289 "description": "Get map leaderboards with specified id.",
264 "produces": [ 290 "produces": [
265 "application/json" 291 "application/json"
266 ], 292 ],
267 "tags": [ 293 "tags": [
268 "maps" 294 "maps"
269 ], 295 ],
270 "summary": "Get map leaderboards with specified id.",
271 "parameters": [ 296 "parameters": [
272 { 297 {
273 "type": "integer", 298 "type": "integer",
@@ -319,6 +344,7 @@
319 }, 344 },
320 "/maps/{id}/record": { 345 "/maps/{id}/record": {
321 "post": { 346 "post": {
347 "description": "Post record with demo of a specific map.",
322 "consumes": [ 348 "consumes": [
323 "multipart/form-data" 349 "multipart/form-data"
324 ], 350 ],
@@ -328,9 +354,15 @@
328 "tags": [ 354 "tags": [
329 "maps" 355 "maps"
330 ], 356 ],
331 "summary": "Post record with demo of a specific map.",
332 "parameters": [ 357 "parameters": [
333 { 358 {
359 "type": "integer",
360 "description": "Map ID",
361 "name": "id",
362 "in": "path",
363 "required": true
364 },
365 {
334 "type": "string", 366 "type": "string",
335 "description": "JWT Token", 367 "description": "JWT Token",
336 "name": "Authorization", 368 "name": "Authorization",
@@ -338,42 +370,29 @@
338 "required": true 370 "required": true
339 }, 371 },
340 { 372 {
341 "type": "array", 373 "type": "file",
342 "items": { 374 "description": "Host Demo",
343 "type": "file" 375 "name": "host_demo",
344 },
345 "description": "Demos",
346 "name": "demos",
347 "in": "formData", 376 "in": "formData",
348 "required": true 377 "required": true
349 }, 378 },
350 { 379 {
351 "type": "integer", 380 "type": "file",
352 "description": "Score Count", 381 "description": "Partner Demo",
353 "name": "score_count", 382 "name": "partner_demo",
354 "in": "formData", 383 "in": "formData"
355 "required": true
356 },
357 {
358 "type": "integer",
359 "description": "Score Time",
360 "name": "score_time",
361 "in": "formData",
362 "required": true
363 }, 384 },
364 { 385 {
365 "type": "boolean", 386 "type": "boolean",
366 "description": "Is Partner Orange", 387 "description": "Is Partner Orange",
367 "name": "is_partner_orange", 388 "name": "is_partner_orange",
368 "in": "formData", 389 "in": "formData"
369 "required": true
370 }, 390 },
371 { 391 {
372 "type": "string", 392 "type": "string",
373 "description": "Partner ID", 393 "description": "Partner ID",
374 "name": "partner_id", 394 "name": "partner_id",
375 "in": "formData", 395 "in": "formData"
376 "required": true
377 } 396 }
378 ], 397 ],
379 "responses": { 398 "responses": {
@@ -388,7 +407,7 @@
388 "type": "object", 407 "type": "object",
389 "properties": { 408 "properties": {
390 "data": { 409 "data": {
391 "$ref": "#/definitions/models.RecordRequest" 410 "$ref": "#/definitions/models.RecordResponse"
392 } 411 }
393 } 412 }
394 } 413 }
@@ -412,13 +431,13 @@
412 }, 431 },
413 "/maps/{id}/summary": { 432 "/maps/{id}/summary": {
414 "get": { 433 "get": {
434 "description": "Get map summary with specified id.",
415 "produces": [ 435 "produces": [
416 "application/json" 436 "application/json"
417 ], 437 ],
418 "tags": [ 438 "tags": [
419 "maps" 439 "maps"
420 ], 440 ],
421 "summary": "Get map summary with specified id.",
422 "parameters": [ 441 "parameters": [
423 { 442 {
424 "type": "integer", 443 "type": "integer",
@@ -440,19 +459,187 @@
440 "type": "object", 459 "type": "object",
441 "properties": { 460 "properties": {
442 "data": { 461 "data": {
443 "allOf": [ 462 "$ref": "#/definitions/models.MapSummaryResponse"
444 { 463 }
445 "$ref": "#/definitions/models.Map" 464 }
446 }, 465 }
447 { 466 ]
448 "type": "object", 467 }
449 "properties": { 468 },
450 "data": { 469 "400": {
451 "$ref": "#/definitions/models.MapSummary" 470 "description": "Bad Request",
452 } 471 "schema": {
453 } 472 "$ref": "#/definitions/models.Response"
454 } 473 }
455 ] 474 }
475 }
476 },
477 "put": {
478 "description": "Edit map summary with specified map id.",
479 "produces": [
480 "application/json"
481 ],
482 "tags": [
483 "maps"
484 ],
485 "parameters": [
486 {
487 "type": "string",
488 "description": "JWT Token",
489 "name": "Authorization",
490 "in": "header",
491 "required": true
492 },
493 {
494 "type": "integer",
495 "description": "Map ID",
496 "name": "id",
497 "in": "path",
498 "required": true
499 },
500 {
501 "description": "Body",
502 "name": "request",
503 "in": "body",
504 "required": true,
505 "schema": {
506 "$ref": "#/definitions/models.EditMapSummaryRequest"
507 }
508 }
509 ],
510 "responses": {
511 "200": {
512 "description": "OK",
513 "schema": {
514 "allOf": [
515 {
516 "$ref": "#/definitions/models.Response"
517 },
518 {
519 "type": "object",
520 "properties": {
521 "data": {
522 "$ref": "#/definitions/models.EditMapSummaryRequest"
523 }
524 }
525 }
526 ]
527 }
528 },
529 "400": {
530 "description": "Bad Request",
531 "schema": {
532 "$ref": "#/definitions/models.Response"
533 }
534 }
535 }
536 },
537 "post": {
538 "description": "Create map summary with specified map id.",
539 "produces": [
540 "application/json"
541 ],
542 "tags": [
543 "maps"
544 ],
545 "parameters": [
546 {
547 "type": "string",
548 "description": "JWT Token",
549 "name": "Authorization",
550 "in": "header",
551 "required": true
552 },
553 {
554 "type": "integer",
555 "description": "Map ID",
556 "name": "id",
557 "in": "path",
558 "required": true
559 },
560 {
561 "description": "Body",
562 "name": "request",
563 "in": "body",
564 "required": true,
565 "schema": {
566 "$ref": "#/definitions/models.CreateMapSummaryRequest"
567 }
568 }
569 ],
570 "responses": {
571 "200": {
572 "description": "OK",
573 "schema": {
574 "allOf": [
575 {
576 "$ref": "#/definitions/models.Response"
577 },
578 {
579 "type": "object",
580 "properties": {
581 "data": {
582 "$ref": "#/definitions/models.CreateMapSummaryRequest"
583 }
584 }
585 }
586 ]
587 }
588 },
589 "400": {
590 "description": "Bad Request",
591 "schema": {
592 "$ref": "#/definitions/models.Response"
593 }
594 }
595 }
596 },
597 "delete": {
598 "description": "Delete map summary with specified map id.",
599 "produces": [
600 "application/json"
601 ],
602 "tags": [
603 "maps"
604 ],
605 "parameters": [
606 {
607 "type": "string",
608 "description": "JWT Token",
609 "name": "Authorization",
610 "in": "header",
611 "required": true
612 },
613 {
614 "type": "integer",
615 "description": "Map ID",
616 "name": "id",
617 "in": "path",
618 "required": true
619 },
620 {
621 "description": "Body",
622 "name": "request",
623 "in": "body",
624 "required": true,
625 "schema": {
626 "$ref": "#/definitions/models.DeleteMapSummaryRequest"
627 }
628 }
629 ],
630 "responses": {
631 "200": {
632 "description": "OK",
633 "schema": {
634 "allOf": [
635 {
636 "$ref": "#/definitions/models.Response"
637 },
638 {
639 "type": "object",
640 "properties": {
641 "data": {
642 "$ref": "#/definitions/models.DeleteMapSummaryRequest"
456 } 643 }
457 } 644 }
458 } 645 }
@@ -470,6 +657,7 @@
470 }, 657 },
471 "/profile": { 658 "/profile": {
472 "get": { 659 "get": {
660 "description": "Get profile page of session user.",
473 "consumes": [ 661 "consumes": [
474 "application/json" 662 "application/json"
475 ], 663 ],
@@ -479,7 +667,6 @@
479 "tags": [ 667 "tags": [
480 "users" 668 "users"
481 ], 669 ],
482 "summary": "Get profile page of session user.",
483 "parameters": [ 670 "parameters": [
484 { 671 {
485 "type": "string", 672 "type": "string",
@@ -523,6 +710,7 @@
523 } 710 }
524 }, 711 },
525 "put": { 712 "put": {
713 "description": "Update country code of session user.",
526 "consumes": [ 714 "consumes": [
527 "application/json" 715 "application/json"
528 ], 716 ],
@@ -532,7 +720,6 @@
532 "tags": [ 720 "tags": [
533 "users" 721 "users"
534 ], 722 ],
535 "summary": "Update country code of session user.",
536 "parameters": [ 723 "parameters": [
537 { 724 {
538 "type": "string", 725 "type": "string",
@@ -553,19 +740,7 @@
553 "200": { 740 "200": {
554 "description": "OK", 741 "description": "OK",
555 "schema": { 742 "schema": {
556 "allOf": [ 743 "$ref": "#/definitions/models.Response"
557 {
558 "$ref": "#/definitions/models.Response"
559 },
560 {
561 "type": "object",
562 "properties": {
563 "data": {
564 "$ref": "#/definitions/models.ProfileResponse"
565 }
566 }
567 }
568 ]
569 } 744 }
570 }, 745 },
571 "400": { 746 "400": {
@@ -583,6 +758,7 @@
583 } 758 }
584 }, 759 },
585 "post": { 760 "post": {
761 "description": "Update profile page of session user.",
586 "consumes": [ 762 "consumes": [
587 "application/json" 763 "application/json"
588 ], 764 ],
@@ -592,7 +768,6 @@
592 "tags": [ 768 "tags": [
593 "users" 769 "users"
594 ], 770 ],
595 "summary": "Update profile page of session user.",
596 "parameters": [ 771 "parameters": [
597 { 772 {
598 "type": "string", 773 "type": "string",
@@ -636,15 +811,60 @@
636 } 811 }
637 } 812 }
638 }, 813 },
814 "/rankings": {
815 "get": {
816 "description": "Get rankings of every player.",
817 "produces": [
818 "application/json"
819 ],
820 "tags": [
821 "rankings"
822 ],
823 "responses": {
824 "200": {
825 "description": "OK",
826 "schema": {
827 "allOf": [
828 {
829 "$ref": "#/definitions/models.Response"
830 },
831 {
832 "type": "object",
833 "properties": {
834 "data": {
835 "$ref": "#/definitions/models.RankingsResponse"
836 }
837 }
838 }
839 ]
840 }
841 },
842 "400": {
843 "description": "Bad Request",
844 "schema": {
845 "$ref": "#/definitions/models.Response"
846 }
847 }
848 }
849 }
850 },
639 "/search": { 851 "/search": {
640 "get": { 852 "get": {
853 "description": "Get all user and map data matching to the query.",
641 "produces": [ 854 "produces": [
642 "application/json" 855 "application/json"
643 ], 856 ],
644 "tags": [ 857 "tags": [
645 "search" 858 "search"
646 ], 859 ],
647 "summary": "Get all user and map data.", 860 "parameters": [
861 {
862 "type": "string",
863 "description": "Search user or map name.",
864 "name": "q",
865 "in": "query"
866 }
867 ],
648 "responses": { 868 "responses": {
649 "200": { 869 "200": {
650 "description": "OK", 870 "description": "OK",
@@ -675,13 +895,13 @@
675 }, 895 },
676 "/token": { 896 "/token": {
677 "get": { 897 "get": {
898 "description": "Gets the token cookie value from the user.",
678 "produces": [ 899 "produces": [
679 "application/json" 900 "application/json"
680 ], 901 ],
681 "tags": [ 902 "tags": [
682 "auth" 903 "auth"
683 ], 904 ],
684 "summary": "Gets the token cookie value from the user.",
685 "responses": { 905 "responses": {
686 "200": { 906 "200": {
687 "description": "OK", 907 "description": "OK",
@@ -710,13 +930,13 @@
710 } 930 }
711 }, 931 },
712 "delete": { 932 "delete": {
933 "description": "Deletes the token cookie from the user.",
713 "produces": [ 934 "produces": [
714 "application/json" 935 "application/json"
715 ], 936 ],
716 "tags": [ 937 "tags": [
717 "auth" 938 "auth"
718 ], 939 ],
719 "summary": "Deletes the token cookie from the user.",
720 "responses": { 940 "responses": {
721 "200": { 941 "200": {
722 "description": "OK", 942 "description": "OK",
@@ -747,6 +967,7 @@
747 }, 967 },
748 "/users/{id}": { 968 "/users/{id}": {
749 "get": { 969 "get": {
970 "description": "Get profile page of another user.",
750 "consumes": [ 971 "consumes": [
751 "application/json" 972 "application/json"
752 ], 973 ],
@@ -756,7 +977,6 @@
756 "tags": [ 977 "tags": [
757 "users" 978 "users"
758 ], 979 ],
759 "summary": "Get profile page of another user.",
760 "parameters": [ 980 "parameters": [
761 { 981 {
762 "type": "integer", 982 "type": "integer",
@@ -802,6 +1022,17 @@
802 } 1022 }
803 }, 1023 },
804 "definitions": { 1024 "definitions": {
1025 "models.Category": {
1026 "type": "object",
1027 "properties": {
1028 "id": {
1029 "type": "integer"
1030 },
1031 "name": {
1032 "type": "string"
1033 }
1034 }
1035 },
805 "models.Chapter": { 1036 "models.Chapter": {
806 "type": "object", 1037 "type": "object",
807 "properties": { 1038 "properties": {
@@ -841,12 +1072,97 @@
841 } 1072 }
842 } 1073 }
843 }, 1074 },
1075 "models.CreateMapSummaryRequest": {
1076 "type": "object",
1077 "required": [
1078 "category_id",
1079 "description",
1080 "record_date",
1081 "score_count",
1082 "user_name"
1083 ],
1084 "properties": {
1085 "category_id": {
1086 "type": "integer"
1087 },
1088 "description": {
1089 "type": "string"
1090 },
1091 "record_date": {
1092 "type": "string"
1093 },
1094 "score_count": {
1095 "type": "integer"
1096 },
1097 "showcase": {
1098 "type": "string"
1099 },
1100 "user_name": {
1101 "type": "string"
1102 }
1103 }
1104 },
1105 "models.DeleteMapSummaryRequest": {
1106 "type": "object",
1107 "required": [
1108 "route_id"
1109 ],
1110 "properties": {
1111 "route_id": {
1112 "type": "integer"
1113 }
1114 }
1115 },
1116 "models.EditMapImageRequest": {
1117 "type": "object",
1118 "required": [
1119 "image"
1120 ],
1121 "properties": {
1122 "image": {
1123 "type": "string"
1124 }
1125 }
1126 },
1127 "models.EditMapSummaryRequest": {
1128 "type": "object",
1129 "required": [
1130 "description",
1131 "record_date",
1132 "route_id",
1133 "score_count",
1134 "user_name"
1135 ],
1136 "properties": {
1137 "description": {
1138 "type": "string"
1139 },
1140 "record_date": {
1141 "type": "string"
1142 },
1143 "route_id": {
1144 "type": "integer"
1145 },
1146 "score_count": {
1147 "type": "integer"
1148 },
1149 "showcase": {
1150 "type": "string"
1151 },
1152 "user_name": {
1153 "type": "string"
1154 }
1155 }
1156 },
844 "models.Game": { 1157 "models.Game": {
845 "type": "object", 1158 "type": "object",
846 "properties": { 1159 "properties": {
847 "id": { 1160 "id": {
848 "type": "integer" 1161 "type": "integer"
849 }, 1162 },
1163 "is_coop": {
1164 "type": "boolean"
1165 },
850 "name": { 1166 "name": {
851 "type": "string" 1167 "type": "string"
852 } 1168 }
@@ -866,32 +1182,20 @@
866 "chapter_name": { 1182 "chapter_name": {
867 "type": "string" 1183 "type": "string"
868 }, 1184 },
869 "data": {},
870 "game_name": { 1185 "game_name": {
871 "type": "string" 1186 "type": "string"
872 }, 1187 },
873 "id": { 1188 "id": {
874 "type": "integer" 1189 "type": "integer"
875 }, 1190 },
876 "map_name": { 1191 "image": {
877 "type": "string" 1192 "type": "string"
878 }
879 }
880 },
881 "models.MapCategoryScores": {
882 "type": "object",
883 "properties": {
884 "any": {
885 "type": "integer"
886 }, 1193 },
887 "cm": { 1194 "is_coop": {
888 "type": "integer" 1195 "type": "boolean"
889 },
890 "inbounds_sla": {
891 "type": "integer"
892 }, 1196 },
893 "no_sla": { 1197 "map_name": {
894 "type": "integer" 1198 "type": "string"
895 } 1199 }
896 } 1200 }
897 }, 1201 },
@@ -915,6 +1219,29 @@
915 "records": {} 1219 "records": {}
916 } 1220 }
917 }, 1221 },
1222 "models.MapRoute": {
1223 "type": "object",
1224 "properties": {
1225 "category": {
1226 "$ref": "#/definitions/models.Category"
1227 },
1228 "description": {
1229 "type": "string"
1230 },
1231 "history": {
1232 "$ref": "#/definitions/models.MapHistory"
1233 },
1234 "rating": {
1235 "type": "number"
1236 },
1237 "route_id": {
1238 "type": "integer"
1239 },
1240 "showcase": {
1241 "type": "string"
1242 }
1243 }
1244 },
918 "models.MapShort": { 1245 "models.MapShort": {
919 "type": "object", 1246 "type": "object",
920 "properties": { 1247 "properties": {
@@ -929,29 +1256,22 @@
929 "models.MapSummary": { 1256 "models.MapSummary": {
930 "type": "object", 1257 "type": "object",
931 "properties": { 1258 "properties": {
932 "category_scores": { 1259 "routes": {
933 "$ref": "#/definitions/models.MapCategoryScores"
934 },
935 "description": {
936 "type": "string"
937 },
938 "history": {
939 "type": "array",
940 "items": {
941 "$ref": "#/definitions/models.MapHistory"
942 }
943 },
944 "rating": {
945 "type": "number"
946 },
947 "routers": {
948 "type": "array", 1260 "type": "array",
949 "items": { 1261 "items": {
950 "type": "string" 1262 "$ref": "#/definitions/models.MapRoute"
951 } 1263 }
1264 }
1265 }
1266 },
1267 "models.MapSummaryResponse": {
1268 "type": "object",
1269 "properties": {
1270 "map": {
1271 "$ref": "#/definitions/models.Map"
952 }, 1272 },
953 "showcase": { 1273 "summary": {
954 "type": "string" 1274 "$ref": "#/definitions/models.MapSummary"
955 } 1275 }
956 } 1276 }
957 }, 1277 },
@@ -1004,21 +1324,9 @@
1004 } 1324 }
1005 } 1325 }
1006 }, 1326 },
1007 "models.RecordRequest": { 1327 "models.RecordResponse": {
1008 "type": "object", 1328 "type": "object",
1009 "required": [
1010 "is_partner_orange",
1011 "partner_id",
1012 "score_count",
1013 "score_time"
1014 ],
1015 "properties": { 1329 "properties": {
1016 "is_partner_orange": {
1017 "type": "boolean"
1018 },
1019 "partner_id": {
1020 "type": "string"
1021 },
1022 "score_count": { 1330 "score_count": {
1023 "type": "integer" 1331 "type": "integer"
1024 }, 1332 },
@@ -1054,29 +1362,13 @@
1054 "maps": { 1362 "maps": {
1055 "type": "array", 1363 "type": "array",
1056 "items": { 1364 "items": {
1057 "type": "object", 1365 "$ref": "#/definitions/models.MapShort"
1058 "properties": {
1059 "id": {
1060 "type": "integer"
1061 },
1062 "name": {
1063 "type": "string"
1064 }
1065 }
1066 } 1366 }
1067 }, 1367 },
1068 "players": { 1368 "players": {
1069 "type": "array", 1369 "type": "array",
1070 "items": { 1370 "items": {
1071 "type": "object", 1371 "$ref": "#/definitions/models.UserShort"
1072 "properties": {
1073 "steam_id": {
1074 "type": "string"
1075 },
1076 "user_name": {
1077 "type": "string"
1078 }
1079 }
1080 } 1372 }
1081 } 1373 }
1082 } 1374 }
@@ -1094,6 +1386,17 @@
1094 "type": "string" 1386 "type": "string"
1095 } 1387 }
1096 } 1388 }
1389 },
1390 "models.UserShort": {
1391 "type": "object",
1392 "properties": {
1393 "steam_id": {
1394 "type": "string"
1395 },
1396 "user_name": {
1397 "type": "string"
1398 }
1399 }
1097 } 1400 }
1098 } 1401 }
1099} \ No newline at end of file 1402} \ No newline at end of file
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index d62b46b..7571073 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -1,5 +1,12 @@
1basePath: /v1 1basePath: /v1
2definitions: 2definitions:
3 models.Category:
4 properties:
5 id:
6 type: integer
7 name:
8 type: string
9 type: object
3 models.Chapter: 10 models.Chapter:
4 properties: 11 properties:
5 id: 12 id:
@@ -25,10 +32,68 @@ definitions:
25 game: 32 game:
26 $ref: '#/definitions/models.Game' 33 $ref: '#/definitions/models.Game'
27 type: object 34 type: object
35 models.CreateMapSummaryRequest:
36 properties:
37 category_id:
38 type: integer
39 description:
40 type: string
41 record_date:
42 type: string
43 score_count:
44 type: integer
45 showcase:
46 type: string
47 user_name:
48 type: string
49 required:
50 - category_id
51 - description
52 - record_date
53 - score_count
54 - user_name
55 type: object
56 models.DeleteMapSummaryRequest:
57 properties:
58 route_id:
59 type: integer
60 required:
61 - route_id
62 type: object
63 models.EditMapImageRequest:
64 properties:
65 image:
66 type: string
67 required:
68 - image
69 type: object
70 models.EditMapSummaryRequest:
71 properties:
72 description:
73 type: string
74 record_date:
75 type: string
76 route_id:
77 type: integer
78 score_count:
79 type: integer
80 showcase:
81 type: string
82 user_name:
83 type: string
84 required:
85 - description
86 - record_date
87 - route_id
88 - score_count
89 - user_name
90 type: object
28 models.Game: 91 models.Game:
29 properties: 92 properties:
30 id: 93 id:
31 type: integer 94 type: integer
95 is_coop:
96 type: boolean
32 name: 97 name:
33 type: string 98 type: string
34 type: object 99 type: object
@@ -41,25 +106,17 @@ definitions:
41 properties: 106 properties:
42 chapter_name: 107 chapter_name:
43 type: string 108 type: string
44 data: {}
45 game_name: 109 game_name:
46 type: string 110 type: string
47 id: 111 id:
48 type: integer 112 type: integer
113 image:
114 type: string
115 is_coop:
116 type: boolean
49 map_name: 117 map_name:
50 type: string 118 type: string
51 type: object 119 type: object
52 models.MapCategoryScores:
53 properties:
54 any:
55 type: integer
56 cm:
57 type: integer
58 inbounds_sla:
59 type: integer
60 no_sla:
61 type: integer
62 type: object
63 models.MapHistory: 120 models.MapHistory:
64 properties: 121 properties:
65 date: 122 date:
@@ -73,6 +130,21 @@ definitions:
73 properties: 130 properties:
74 records: {} 131 records: {}
75 type: object 132 type: object
133 models.MapRoute:
134 properties:
135 category:
136 $ref: '#/definitions/models.Category'
137 description:
138 type: string
139 history:
140 $ref: '#/definitions/models.MapHistory'
141 rating:
142 type: number
143 route_id:
144 type: integer
145 showcase:
146 type: string
147 type: object
76 models.MapShort: 148 models.MapShort:
77 properties: 149 properties:
78 id: 150 id:
@@ -82,22 +154,17 @@ definitions:
82 type: object 154 type: object
83 models.MapSummary: 155 models.MapSummary:
84 properties: 156 properties:
85 category_scores: 157 routes:
86 $ref: '#/definitions/models.MapCategoryScores'
87 description:
88 type: string
89 history:
90 items:
91 $ref: '#/definitions/models.MapHistory'
92 type: array
93 rating:
94 type: number
95 routers:
96 items: 158 items:
97 type: string 159 $ref: '#/definitions/models.MapRoute'
98 type: array 160 type: array
99 showcase: 161 type: object
100 type: string 162 models.MapSummaryResponse:
163 properties:
164 map:
165 $ref: '#/definitions/models.Map'
166 summary:
167 $ref: '#/definitions/models.MapSummary'
101 type: object 168 type: object
102 models.ProfileResponse: 169 models.ProfileResponse:
103 properties: 170 properties:
@@ -131,21 +198,12 @@ definitions:
131 $ref: '#/definitions/models.UserRanking' 198 $ref: '#/definitions/models.UserRanking'
132 type: array 199 type: array
133 type: object 200 type: object
134 models.RecordRequest: 201 models.RecordResponse:
135 properties: 202 properties:
136 is_partner_orange:
137 type: boolean
138 partner_id:
139 type: string
140 score_count: 203 score_count:
141 type: integer 204 type: integer
142 score_time: 205 score_time:
143 type: integer 206 type: integer
144 required:
145 - is_partner_orange
146 - partner_id
147 - score_count
148 - score_time
149 type: object 207 type: object
150 models.Response: 208 models.Response:
151 properties: 209 properties:
@@ -165,21 +223,11 @@ definitions:
165 properties: 223 properties:
166 maps: 224 maps:
167 items: 225 items:
168 properties: 226 $ref: '#/definitions/models.MapShort'
169 id:
170 type: integer
171 name:
172 type: string
173 type: object
174 type: array 227 type: array
175 players: 228 players:
176 items: 229 items:
177 properties: 230 $ref: '#/definitions/models.UserShort'
178 steam_id:
179 type: string
180 user_name:
181 type: string
182 type: object
183 type: array 231 type: array
184 type: object 232 type: object
185 models.UserRanking: 233 models.UserRanking:
@@ -191,6 +239,13 @@ definitions:
191 user_name: 239 user_name:
192 type: string 240 type: string
193 type: object 241 type: object
242 models.UserShort:
243 properties:
244 steam_id:
245 type: string
246 user_name:
247 type: string
248 type: object
194host: lp.ardapektezol.com/api 249host: lp.ardapektezol.com/api
195info: 250info:
196 contact: {} 251 contact: {}
@@ -203,6 +258,7 @@ info:
203paths: 258paths:
204 /chapters/{id}: 259 /chapters/{id}:
205 get: 260 get:
261 description: Get maps from the specified chapter id.
206 parameters: 262 parameters:
207 - description: Chapter ID 263 - description: Chapter ID
208 in: path 264 in: path
@@ -225,40 +281,19 @@ paths:
225 description: Bad Request 281 description: Bad Request
226 schema: 282 schema:
227 $ref: '#/definitions/models.Response' 283 $ref: '#/definitions/models.Response'
228 summary: Get maps from the specified chapter id.
229 tags: 284 tags:
230 - games & chapters 285 - games & chapters
231 /demo:
232 get:
233 produces:
234 - application/json
235 responses:
236 "200":
237 description: OK
238 schema:
239 allOf:
240 - $ref: '#/definitions/models.Response'
241 - properties:
242 data:
243 $ref: '#/definitions/models.RankingsResponse'
244 type: object
245 "400":
246 description: Bad Request
247 schema:
248 $ref: '#/definitions/models.Response'
249 summary: Get rankings of every player.
250 tags:
251 - rankings
252 /demos: 286 /demos:
253 get: 287 get:
254 consumes: 288 consumes:
255 - application/json 289 - application/json
290 description: Get demo with specified demo uuid.
256 parameters: 291 parameters:
257 - description: Demo UUID 292 - description: Demo UUID
258 in: query 293 in: query
259 name: uuid 294 name: uuid
260 required: true 295 required: true
261 type: integer 296 type: string
262 produces: 297 produces:
263 - application/octet-stream 298 - application/octet-stream
264 responses: 299 responses:
@@ -270,11 +305,11 @@ paths:
270 description: Bad Request 305 description: Bad Request
271 schema: 306 schema:
272 $ref: '#/definitions/models.Response' 307 $ref: '#/definitions/models.Response'
273 summary: Get demo with specified demo uuid.
274 tags: 308 tags:
275 - demo 309 - demo
276 /games: 310 /games:
277 get: 311 get:
312 description: Get games from the leaderboards.
278 produces: 313 produces:
279 - application/json 314 - application/json
280 responses: 315 responses:
@@ -293,11 +328,11 @@ paths:
293 description: Bad Request 328 description: Bad Request
294 schema: 329 schema:
295 $ref: '#/definitions/models.Response' 330 $ref: '#/definitions/models.Response'
296 summary: Get games from the leaderboards.
297 tags: 331 tags:
298 - games & chapters 332 - games & chapters
299 /games/{id}: 333 /games/{id}:
300 get: 334 get:
335 description: Get chapters from the specified game id.
301 parameters: 336 parameters:
302 - description: Game ID 337 - description: Game ID
303 in: path 338 in: path
@@ -320,13 +355,13 @@ paths:
320 description: Bad Request 355 description: Bad Request
321 schema: 356 schema:
322 $ref: '#/definitions/models.Response' 357 $ref: '#/definitions/models.Response'
323 summary: Get chapters from the specified game id.
324 tags: 358 tags:
325 - games & chapters 359 - games & chapters
326 /login: 360 /login:
327 get: 361 get:
328 consumes: 362 consumes:
329 - application/json 363 - application/json
364 description: Get (redirect) login page for Steam auth.
330 produces: 365 produces:
331 - application/json 366 - application/json
332 responses: 367 responses:
@@ -343,11 +378,49 @@ paths:
343 description: Bad Request 378 description: Bad Request
344 schema: 379 schema:
345 $ref: '#/definitions/models.Response' 380 $ref: '#/definitions/models.Response'
346 summary: Get (redirect) login page for Steam auth.
347 tags: 381 tags:
348 - login 382 - login
383 /maps/{id}/image:
384 put:
385 description: Edit map image with specified map id.
386 parameters:
387 - description: JWT Token
388 in: header
389 name: Authorization
390 required: true
391 type: string
392 - description: Map ID
393 in: path
394 name: id
395 required: true
396 type: integer
397 - description: Body
398 in: body
399 name: request
400 required: true
401 schema:
402 $ref: '#/definitions/models.EditMapImageRequest'
403 produces:
404 - application/json
405 responses:
406 "200":
407 description: OK
408 schema:
409 allOf:
410 - $ref: '#/definitions/models.Response'
411 - properties:
412 data:
413 $ref: '#/definitions/models.EditMapImageRequest'
414 type: object
415 "400":
416 description: Bad Request
417 schema:
418 $ref: '#/definitions/models.Response'
419 tags:
420 - maps
349 /maps/{id}/leaderboards: 421 /maps/{id}/leaderboards:
350 get: 422 get:
423 description: Get map leaderboards with specified id.
351 parameters: 424 parameters:
352 - description: Map ID 425 - description: Map ID
353 in: path 426 in: path
@@ -375,45 +448,40 @@ paths:
375 description: Bad Request 448 description: Bad Request
376 schema: 449 schema:
377 $ref: '#/definitions/models.Response' 450 $ref: '#/definitions/models.Response'
378 summary: Get map leaderboards with specified id.
379 tags: 451 tags:
380 - maps 452 - maps
381 /maps/{id}/record: 453 /maps/{id}/record:
382 post: 454 post:
383 consumes: 455 consumes:
384 - multipart/form-data 456 - multipart/form-data
457 description: Post record with demo of a specific map.
385 parameters: 458 parameters:
459 - description: Map ID
460 in: path
461 name: id
462 required: true
463 type: integer
386 - description: JWT Token 464 - description: JWT Token
387 in: header 465 in: header
388 name: Authorization 466 name: Authorization
389 required: true 467 required: true
390 type: string 468 type: string
391 - description: Demos 469 - description: Host Demo
392 in: formData 470 in: formData
393 items: 471 name: host_demo
394 type: file
395 name: demos
396 required: true 472 required: true
397 type: array 473 type: file
398 - description: Score Count 474 - description: Partner Demo
399 in: formData 475 in: formData
400 name: score_count 476 name: partner_demo
401 required: true 477 type: file
402 type: integer
403 - description: Score Time
404 in: formData
405 name: score_time
406 required: true
407 type: integer
408 - description: Is Partner Orange 478 - description: Is Partner Orange
409 in: formData 479 in: formData
410 name: is_partner_orange 480 name: is_partner_orange
411 required: true
412 type: boolean 481 type: boolean
413 - description: Partner ID 482 - description: Partner ID
414 in: formData 483 in: formData
415 name: partner_id 484 name: partner_id
416 required: true
417 type: string 485 type: string
418 produces: 486 produces:
419 - application/json 487 - application/json
@@ -425,7 +493,7 @@ paths:
425 - $ref: '#/definitions/models.Response' 493 - $ref: '#/definitions/models.Response'
426 - properties: 494 - properties:
427 data: 495 data:
428 $ref: '#/definitions/models.RecordRequest' 496 $ref: '#/definitions/models.RecordResponse'
429 type: object 497 type: object
430 "400": 498 "400":
431 description: Bad Request 499 description: Bad Request
@@ -435,11 +503,48 @@ paths:
435 description: Unauthorized 503 description: Unauthorized
436 schema: 504 schema:
437 $ref: '#/definitions/models.Response' 505 $ref: '#/definitions/models.Response'
438 summary: Post record with demo of a specific map.
439 tags: 506 tags:
440 - maps 507 - maps
441 /maps/{id}/summary: 508 /maps/{id}/summary:
509 delete:
510 description: Delete map summary with specified map id.
511 parameters:
512 - description: JWT Token
513 in: header
514 name: Authorization
515 required: true
516 type: string
517 - description: Map ID
518 in: path
519 name: id
520 required: true
521 type: integer
522 - description: Body
523 in: body
524 name: request
525 required: true
526 schema:
527 $ref: '#/definitions/models.DeleteMapSummaryRequest'
528 produces:
529 - application/json
530 responses:
531 "200":
532 description: OK
533 schema:
534 allOf:
535 - $ref: '#/definitions/models.Response'
536 - properties:
537 data:
538 $ref: '#/definitions/models.DeleteMapSummaryRequest'
539 type: object
540 "400":
541 description: Bad Request
542 schema:
543 $ref: '#/definitions/models.Response'
544 tags:
545 - maps
442 get: 546 get:
547 description: Get map summary with specified id.
443 parameters: 548 parameters:
444 - description: Map ID 549 - description: Map ID
445 in: path 550 in: path
@@ -456,24 +561,93 @@ paths:
456 - $ref: '#/definitions/models.Response' 561 - $ref: '#/definitions/models.Response'
457 - properties: 562 - properties:
458 data: 563 data:
459 allOf: 564 $ref: '#/definitions/models.MapSummaryResponse'
460 - $ref: '#/definitions/models.Map' 565 type: object
461 - properties: 566 "400":
462 data: 567 description: Bad Request
463 $ref: '#/definitions/models.MapSummary' 568 schema:
464 type: object 569 $ref: '#/definitions/models.Response'
570 tags:
571 - maps
572 post:
573 description: Create map summary with specified map id.
574 parameters:
575 - description: JWT Token
576 in: header
577 name: Authorization
578 required: true
579 type: string
580 - description: Map ID
581 in: path
582 name: id
583 required: true
584 type: integer
585 - description: Body
586 in: body
587 name: request
588 required: true
589 schema:
590 $ref: '#/definitions/models.CreateMapSummaryRequest'
591 produces:
592 - application/json
593 responses:
594 "200":
595 description: OK
596 schema:
597 allOf:
598 - $ref: '#/definitions/models.Response'
599 - properties:
600 data:
601 $ref: '#/definitions/models.CreateMapSummaryRequest'
602 type: object
603 "400":
604 description: Bad Request
605 schema:
606 $ref: '#/definitions/models.Response'
607 tags:
608 - maps
609 put:
610 description: Edit map summary with specified map id.
611 parameters:
612 - description: JWT Token
613 in: header
614 name: Authorization
615 required: true
616 type: string
617 - description: Map ID
618 in: path
619 name: id
620 required: true
621 type: integer
622 - description: Body
623 in: body
624 name: request
625 required: true
626 schema:
627 $ref: '#/definitions/models.EditMapSummaryRequest'
628 produces:
629 - application/json
630 responses:
631 "200":
632 description: OK
633 schema:
634 allOf:
635 - $ref: '#/definitions/models.Response'
636 - properties:
637 data:
638 $ref: '#/definitions/models.EditMapSummaryRequest'
465 type: object 639 type: object
466 "400": 640 "400":
467 description: Bad Request 641 description: Bad Request
468 schema: 642 schema:
469 $ref: '#/definitions/models.Response' 643 $ref: '#/definitions/models.Response'
470 summary: Get map summary with specified id.
471 tags: 644 tags:
472 - maps 645 - maps
473 /profile: 646 /profile:
474 get: 647 get:
475 consumes: 648 consumes:
476 - application/json 649 - application/json
650 description: Get profile page of session user.
477 parameters: 651 parameters:
478 - description: JWT Token 652 - description: JWT Token
479 in: header 653 in: header
@@ -500,12 +674,12 @@ paths:
500 description: Unauthorized 674 description: Unauthorized
501 schema: 675 schema:
502 $ref: '#/definitions/models.Response' 676 $ref: '#/definitions/models.Response'
503 summary: Get profile page of session user.
504 tags: 677 tags:
505 - users 678 - users
506 post: 679 post:
507 consumes: 680 consumes:
508 - application/json 681 - application/json
682 description: Update profile page of session user.
509 parameters: 683 parameters:
510 - description: JWT Token 684 - description: JWT Token
511 in: header 685 in: header
@@ -532,12 +706,12 @@ paths:
532 description: Unauthorized 706 description: Unauthorized
533 schema: 707 schema:
534 $ref: '#/definitions/models.Response' 708 $ref: '#/definitions/models.Response'
535 summary: Update profile page of session user.
536 tags: 709 tags:
537 - users 710 - users
538 put: 711 put:
539 consumes: 712 consumes:
540 - application/json 713 - application/json
714 description: Update country code of session user.
541 parameters: 715 parameters:
542 - description: JWT Token 716 - description: JWT Token
543 in: header 717 in: header
@@ -555,12 +729,7 @@ paths:
555 "200": 729 "200":
556 description: OK 730 description: OK
557 schema: 731 schema:
558 allOf: 732 $ref: '#/definitions/models.Response'
559 - $ref: '#/definitions/models.Response'
560 - properties:
561 data:
562 $ref: '#/definitions/models.ProfileResponse'
563 type: object
564 "400": 733 "400":
565 description: Bad Request 734 description: Bad Request
566 schema: 735 schema:
@@ -569,11 +738,37 @@ paths:
569 description: Unauthorized 738 description: Unauthorized
570 schema: 739 schema:
571 $ref: '#/definitions/models.Response' 740 $ref: '#/definitions/models.Response'
572 summary: Update country code of session user.
573 tags: 741 tags:
574 - users 742 - users
743 /rankings:
744 get:
745 description: Get rankings of every player.
746 produces:
747 - application/json
748 responses:
749 "200":
750 description: OK
751 schema:
752 allOf:
753 - $ref: '#/definitions/models.Response'
754 - properties:
755 data:
756 $ref: '#/definitions/models.RankingsResponse'
757 type: object
758 "400":
759 description: Bad Request
760 schema:
761 $ref: '#/definitions/models.Response'
762 tags:
763 - rankings
575 /search: 764 /search:
576 get: 765 get:
766 description: Get all user and map data matching to the query.
767 parameters:
768 - description: Search user or map name.
769 in: query
770 name: q
771 type: string
577 produces: 772 produces:
578 - application/json 773 - application/json
579 responses: 774 responses:
@@ -590,11 +785,11 @@ paths:
590 description: Bad Request 785 description: Bad Request
591 schema: 786 schema:
592 $ref: '#/definitions/models.Response' 787 $ref: '#/definitions/models.Response'
593 summary: Get all user and map data.
594 tags: 788 tags:
595 - search 789 - search
596 /token: 790 /token:
597 delete: 791 delete:
792 description: Deletes the token cookie from the user.
598 produces: 793 produces:
599 - application/json 794 - application/json
600 responses: 795 responses:
@@ -611,10 +806,10 @@ paths:
611 description: Not Found 806 description: Not Found
612 schema: 807 schema:
613 $ref: '#/definitions/models.Response' 808 $ref: '#/definitions/models.Response'
614 summary: Deletes the token cookie from the user.
615 tags: 809 tags:
616 - auth 810 - auth
617 get: 811 get:
812 description: Gets the token cookie value from the user.
618 produces: 813 produces:
619 - application/json 814 - application/json
620 responses: 815 responses:
@@ -631,13 +826,13 @@ paths:
631 description: Not Found 826 description: Not Found
632 schema: 827 schema:
633 $ref: '#/definitions/models.Response' 828 $ref: '#/definitions/models.Response'
634 summary: Gets the token cookie value from the user.
635 tags: 829 tags:
636 - auth 830 - auth
637 /users/{id}: 831 /users/{id}:
638 get: 832 get:
639 consumes: 833 consumes:
640 - application/json 834 - application/json
835 description: Get profile page of another user.
641 parameters: 836 parameters:
642 - description: User ID 837 - description: User ID
643 in: path 838 in: path
@@ -664,7 +859,6 @@ paths:
664 description: Not Found 859 description: Not Found
665 schema: 860 schema:
666 $ref: '#/definitions/models.Response' 861 $ref: '#/definitions/models.Response'
667 summary: Get profile page of another user.
668 tags: 862 tags:
669 - users 863 - users
670swagger: "2.0" 864swagger: "2.0"
diff --git a/go.mod b/go.mod
index c0770da..312afcf 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,6 @@ module github.com/pektezol/leastportals
3go 1.19 3go 1.19
4 4
5require ( 5require (
6 github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19
7 github.com/gin-gonic/gin v1.8.1 6 github.com/gin-gonic/gin v1.8.1
8 github.com/joho/godotenv v1.4.0 7 github.com/joho/godotenv v1.4.0
9 github.com/solovev/steam_go v0.0.0-20170222182106-48eb5aae6c50 8 github.com/solovev/steam_go v0.0.0-20170222182106-48eb5aae6c50
diff --git a/go.sum b/go.sum
index fe4d065..f5b3907 100644
--- a/go.sum
+++ b/go.sum
@@ -26,8 +26,6 @@ github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d
26github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= 26github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
27github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 27github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
28github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 28github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
29github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19 h1:J2LPEOcQmWaooBnBtUDV9KHFEnP5LYTZY03GiQ0oQBw=
30github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
31github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= 29github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
32github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= 30github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
33github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 31github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
diff --git a/main.go b/main.go
index 991f3c0..8aaa80c 100644
--- a/main.go
+++ b/main.go
@@ -31,8 +31,6 @@ func main() {
31 } 31 }
32 router := gin.Default() 32 router := gin.Default()
33 database.ConnectDB() 33 database.ConnectDB()
34 // For frontend static serving - only for local debug
35 // router.Use(static.Serve("/", static.LocalFile("./frontend/build", true)))
36 routes.InitRoutes(router) 34 routes.InitRoutes(router)
37 router.Run(fmt.Sprintf(":%s", os.Getenv("PORT"))) 35 router.Run(fmt.Sprintf(":%s", os.Getenv("PORT")))
38} 36}