diff options
Diffstat (limited to 'backend/handlers/stats.go')
| -rw-r--r-- | backend/handlers/stats.go | 204 |
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 @@ | |||
| 1 | package handlers | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "net/http" | ||
| 5 | "time" | ||
| 6 | |||
| 7 | "lphub/database" | ||
| 8 | "lphub/models" | ||
| 9 | |||
| 10 | "github.com/gin-gonic/gin" | ||
| 11 | ) | ||
| 12 | |||
| 13 | type ScoresResponse struct { | ||
| 14 | Logs []ScoresDetails `json:"scores"` | ||
| 15 | } | ||
| 16 | |||
| 17 | type 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 | |||
| 27 | type TimelinePoint struct { | ||
| 28 | Date string `json:"date"` | ||
| 29 | Count int `json:"count"` | ||
| 30 | } | ||
| 31 | |||
| 32 | type 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] | ||
| 44 | func 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] | ||
| 103 | func 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 | } | ||