aboutsummaryrefslogtreecommitdiff
path: root/backend/handlers/stats.go
diff options
context:
space:
mode:
authorArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2025-10-27 22:12:31 +0300
committerArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2025-10-27 23:13:45 +0400
commit96f55dba15bcbba4e6d8f14035ecfd20ebcea8a8 (patch)
treed33156305b0e34e1c28e294e021447635dde3531 /backend/handlers/stats.go
parentfix/frontend: hide breakpoint in-between tablet and desktop on maps (#290) (diff)
downloadlphub-96f55dba15bcbba4e6d8f14035ecfd20ebcea8a8.tar.gz
lphub-96f55dba15bcbba4e6d8f14035ecfd20ebcea8a8.tar.bz2
lphub-96f55dba15bcbba4e6d8f14035ecfd20ebcea8a8.zip
feat/backend: timeline stats endpoint
Diffstat (limited to 'backend/handlers/stats.go')
-rw-r--r--backend/handlers/stats.go204
1 files changed, 204 insertions, 0 deletions
diff --git a/backend/handlers/stats.go b/backend/handlers/stats.go
new file mode 100644
index 0000000..8fe98f9
--- /dev/null
+++ b/backend/handlers/stats.go
@@ -0,0 +1,204 @@
1package handlers
2
3import (
4 "net/http"
5 "time"
6
7 "lphub/database"
8 "lphub/models"
9
10 "github.com/gin-gonic/gin"
11)
12
13type ScoresResponse struct {
14 Logs []ScoresDetails `json:"scores"`
15}
16
17type ScoresDetails struct {
18 Game models.Game `json:"game"`
19 User models.UserShort `json:"user"`
20 Map models.MapShort `json:"map"`
21 ScoreCount int `json:"score_count"`
22 ScoreTime int `json:"score_time"`
23 DemoID string `json:"demo_id"`
24 Date time.Time `json:"date"`
25}
26
27type TimelinePoint struct {
28 Date string `json:"date"`
29 Count int `json:"count"`
30}
31
32type TimelineResponse struct {
33 Singleplayer []TimelinePoint `json:"timeline_singleplayer"`
34 Multiplayer []TimelinePoint `json:"timeline_multiplayer"`
35}
36
37// GET Scores
38//
39// @Description Get score logs of every player.
40// @Tags stats
41// @Produce json
42// @Success 200 {object} models.Response{data=ScoresResponse}
43// @Router /stats/scores [get]
44func Scores(c *gin.Context) {
45 response := ScoresResponse{Logs: []ScoresDetails{}}
46 sql := `SELECT g.id,
47 g."name",
48 g.is_coop,
49 rs.map_id,
50 m.name AS map_name,
51 u.steam_id,
52 u.user_name,
53 rs.score_count,
54 rs.score_time,
55 rs.demo_id,
56 rs.record_date
57 FROM (
58 SELECT id, map_id, user_id, score_count, score_time, demo_id, record_date
59 FROM records_sp WHERE is_deleted = false
60
61 UNION ALL
62
63 SELECT id, map_id, host_id AS user_id, score_count, score_time, host_demo_id AS demo_id, record_date
64 FROM records_mp WHERE is_deleted = false
65
66 UNION ALL
67
68 SELECT id, map_id, partner_id AS user_id, score_count, score_time, partner_demo_id AS demo_id, record_date
69 FROM records_mp WHERE is_deleted = false
70 ) AS rs
71 JOIN users u ON rs.user_id = u.steam_id
72 JOIN maps m ON rs.map_id = m.id
73 JOIN games g ON m.game_id = g.id
74 ORDER BY rs.record_date DESC LIMIT 100;`
75 rows, err := database.DB.Query(sql)
76 if err != nil {
77 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
78 return
79 }
80 for rows.Next() {
81 score := ScoresDetails{}
82 err = rows.Scan(&score.Game.ID, &score.Game.Name, &score.Game.IsCoop, &score.Map.ID, &score.Map.Name, &score.User.SteamID, &score.User.UserName, &score.ScoreCount, &score.ScoreTime, &score.DemoID, &score.Date)
83 if err != nil {
84 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
85 return
86 }
87 response.Logs = append(response.Logs, score)
88 }
89 c.JSON(http.StatusOK, models.Response{
90 Success: true,
91 Message: "Successfully retrieved score logs.",
92 Data: response,
93 })
94}
95
96// GET Timeline
97//
98// @Description Get the history of portal count world records over time.
99// @Tags stats
100// @Produce json
101// @Success 200 {object} models.Response{data=TimelineResponse}
102// @Router /stats/timeline [get]
103func Timeline(c *gin.Context) {
104 result := TimelineResponse{
105 Singleplayer: []TimelinePoint{},
106 Multiplayer: []TimelinePoint{},
107 }
108 spQuery := `
109 WITH date_series AS (
110 SELECT DISTINCT record_date as date
111 FROM map_history
112 WHERE category_id = 1 AND map_id <= 60 AND record_date >= '2013-01-31'
113 ORDER BY record_date
114 ),
115 map_best_at_date AS (
116 SELECT
117 ds.date,
118 mh.map_id,
119 MIN(mh.score_count) as best_count
120 FROM date_series ds
121 CROSS JOIN (SELECT DISTINCT map_id FROM map_history WHERE category_id = 1 AND map_id <= 60) maps
122 LEFT JOIN map_history mh ON mh.map_id = maps.map_id
123 AND mh.category_id = 1
124 AND mh.record_date <= ds.date
125 GROUP BY ds.date, mh.map_id
126 )
127 SELECT
128 date,
129 SUM(best_count) as total_count
130 FROM map_best_at_date
131 GROUP BY date
132 ORDER BY date ASC;
133 `
134
135 mpQuery := `
136 WITH date_series AS (
137 SELECT DISTINCT record_date as date
138 FROM map_history
139 WHERE category_id = 1 AND map_id > 60 AND record_date >= '2011-12-21'
140 ORDER BY record_date
141 ),
142 map_best_at_date AS (
143 SELECT
144 ds.date,
145 mh.map_id,
146 MIN(mh.score_count) as best_count
147 FROM date_series ds
148 CROSS JOIN (SELECT DISTINCT map_id FROM map_history WHERE category_id = 1 AND map_id > 60) maps
149 LEFT JOIN map_history mh ON mh.map_id = maps.map_id
150 AND mh.category_id = 1
151 AND mh.record_date <= ds.date
152 GROUP BY ds.date, mh.map_id
153 )
154 SELECT
155 date,
156 SUM(best_count) as total_count
157 FROM map_best_at_date
158 GROUP BY date
159 ORDER BY date ASC;
160 `
161
162 rows, err := database.DB.Query(spQuery)
163 if err != nil {
164 return
165 }
166 defer rows.Close()
167
168 for rows.Next() {
169 var dateTime time.Time
170 var count int
171 if err := rows.Scan(&dateTime, &count); err != nil {
172 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
173 return
174 }
175 result.Singleplayer = append(result.Singleplayer, TimelinePoint{
176 Date: dateTime.Format("2006-01-02"),
177 Count: count,
178 })
179 }
180
181 rows, err = database.DB.Query(mpQuery)
182 if err != nil {
183 return
184 }
185 defer rows.Close()
186
187 for rows.Next() {
188 var dateTime time.Time
189 var count int
190 if err := rows.Scan(&dateTime, &count); err != nil {
191 c.JSON(http.StatusOK, models.ErrorResponse(err.Error()))
192 return
193 }
194 result.Multiplayer = append(result.Multiplayer, TimelinePoint{
195 Date: dateTime.Format("2006-01-02"),
196 Count: count,
197 })
198 }
199 c.JSON(http.StatusOK, models.Response{
200 Success: true,
201 Message: "Successfully retrieved portal count timeline.",
202 Data: result,
203 })
204}