From 476e6ff59c111585979dbb3c5edcd07a2350ecb5 Mon Sep 17 00:00:00 2001 From: harold Date: Sun, 9 Mar 2025 19:28:16 +0500 Subject: [PATCH] add router for get statistic by date --- internal/api/http/handlers/donat/donat.go | 36 +++++++++++ internal/app/http/app.go | 2 + internal/docs/docs.go | 78 +++++++++++++++++++++++ internal/docs/swagger.json | 78 +++++++++++++++++++++++ internal/docs/swagger.yaml | 51 +++++++++++++++ internal/model/interfaces.go | 4 ++ internal/model/models.go | 12 ++++ internal/model/sql/query.go | 64 +++++++++++++++++++ internal/repository/donat/donat.go | 39 ++++++++++++ internal/service/donat/donat.go | 23 +++++++ 10 files changed, 387 insertions(+) diff --git a/internal/api/http/handlers/donat/donat.go b/internal/api/http/handlers/donat/donat.go index e64c4f4..89323e1 100644 --- a/internal/api/http/handlers/donat/donat.go +++ b/internal/api/http/handlers/donat/donat.go @@ -455,3 +455,39 @@ func UpdateModerationSettings(donatService model.DonatService) echo.HandlerFunc return request.JSON(http.StatusOK, "Success update") } } + +// GetDonationsStats godoc +// @Summary Get donations statistics +// @Description Get donations statistics for a given period +// @Tags Donate +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param period query string true "Period for statistics" Enums(24h, 7d, 1m, 1y) +// @Success 200 {array} model.DonationStatResponse "Donations statistics" +// @Failure 400 {object} echo.HTTPError "Bad request" +// @Failure 401 {object} echo.HTTPError "Unauthorized or expired token" +// @Failure 422 {object} echo.HTTPError "Validation error" +// @Router /donat/period-stat [get] +func GetDonationsStats(donatService model.DonatService) echo.HandlerFunc { + return func(request echo.Context) error { + authData, err := donatService.CheckToken(request) + if err != nil { + slog.Error("Unauthorized") + return echo.NewHTTPError(http.StatusUnauthorized, err.Error()) + } + ctx := context.Background() + + period := request.QueryParam("period") + if period == "" { + return echo.NewHTTPError(http.StatusBadRequest, "period is required") + } + + stats, err := donatService.GetDonationsStats(ctx, authData.AccountID, period) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return request.JSON(http.StatusOK, stats) + } +} diff --git a/internal/app/http/app.go b/internal/app/http/app.go index 4cecb15..57acfcd 100644 --- a/internal/app/http/app.go +++ b/internal/app/http/app.go @@ -88,6 +88,8 @@ func IncludeDonatHandlers( server.POST(PREFIX+"/donat/view/:donatID", MarkDonatView(donatService)) server.POST(PREFIX+"/donat/paid", MarkDonatPaid(donatService)) + + server.GET(PREFIX+"/donat/period-stat", GetDonationsStats(donatService)) } func IncludeWidgetHandlers( diff --git a/internal/docs/docs.go b/internal/docs/docs.go index 273fbd0..9b281d9 100644 --- a/internal/docs/docs.go +++ b/internal/docs/docs.go @@ -93,6 +93,70 @@ const docTemplate = `{ } } }, + "/donat/period-stat": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get donations statistics for a given period", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Donate" + ], + "summary": "Get donations statistics", + "parameters": [ + { + "enum": [ + "24h", + "7d", + "1m", + "1y" + ], + "type": "string", + "description": "Period for statistics", + "name": "period", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Donations statistics", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/donat-widget_internal_model.DonationStatResponse" + } + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "401": { + "description": "Unauthorized or expired token", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "422": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + } + } + } + }, "/donat/{streamer-login}": { "post": { "description": "Create donat", @@ -979,6 +1043,20 @@ const docTemplate = `{ } } }, + "donat-widget_internal_model.DonationStatResponse": { + "type": "object", + "properties": { + "amount_collected": { + "type": "integer" + }, + "date": { + "type": "string" + }, + "donations_count": { + "type": "integer" + } + } + }, "donat-widget_internal_model.FilterSettingResponse": { "type": "object", "properties": { diff --git a/internal/docs/swagger.json b/internal/docs/swagger.json index 67f2eb6..31cec24 100644 --- a/internal/docs/swagger.json +++ b/internal/docs/swagger.json @@ -86,6 +86,70 @@ } } }, + "/donat/period-stat": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get donations statistics for a given period", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Donate" + ], + "summary": "Get donations statistics", + "parameters": [ + { + "enum": [ + "24h", + "7d", + "1m", + "1y" + ], + "type": "string", + "description": "Period for statistics", + "name": "period", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Donations statistics", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/donat-widget_internal_model.DonationStatResponse" + } + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "401": { + "description": "Unauthorized or expired token", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "422": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + } + } + } + }, "/donat/{streamer-login}": { "post": { "description": "Create donat", @@ -972,6 +1036,20 @@ } } }, + "donat-widget_internal_model.DonationStatResponse": { + "type": "object", + "properties": { + "amount_collected": { + "type": "integer" + }, + "date": { + "type": "string" + }, + "donations_count": { + "type": "integer" + } + } + }, "donat-widget_internal_model.FilterSettingResponse": { "type": "object", "properties": { diff --git a/internal/docs/swagger.yaml b/internal/docs/swagger.yaml index ca94c0e..e7949c0 100644 --- a/internal/docs/swagger.yaml +++ b/internal/docs/swagger.yaml @@ -66,6 +66,15 @@ definitions: streamer_id: type: integer type: object + donat-widget_internal_model.DonationStatResponse: + properties: + amount_collected: + type: integer + date: + type: string + donations_count: + type: integer + type: object donat-widget_internal_model.FilterSettingResponse: properties: enable_links: @@ -424,6 +433,48 @@ paths: summary: Create donat tags: - Donate + /donat/period-stat: + get: + consumes: + - application/json + description: Get donations statistics for a given period + parameters: + - description: Period for statistics + enum: + - 24h + - 7d + - 1m + - 1y + in: query + name: period + required: true + type: string + produces: + - application/json + responses: + "200": + description: Donations statistics + schema: + items: + $ref: '#/definitions/donat-widget_internal_model.DonationStatResponse' + type: array + "400": + description: Bad request + schema: + $ref: '#/definitions/echo.HTTPError' + "401": + description: Unauthorized or expired token + schema: + $ref: '#/definitions/echo.HTTPError' + "422": + description: Validation error + schema: + $ref: '#/definitions/echo.HTTPError' + security: + - BearerAuth: [] + summary: Get donations statistics + tags: + - Donate /files: post: consumes: diff --git a/internal/model/interfaces.go b/internal/model/interfaces.go index 1997360..c567571 100644 --- a/internal/model/interfaces.go +++ b/internal/model/interfaces.go @@ -98,6 +98,8 @@ type DonatService interface { GetModerationSettings(ctx context.Context, streamerID int) (ModerationResponse, error) UpdateModerationSettings(ctx context.Context, streamerID int, updateModel UpdateModeration) error + + GetDonationsStats(ctx context.Context, streamerID int, period string) ([]DonationStatResponse, error) } type DonatRepo interface { @@ -147,6 +149,8 @@ type DonatRepo interface { GetModeration(ctx context.Context, streamerID int) (ModerationResponse, error) UpdateModeration(ctx context.Context, streamerID int, enable *bool, duration *int) error + + GetDonationsStats(ctx context.Context, streamerID int, period string) ([]DonationStat, error) } type TargetService interface { diff --git a/internal/model/models.go b/internal/model/models.go index a03f353..6c56397 100644 --- a/internal/model/models.go +++ b/internal/model/models.go @@ -255,6 +255,18 @@ type CreateDonatBody struct { DonatUser string `json:"donatUser"` } +type DonationStat struct { + Date time.Time `db:"date"` + AmountCollected int `db:"amount_collected"` + DonationsCount int `db:"donations_count"` +} + +type DonationStatResponse struct { + Date time.Time `json:"date"` + AmountCollected int `json:"amount_collected"` + DonationsCount int `json:"donations_count"` +} + //func (widget *GetWidgetDb) GetMediaUrl(mediaType MediaType) MediaUrl { // var mediaUrl MediaUrl // if mediaType == "background_url" { diff --git a/internal/model/sql/query.go b/internal/model/sql/query.go index 066a615..07e39bb 100644 --- a/internal/model/sql/query.go +++ b/internal/model/sql/query.go @@ -313,3 +313,67 @@ WHERE streamer_id = @streamer_id; ` var GetModeration = ` SELECT enable, duration FROM moderation WHERE streamer_id = @streamer_id;` + +const GetDonationsLast24Hours = ` +SELECT + DATE_TRUNC('hour', created_at) AS date, -- Группировка по часам + SUM(amount) AS amount_collected, + COUNT(*) AS donations_count +FROM + public.donats +WHERE + streamer_id = @streamer_id + AND created_at >= NOW() - INTERVAL '24 hours' +GROUP BY + DATE_TRUNC('hour', created_at) -- Группировка по часам +ORDER BY + date; +` + +const GetDonationsLast7Days = ` +SELECT + DATE(created_at) AS date, -- Группировка по дням + SUM(amount) AS amount_collected, + COUNT(*) AS donations_count +FROM + public.donats +WHERE + streamer_id = @streamer_id + AND created_at >= NOW() - INTERVAL '7 days' +GROUP BY + DATE(created_at) -- Группировка по дням +ORDER BY + date; +` + +const GetDonationsLastMonth = ` +SELECT + DATE(created_at) AS date, -- Группировка по дням + SUM(amount) AS amount_collected, + COUNT(*) AS donations_count +FROM + public.donats +WHERE + streamer_id = @streamer_id + AND created_at >= NOW() - INTERVAL '1 month' +GROUP BY + DATE(created_at) -- Группировка по дням +ORDER BY + date; +` + +const GetDonationsLastYear = ` +SELECT + DATE_TRUNC('month', created_at) AS date, -- Группировка по месяцам + SUM(amount) AS amount_collected, + COUNT(*) AS donations_count +FROM + public.donats +WHERE + streamer_id = @streamer_id + AND created_at >= NOW() - INTERVAL '1 year' +GROUP BY + DATE_TRUNC('month', created_at) -- Группировка по месяцам +ORDER BY + date; +` diff --git a/internal/repository/donat/donat.go b/internal/repository/donat/donat.go index d0213ce..8d2cc41 100644 --- a/internal/repository/donat/donat.go +++ b/internal/repository/donat/donat.go @@ -604,3 +604,42 @@ func (repoDonat *RepoDonat) UpdateModeration( } return nil } + +func (repoDonat *RepoDonat) GetDonationsStats( + ctx context.Context, + streamerID int, + period string, // "24h", "7d", "1m", "1y" +) ([]model.DonationStat, error) { + var query string + switch period { + case "24h": + query = sql.GetDonationsLast24Hours + case "7d": + query = sql.GetDonationsLast7Days + case "1m": + query = sql.GetDonationsLastMonth + case "1y": + query = sql.GetDonationsLastYear + default: + return nil, errors.New("invalid period") + } + + args := pgx.NamedArgs{ + "streamer_id": streamerID, + } + + rows, err := repoDonat.db.Select(ctx, query, args) + if err != nil { + slog.Error(err.Error()) + return nil, err + } + + var stats []model.DonationStat + err = pgxscan.ScanAll(&stats, rows) + if err != nil { + slog.Error(err.Error()) + return nil, err + } + + return stats, nil +} diff --git a/internal/service/donat/donat.go b/internal/service/donat/donat.go index a066ade..bada331 100644 --- a/internal/service/donat/donat.go +++ b/internal/service/donat/donat.go @@ -455,3 +455,26 @@ func (donatService *ServiceDonat) UpdateModerationSettings( } return nil } + +func (donatService *ServiceDonat) GetDonationsStats( + ctx context.Context, + streamerID int, + period string, +) ([]model.DonationStatResponse, error) { + stats, err := donatService.donatRepo.GetDonationsStats(ctx, streamerID, period) + if err != nil { + slog.Error(err.Error()) + return nil, err + } + + var response []model.DonationStatResponse + for _, stat := range stats { + response = append(response, model.DonationStatResponse{ + Date: stat.Date, + AmountCollected: stat.AmountCollected, + DonationsCount: stat.DonationsCount, + }) + } + + return response, nil +}