From 96f55dba15bcbba4e6d8f14035ecfd20ebcea8a8 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:12:31 +0300 Subject: feat/backend: timeline stats endpoint --- backend/api/routes.go | 6 +- backend/docs/docs.go | 148 ++++++++++++++++++++++---------- backend/docs/swagger.json | 148 ++++++++++++++++++++++---------- backend/docs/swagger.yaml | 87 +++++++++++++------ backend/go.mod | 41 +++++---- backend/go.sum | 90 ++++++++++---------- backend/handlers/logs.go | 103 ----------------------- backend/handlers/record.go | 10 +-- backend/handlers/stats.go | 204 +++++++++++++++++++++++++++++++++++++++++++++ backend/main.go | 36 +++----- 10 files changed, 561 insertions(+), 312 deletions(-) delete mode 100644 backend/handlers/logs.go create mode 100644 backend/handlers/stats.go diff --git a/backend/api/routes.go b/backend/api/routes.go index 690f844..ab6e704 100644 --- a/backend/api/routes.go +++ b/backend/api/routes.go @@ -54,8 +54,8 @@ func InitRoutes(router *gin.Engine) { v1.GET("/games/:gameid", RateLimit, handlers.FetchChapters) v1.GET("/chapters/:chapterid", RateLimit, handlers.FetchChapterMaps) v1.GET("/games/:gameid/maps", RateLimit, handlers.FetchMaps) - // Logs - v1.GET("/logs/score", RateLimit, handlers.ScoreLogs) - // v1.GET("/logs/mod", IsAuthenticated, handlers.ModLogs) + // Stats + v1.GET("/stats/timeline", RateLimit, handlers.Timeline) + v1.GET("/stats/scores", RateLimit, handlers.Scores) } } diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 44d53e1..7ce9a8b 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -250,37 +250,6 @@ const docTemplate = `{ } } }, - "/logs/score": { - "get": { - "description": "Get score logs of every player.", - "produces": [ - "application/json" - ], - "tags": [ - "logs" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/models.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/handlers.ScoreLogsResponse" - } - } - } - ] - } - } - } - } - }, "/maps/{mapid}/discussions": { "get": { "description": "Get map discussions with specified map id.", @@ -1220,6 +1189,68 @@ const docTemplate = `{ } } }, + "/stats/scores": { + "get": { + "description": "Get score logs of every player.", + "produces": [ + "application/json" + ], + "tags": [ + "stats" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.ScoresResponse" + } + } + } + ] + } + } + } + } + }, + "/stats/timeline": { + "get": { + "description": "Get the history of portal count world records over time.", + "produces": [ + "application/json" + ], + "tags": [ + "stats" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.TimelineResponse" + } + } + } + ] + } + } + } + } + }, "/token": { "get": { "description": "Gets the token cookie value from the user.", @@ -1774,18 +1805,7 @@ const docTemplate = `{ } } }, - "handlers.ScoreLogsResponse": { - "type": "object", - "properties": { - "scores": { - "type": "array", - "items": { - "$ref": "#/definitions/handlers.ScoreLogsResponseDetails" - } - } - } - }, - "handlers.ScoreLogsResponseDetails": { + "handlers.ScoresDetails": { "type": "object", "properties": { "date": { @@ -1811,6 +1831,17 @@ const docTemplate = `{ } } }, + "handlers.ScoresResponse": { + "type": "object", + "properties": { + "scores": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.ScoresDetails" + } + } + } + }, "handlers.SearchResponse": { "type": "object", "properties": { @@ -1860,6 +1891,34 @@ const docTemplate = `{ } } }, + "handlers.TimelinePoint": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + }, + "handlers.TimelineResponse": { + "type": "object", + "properties": { + "timeline_multiplayer": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.TimelinePoint" + } + }, + "timeline_singleplayer": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.TimelinePoint" + } + } + } + }, "models.Category": { "type": "object", "properties": { @@ -1945,6 +2004,9 @@ const docTemplate = `{ "chapter_name": { "type": "string" }, + "difficulty": { + "type": "integer" + }, "game_name": { "type": "string" }, diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 6c10cfc..981d017 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -244,37 +244,6 @@ } } }, - "/logs/score": { - "get": { - "description": "Get score logs of every player.", - "produces": [ - "application/json" - ], - "tags": [ - "logs" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/models.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/handlers.ScoreLogsResponse" - } - } - } - ] - } - } - } - } - }, "/maps/{mapid}/discussions": { "get": { "description": "Get map discussions with specified map id.", @@ -1214,6 +1183,68 @@ } } }, + "/stats/scores": { + "get": { + "description": "Get score logs of every player.", + "produces": [ + "application/json" + ], + "tags": [ + "stats" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.ScoresResponse" + } + } + } + ] + } + } + } + } + }, + "/stats/timeline": { + "get": { + "description": "Get the history of portal count world records over time.", + "produces": [ + "application/json" + ], + "tags": [ + "stats" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handlers.TimelineResponse" + } + } + } + ] + } + } + } + } + }, "/token": { "get": { "description": "Gets the token cookie value from the user.", @@ -1768,18 +1799,7 @@ } } }, - "handlers.ScoreLogsResponse": { - "type": "object", - "properties": { - "scores": { - "type": "array", - "items": { - "$ref": "#/definitions/handlers.ScoreLogsResponseDetails" - } - } - } - }, - "handlers.ScoreLogsResponseDetails": { + "handlers.ScoresDetails": { "type": "object", "properties": { "date": { @@ -1805,6 +1825,17 @@ } } }, + "handlers.ScoresResponse": { + "type": "object", + "properties": { + "scores": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.ScoresDetails" + } + } + } + }, "handlers.SearchResponse": { "type": "object", "properties": { @@ -1854,6 +1885,34 @@ } } }, + "handlers.TimelinePoint": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "string" + } + } + }, + "handlers.TimelineResponse": { + "type": "object", + "properties": { + "timeline_multiplayer": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.TimelinePoint" + } + }, + "timeline_singleplayer": { + "type": "array", + "items": { + "$ref": "#/definitions/handlers.TimelinePoint" + } + } + } + }, "models.Category": { "type": "object", "properties": { @@ -1939,6 +1998,9 @@ "chapter_name": { "type": "string" }, + "difficulty": { + "type": "integer" + }, "game_name": { "type": "string" }, diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 8f33b94..0b3258c 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -287,14 +287,7 @@ definitions: score_time: type: integer type: object - handlers.ScoreLogsResponse: - properties: - scores: - items: - $ref: '#/definitions/handlers.ScoreLogsResponseDetails' - type: array - type: object - handlers.ScoreLogsResponseDetails: + handlers.ScoresDetails: properties: date: type: string @@ -311,6 +304,13 @@ definitions: user: $ref: '#/definitions/models.UserShort' type: object + handlers.ScoresResponse: + properties: + scores: + items: + $ref: '#/definitions/handlers.ScoresDetails' + type: array + type: object handlers.SearchResponse: properties: maps: @@ -343,6 +343,24 @@ definitions: user_name: type: string type: object + handlers.TimelinePoint: + properties: + count: + type: integer + date: + type: string + type: object + handlers.TimelineResponse: + properties: + timeline_multiplayer: + items: + $ref: '#/definitions/handlers.TimelinePoint' + type: array + timeline_singleplayer: + items: + $ref: '#/definitions/handlers.TimelinePoint' + type: array + type: object models.Category: properties: id: @@ -398,6 +416,8 @@ definitions: properties: chapter_name: type: string + difficulty: + type: integer game_name: type: string id: @@ -672,23 +692,6 @@ paths: type: object tags: - login - /logs/score: - get: - description: Get score logs of every player. - produces: - - application/json - responses: - "200": - description: OK - schema: - allOf: - - $ref: '#/definitions/models.Response' - - properties: - data: - $ref: '#/definitions/handlers.ScoreLogsResponse' - type: object - tags: - - logs /maps/{mapid}/discussions: get: description: Get map discussions with specified map id. @@ -1259,6 +1262,40 @@ paths: type: object tags: - search + /stats/scores: + get: + description: Get score logs of every player. + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + $ref: '#/definitions/handlers.ScoresResponse' + type: object + tags: + - stats + /stats/timeline: + get: + description: Get the history of portal count world records over time. + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + $ref: '#/definitions/handlers.TimelineResponse' + type: object + tags: + - stats /token: delete: description: Deletes the token cookie from the user. diff --git a/backend/go.mod b/backend/go.mod index f9fe0db..df904e8 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -3,7 +3,7 @@ module lphub go 1.23.0 require ( - github.com/gin-gonic/gin v1.10.0 + github.com/gin-gonic/gin v1.10.1 github.com/joho/godotenv v1.5.1 ) @@ -11,6 +11,8 @@ require ( github.com/Backblaze/blazer v0.7.1 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/uuid v1.6.0 + github.com/newrelic/go-agent/v3 v3.40.1 + github.com/newrelic/go-agent/v3/integrations/nrgin v1.4.1 github.com/pektezol/steam_go v1.1.2 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 @@ -20,34 +22,31 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/bytedance/sonic v1.12.2 // indirect - github.com/bytedance/sonic/loader v0.2.0 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/bytedance/sonic v1.13.3 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/newrelic/go-agent/v3 v3.40.1 // indirect - github.com/newrelic/go-agent/v3/integrations/nrgin v1.4.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.10.0 // indirect - golang.org/x/tools v0.25.0 // indirect + golang.org/x/arch v0.18.0 // indirect + golang.org/x/tools v0.33.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect google.golang.org/grpc v1.65.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( - github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.1 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 @@ -55,11 +54,11 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pektezol/bitreader v1.4.3 - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index f655023..b88dd43 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -2,26 +2,25 @@ github.com/Backblaze/blazer v0.7.1 h1:J43PbFj6hXLg1jvCNr+rQoAsxzKK0IP7ftl1ReCwpc github.com/Backblaze/blazer v0.7.1/go.mod h1:MhntL1nMpIuoqrPP6TnZu/xTydMgOAe/Xm6KongbjKs= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg= -github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0= +github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= -github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= +github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= -github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -36,14 +35,14 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -54,8 +53,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -82,8 +81,8 @@ github.com/pektezol/bitreader v1.4.3 h1:+WjsD6qOAaI6Q1jOOlEJcnaEso8vPMKRZnnaDnZh github.com/pektezol/bitreader v1.4.3/go.mod h1:xBQEsQpOf8B5yPrnOTkirZGyVUV6Bqp0ups2RIlTskk= github.com/pektezol/steam_go v1.1.2 h1:fta6SW+La8NfmCtR/Kn73bAmTBvCgUkkLCplsJGzx7g= github.com/pektezol/steam_go v1.1.2/go.mod h1:8dk95CLOQKRr0BA8ChnNbTEe0/f2Ibi5O4rmpS9oZCo= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -96,8 +95,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= @@ -106,28 +105,28 @@ github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= -golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc= +golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -135,8 +134,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -144,23 +143,22 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/backend/handlers/logs.go b/backend/handlers/logs.go deleted file mode 100644 index 693c448..0000000 --- a/backend/handlers/logs.go +++ /dev/null @@ -1,103 +0,0 @@ -package handlers - -import ( - "net/http" - "time" - - "lphub/database" - "lphub/models" - - "github.com/gin-gonic/gin" -) - -type Log struct { - User models.UserShort `json:"user"` - Type string `json:"type"` - Description string `json:"description"` - Message string `json:"message"` - Date time.Time `json:"date"` -} - -type LogsResponse struct { - Logs []LogsResponseDetails `json:"logs"` -} - -type LogsResponseDetails struct { - User models.UserShort `json:"user"` - Log string `json:"detail"` - Message string `json:"message"` - Date time.Time `json:"date"` -} - -type ScoreLogsResponse struct { - Logs []ScoreLogsResponseDetails `json:"scores"` -} - -type ScoreLogsResponseDetails struct { - Game models.Game `json:"game"` - User models.UserShort `json:"user"` - Map models.MapShort `json:"map"` - ScoreCount int `json:"score_count"` - ScoreTime int `json:"score_time"` - DemoID string `json:"demo_id"` - Date time.Time `json:"date"` -} - -// GET Score Logs -// -// @Description Get score logs of every player. -// @Tags logs -// @Produce json -// @Success 200 {object} models.Response{data=ScoreLogsResponse} -// @Router /logs/score [get] -func ScoreLogs(c *gin.Context) { - response := ScoreLogsResponse{Logs: []ScoreLogsResponseDetails{}} - sql := `SELECT g.id, - g."name", - g.is_coop, - rs.map_id, - m.name AS map_name, - u.steam_id, - u.user_name, - rs.score_count, - rs.score_time, - rs.demo_id, - rs.record_date - FROM ( - SELECT id, map_id, user_id, score_count, score_time, demo_id, record_date - FROM records_sp WHERE is_deleted = false - - UNION ALL - - SELECT id, map_id, host_id AS user_id, score_count, score_time, host_demo_id AS demo_id, record_date - FROM records_mp WHERE is_deleted = false - - UNION ALL - - SELECT id, map_id, partner_id AS user_id, score_count, score_time, partner_demo_id AS demo_id, record_date - FROM records_mp WHERE is_deleted = false - ) AS rs - JOIN users u ON rs.user_id = u.steam_id - JOIN maps m ON rs.map_id = m.id - JOIN games g ON m.game_id = g.id - ORDER BY rs.record_date DESC LIMIT 100;` - rows, err := database.DB.Query(sql) - if err != nil { - c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) - return - } - for rows.Next() { - score := ScoreLogsResponseDetails{} - 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) - if err != nil { - c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) - return - } - response.Logs = append(response.Logs, score) - } - c.JSON(http.StatusOK, models.Response{ - Success: true, - Message: "Successfully retrieved score logs.", - Data: response, - }) -} diff --git a/backend/handlers/record.go b/backend/handlers/record.go index 25a6c6d..07ae5a8 100644 --- a/backend/handlers/record.go +++ b/backend/handlers/record.go @@ -35,11 +35,11 @@ type RecordResponse struct { // @Tags maps / leaderboards // @Accept mpfd // @Produce json -// @Param mapid path int true "Map ID" -// @Param Authorization header string true "JWT Token" -// @Param host_demo formData file true "Host Demo" -// @Param partner_demo formData file false "Partner Demo" -// @Success 200 {object} models.Response{data=RecordResponse} +// @Param mapid path int true "Map ID" +// @Param Authorization header string true "JWT Token" +// @Param host_demo formData file true "Host Demo" +// @Param partner_demo formData file false "Partner Demo" +// @Success 200 {object} models.Response{data=RecordResponse} // @Router /maps/{mapid}/record [post] func CreateRecordWithDemo(c *gin.Context) { id := c.Param("mapid") 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 @@ +package handlers + +import ( + "net/http" + "time" + + "lphub/database" + "lphub/models" + + "github.com/gin-gonic/gin" +) + +type ScoresResponse struct { + Logs []ScoresDetails `json:"scores"` +} + +type ScoresDetails struct { + Game models.Game `json:"game"` + User models.UserShort `json:"user"` + Map models.MapShort `json:"map"` + ScoreCount int `json:"score_count"` + ScoreTime int `json:"score_time"` + DemoID string `json:"demo_id"` + Date time.Time `json:"date"` +} + +type TimelinePoint struct { + Date string `json:"date"` + Count int `json:"count"` +} + +type TimelineResponse struct { + Singleplayer []TimelinePoint `json:"timeline_singleplayer"` + Multiplayer []TimelinePoint `json:"timeline_multiplayer"` +} + +// GET Scores +// +// @Description Get score logs of every player. +// @Tags stats +// @Produce json +// @Success 200 {object} models.Response{data=ScoresResponse} +// @Router /stats/scores [get] +func Scores(c *gin.Context) { + response := ScoresResponse{Logs: []ScoresDetails{}} + sql := `SELECT g.id, + g."name", + g.is_coop, + rs.map_id, + m.name AS map_name, + u.steam_id, + u.user_name, + rs.score_count, + rs.score_time, + rs.demo_id, + rs.record_date + FROM ( + SELECT id, map_id, user_id, score_count, score_time, demo_id, record_date + FROM records_sp WHERE is_deleted = false + + UNION ALL + + SELECT id, map_id, host_id AS user_id, score_count, score_time, host_demo_id AS demo_id, record_date + FROM records_mp WHERE is_deleted = false + + UNION ALL + + SELECT id, map_id, partner_id AS user_id, score_count, score_time, partner_demo_id AS demo_id, record_date + FROM records_mp WHERE is_deleted = false + ) AS rs + JOIN users u ON rs.user_id = u.steam_id + JOIN maps m ON rs.map_id = m.id + JOIN games g ON m.game_id = g.id + ORDER BY rs.record_date DESC LIMIT 100;` + rows, err := database.DB.Query(sql) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + for rows.Next() { + score := ScoresDetails{} + 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) + if err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + response.Logs = append(response.Logs, score) + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully retrieved score logs.", + Data: response, + }) +} + +// GET Timeline +// +// @Description Get the history of portal count world records over time. +// @Tags stats +// @Produce json +// @Success 200 {object} models.Response{data=TimelineResponse} +// @Router /stats/timeline [get] +func Timeline(c *gin.Context) { + result := TimelineResponse{ + Singleplayer: []TimelinePoint{}, + Multiplayer: []TimelinePoint{}, + } + spQuery := ` + WITH date_series AS ( + SELECT DISTINCT record_date as date + FROM map_history + WHERE category_id = 1 AND map_id <= 60 AND record_date >= '2013-01-31' + ORDER BY record_date + ), + map_best_at_date AS ( + SELECT + ds.date, + mh.map_id, + MIN(mh.score_count) as best_count + FROM date_series ds + CROSS JOIN (SELECT DISTINCT map_id FROM map_history WHERE category_id = 1 AND map_id <= 60) maps + LEFT JOIN map_history mh ON mh.map_id = maps.map_id + AND mh.category_id = 1 + AND mh.record_date <= ds.date + GROUP BY ds.date, mh.map_id + ) + SELECT + date, + SUM(best_count) as total_count + FROM map_best_at_date + GROUP BY date + ORDER BY date ASC; + ` + + mpQuery := ` + WITH date_series AS ( + SELECT DISTINCT record_date as date + FROM map_history + WHERE category_id = 1 AND map_id > 60 AND record_date >= '2011-12-21' + ORDER BY record_date + ), + map_best_at_date AS ( + SELECT + ds.date, + mh.map_id, + MIN(mh.score_count) as best_count + FROM date_series ds + CROSS JOIN (SELECT DISTINCT map_id FROM map_history WHERE category_id = 1 AND map_id > 60) maps + LEFT JOIN map_history mh ON mh.map_id = maps.map_id + AND mh.category_id = 1 + AND mh.record_date <= ds.date + GROUP BY ds.date, mh.map_id + ) + SELECT + date, + SUM(best_count) as total_count + FROM map_best_at_date + GROUP BY date + ORDER BY date ASC; + ` + + rows, err := database.DB.Query(spQuery) + if err != nil { + return + } + defer rows.Close() + + for rows.Next() { + var dateTime time.Time + var count int + if err := rows.Scan(&dateTime, &count); err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + result.Singleplayer = append(result.Singleplayer, TimelinePoint{ + Date: dateTime.Format("2006-01-02"), + Count: count, + }) + } + + rows, err = database.DB.Query(mpQuery) + if err != nil { + return + } + defer rows.Close() + + for rows.Next() { + var dateTime time.Time + var count int + if err := rows.Scan(&dateTime, &count); err != nil { + c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) + return + } + result.Multiplayer = append(result.Multiplayer, TimelinePoint{ + Date: dateTime.Format("2006-01-02"), + Count: count, + }) + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully retrieved portal count timeline.", + Data: result, + }) +} diff --git a/backend/main.go b/backend/main.go index e422359..2147be8 100644 --- a/backend/main.go +++ b/backend/main.go @@ -29,35 +29,25 @@ func main() { if err != nil { log.Fatal("Error loading .env file") } + var app *newrelic.Application if os.Getenv("ENV") == "PROD" { gin.SetMode(gin.ReleaseMode) - } - app, err := newrelic.NewApplication( - newrelic.ConfigAppName("lphub"), - newrelic.ConfigLicense(os.Getenv("NEWRELIC_LICENSE_KEY")), - newrelic.ConfigAppLogForwardingEnabled(true), - ) - if err != nil { - log.Fatal("Error instrumenting newrelic") + app, err = newrelic.NewApplication( + newrelic.ConfigAppName("lphub"), + newrelic.ConfigLicense(os.Getenv("NEWRELIC_LICENSE_KEY")), + newrelic.ConfigAppLogForwardingEnabled(true), + ) + if err != nil { + log.Fatal("Error instrumenting newrelic") + } } router := gin.Default() - router.Use(nrgin.Middleware(app)) + if app != nil { + router.Use(nrgin.Middleware(app)) + } + // router.Use(cors.Default()) // ONLY FOR DEV database.ConnectDB() api.InitRoutes(router) - // for debugging - // router.Use(cors.New(cors.Config{ - // AllowOrigins: []string{"*"}, - // AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "PATCH"}, - // AllowHeaders: []string{"Origin"}, - // ExposeHeaders: []string{"Content-Length"}, - // AllowCredentials: true, - // MaxAge: 12 * time.Hour, - // })) - // router.Static("/static", "../frontend/build/static") - // router.StaticFile("/", "../frontend/build/index.html") - // router.NoRoute(func(c *gin.Context) { - // c.File("../frontend/build/index.html") - // }) router.MaxMultipartMemory = 250 << 20 // 250 mb limit for demos router.Run(fmt.Sprintf(":%s", os.Getenv("PORT"))) } -- cgit v1.2.3