aboutsummaryrefslogtreecommitdiff
path: root/backend/handlers/home.go
diff options
context:
space:
mode:
Diffstat (limited to 'backend/handlers/home.go')
-rw-r--r--backend/handlers/home.go289
1 files changed, 289 insertions, 0 deletions
diff --git a/backend/handlers/home.go b/backend/handlers/home.go
new file mode 100644
index 0000000..2095a74
--- /dev/null
+++ b/backend/handlers/home.go
@@ -0,0 +1,289 @@
1package handlers
2
3import (
4 "log"
5 "net/http"
6 "sort"
7 "strings"
8
9 "github.com/gin-gonic/gin"
10 "github.com/pektezol/leastportalshub/backend/database"
11 "github.com/pektezol/leastportalshub/backend/models"
12)
13
14type SearchResponse struct {
15 Players []models.UserShort `json:"players"`
16 Maps []models.MapShort `json:"maps"`
17}
18
19type RankingsResponse struct {
20 Overall []models.UserRanking `json:"rankings_overall"`
21 Singleplayer []models.UserRanking `json:"rankings_singleplayer"`
22 Multiplayer []models.UserRanking `json:"rankings_multiplayer"`
23}
24
25// GET Rankings
26//
27// @Description Get rankings of every player.
28// @Tags rankings
29// @Produce json
30// @Success 200 {object} models.Response{data=RankingsResponse}
31// @Failure 400 {object} models.Response
32// @Router /rankings [get]
33func Rankings(c *gin.Context) {
34 response := RankingsResponse{
35 Overall: []models.UserRanking{},
36 Singleplayer: []models.UserRanking{},
37 Multiplayer: []models.UserRanking{},
38 }
39 // Singleplayer rankings
40 sql := `SELECT u.steam_id, u.user_name, COUNT(DISTINCT map_id),
41 (SELECT COUNT(maps.name) FROM maps INNER JOIN games g ON maps.game_id = g.id WHERE g.is_coop = FALSE AND is_disabled = false),
42 (SELECT SUM(min_score_count) AS total_min_score_count FROM (
43 SELECT
44 user_id,
45 MIN(score_count) AS min_score_count
46 FROM records_sp
47 GROUP BY user_id, map_id
48 ) AS subquery
49 WHERE user_id = u.steam_id)
50 FROM records_sp sp JOIN users u ON u.steam_id = sp.user_id GROUP BY u.steam_id, u.user_name`
51 rows, err := database.DB.Query(sql)
52 if err != nil {
53 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
54 return
55 }
56 for rows.Next() {
57 ranking := models.UserRanking{}
58 var currentCount int
59 var totalCount int
60 err = rows.Scan(&ranking.User.SteamID, &ranking.User.UserName, &currentCount, &totalCount, &ranking.TotalScore)
61 if err != nil {
62 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
63 return
64 }
65 if currentCount != totalCount {
66 continue
67 }
68 response.Singleplayer = append(response.Singleplayer, ranking)
69 }
70 // Multiplayer rankings
71 sql = `SELECT u.steam_id, u.user_name, COUNT(DISTINCT map_id),
72 (SELECT COUNT(maps.name) FROM maps INNER JOIN games g ON maps.game_id = g.id WHERE g.is_coop = FALSE AND is_disabled = false),
73 (SELECT SUM(min_score_count) AS total_min_score_count FROM (
74 SELECT
75 host_id,
76 partner_id,
77 MIN(score_count) AS min_score_count
78 FROM records_mp
79 GROUP BY host_id, partner_id, map_id
80 ) AS subquery
81 WHERE host_id = u.steam_id OR partner_id = u.steam_id)
82 FROM records_mp mp JOIN users u ON u.steam_id = mp.host_id OR u.steam_id = mp.partner_id GROUP BY u.steam_id, u.user_name`
83 rows, err = database.DB.Query(sql)
84 if err != nil {
85 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
86 return
87 }
88 for rows.Next() {
89 ranking := models.UserRanking{}
90 var currentCount int
91 var totalCount int
92 err = rows.Scan(&ranking.User.SteamID, &ranking.User.UserName, &currentCount, &totalCount, &ranking.TotalScore)
93 if err != nil {
94 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
95 return
96 }
97 if currentCount != totalCount {
98 continue
99 }
100 response.Multiplayer = append(response.Multiplayer, ranking)
101 }
102 // Has both so they are qualified for overall ranking
103 for _, spRanking := range response.Singleplayer {
104 for _, mpRanking := range response.Multiplayer {
105 if spRanking.User.SteamID == mpRanking.User.SteamID {
106 totalScore := spRanking.TotalScore + mpRanking.TotalScore
107 overallRanking := models.UserRanking{
108 User: spRanking.User,
109 TotalScore: totalScore,
110 }
111 response.Overall = append(response.Overall, overallRanking)
112 }
113 }
114 }
115 sort.Slice(response.Singleplayer, func(i, j int) bool {
116 return response.Singleplayer[i].TotalScore < response.Singleplayer[j].TotalScore
117 })
118 sort.Slice(response.Multiplayer, func(i, j int) bool {
119 return response.Multiplayer[i].TotalScore < response.Multiplayer[j].TotalScore
120 })
121 sort.Slice(response.Overall, func(i, j int) bool {
122 return response.Overall[i].TotalScore < response.Overall[j].TotalScore
123 })
124 c.JSON(http.StatusOK, models.Response{
125 Success: true,
126 Message: "Successfully retrieved rankings.",
127 Data: response,
128 })
129}
130
131// GET Search With Query
132//
133// @Description Get all user and map data matching to the query.
134// @Tags search
135// @Produce json
136// @Param q query string false "Search user or map name."
137// @Success 200 {object} models.Response{data=SearchResponse}
138// @Failure 400 {object} models.Response
139// @Router /search [get]
140func SearchWithQuery(c *gin.Context) {
141 query := c.Query("q")
142 query = strings.ToLower(query)
143 log.Println(query)
144 var response SearchResponse
145 // Cache all maps for faster response
146 var maps = []models.MapShort{
147 {ID: 1, Name: "Container Ride"},
148 {ID: 2, Name: "Portal Carousel"},
149 {ID: 3, Name: "Portal Gun"},
150 {ID: 4, Name: "Smooth Jazz"},
151 {ID: 5, Name: "Cube Momentum"},
152 {ID: 6, Name: "Future Starter"},
153 {ID: 7, Name: "Secret Panel"},
154 {ID: 8, Name: "Wakeup"},
155 {ID: 9, Name: "Incinerator"},
156 {ID: 10, Name: "Laser Intro"},
157 {ID: 11, Name: "Laser Stairs"},
158 {ID: 12, Name: "Dual Lasers"},
159 {ID: 13, Name: "Laser Over Goo"},
160 {ID: 14, Name: "Catapult Intro"},
161 {ID: 15, Name: "Trust Fling"},
162 {ID: 16, Name: "Pit Flings"},
163 {ID: 17, Name: "Fizzler Intro"},
164 {ID: 18, Name: "Ceiling Catapult"},
165 {ID: 19, Name: "Ricochet"},
166 {ID: 20, Name: "Bridge Intro"},
167 {ID: 21, Name: "Bridge The Gap"},
168 {ID: 22, Name: "Turret Intro"},
169 {ID: 23, Name: "Laser Relays"},
170 {ID: 24, Name: "Turret Blocker"},
171 {ID: 25, Name: "Laser vs Turret"},
172 {ID: 26, Name: "Pull The Rug"},
173 {ID: 27, Name: "Column Blocker"},
174 {ID: 28, Name: "Laser Chaining"},
175 {ID: 29, Name: "Triple Laser"},
176 {ID: 30, Name: "Jail Break"},
177 {ID: 31, Name: "Escape"},
178 {ID: 32, Name: "Turret Factory"},
179 {ID: 33, Name: "Turret Sabotage"},
180 {ID: 34, Name: "Neurotoxin Sabotage"},
181 {ID: 35, Name: "Core"},
182 {ID: 36, Name: "Underground"},
183 {ID: 37, Name: "Cave Johnson"},
184 {ID: 38, Name: "Repulsion Intro"},
185 {ID: 39, Name: "Bomb Flings"},
186 {ID: 40, Name: "Crazy Box"},
187 {ID: 41, Name: "PotatOS"},
188 {ID: 42, Name: "Propulsion Intro"},
189 {ID: 43, Name: "Propulsion Flings"},
190 {ID: 44, Name: "Conversion Intro"},
191 {ID: 45, Name: "Three Gels"},
192 {ID: 46, Name: "Test"},
193 {ID: 47, Name: "Funnel Intro"},
194 {ID: 48, Name: "Ceiling Button"},
195 {ID: 49, Name: "Wall Button"},
196 {ID: 50, Name: "Polarity"},
197 {ID: 51, Name: "Funnel Catch"},
198 {ID: 52, Name: "Stop The Box"},
199 {ID: 53, Name: "Laser Catapult"},
200 {ID: 54, Name: "Laser Platform"},
201 {ID: 55, Name: "Propulsion Catch"},
202 {ID: 56, Name: "Repulsion Polarity"},
203 {ID: 57, Name: "Finale 1"},
204 {ID: 58, Name: "Finale 2"},
205 {ID: 59, Name: "Finale 3"},
206 {ID: 60, Name: "Finale 4"},
207 {ID: 61, Name: "Calibration"},
208 {ID: 62, Name: "Hub"},
209 {ID: 63, Name: "Doors"},
210 {ID: 64, Name: "Buttons"},
211 {ID: 65, Name: "Lasers"},
212 {ID: 66, Name: "Rat Maze"},
213 {ID: 67, Name: "Laser Crusher"},
214 {ID: 68, Name: "Behind The Scenes"},
215 {ID: 69, Name: "Flings"},
216 {ID: 70, Name: "Infinifling"},
217 {ID: 71, Name: "Team Retrieval"},
218 {ID: 72, Name: "Vertical Flings"},
219 {ID: 73, Name: "Catapults"},
220 {ID: 74, Name: "Multifling"},
221 {ID: 75, Name: "Fling Crushers"},
222 {ID: 76, Name: "Industrial Fan"},
223 {ID: 77, Name: "Cooperative Bridges"},
224 {ID: 78, Name: "Bridge Swap"},
225 {ID: 79, Name: "Fling Block"},
226 {ID: 80, Name: "Catapult Block"},
227 {ID: 81, Name: "Bridge Fling"},
228 {ID: 82, Name: "Turret Walls"},
229 {ID: 83, Name: "Turret Assasin"},
230 {ID: 84, Name: "Bridge Testing"},
231 {ID: 85, Name: "Cooperative Funnels"},
232 {ID: 86, Name: "Funnel Drill"},
233 {ID: 87, Name: "Funnel Catch"},
234 {ID: 88, Name: "Funnel Laser"},
235 {ID: 89, Name: "Cooperative Polarity"},
236 {ID: 90, Name: "Funnel Hop"},
237 {ID: 91, Name: "Advanced Polarity"},
238 {ID: 92, Name: "Funnel Maze"},
239 {ID: 93, Name: "Turret Warehouse"},
240 {ID: 94, Name: "Repulsion Jumps"},
241 {ID: 95, Name: "Double Bounce"},
242 {ID: 96, Name: "Bridge Repulsion"},
243 {ID: 97, Name: "Wall Repulsion"},
244 {ID: 98, Name: "Propulsion Crushers"},
245 {ID: 99, Name: "Turret Ninja"},
246 {ID: 100, Name: "Propulsion Retrieval"},
247 {ID: 101, Name: "Vault Entrance"},
248 {ID: 102, Name: "Seperation"},
249 {ID: 103, Name: "Triple Axis"},
250 {ID: 104, Name: "Catapult Catch"},
251 {ID: 105, Name: "Bridge Gels"},
252 {ID: 106, Name: "Maintenance"},
253 {ID: 107, Name: "Bridge Catch"},
254 {ID: 108, Name: "Double Lift"},
255 {ID: 109, Name: "Gel Maze"},
256 {ID: 110, Name: "Crazier Box"},
257 }
258 var filteredMaps []models.MapShort
259 for _, m := range maps {
260 if strings.Contains(strings.ToLower(m.Name), strings.ToLower(query)) {
261 filteredMaps = append(filteredMaps, m)
262 }
263 }
264 response.Maps = filteredMaps
265 if len(response.Maps) == 0 {
266 response.Maps = []models.MapShort{}
267 }
268 rows, err := database.DB.Query("SELECT steam_id, user_name FROM users WHERE lower(user_name) LIKE $1", "%"+query+"%")
269 if err != nil {
270 log.Fatal(err)
271 }
272 defer rows.Close()
273 for rows.Next() {
274 var user models.UserShort
275 if err := rows.Scan(&user.SteamID, &user.UserName); err != nil {
276 c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error()))
277 return
278 }
279 response.Players = append(response.Players, user)
280 }
281 if len(response.Players) == 0 {
282 response.Players = []models.UserShort{}
283 }
284 c.JSON(http.StatusOK, models.Response{
285 Success: true,
286 Message: "Search successfully retrieved.",
287 Data: response,
288 })
289}