diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 635cbcfe..a6576a7e 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -591,6 +591,35 @@ ], "summary": "获取分类统计信息", "operationId": "category-stat-dashboard", + "parameters": [ + { + "maximum": 90, + "minimum": 24, + "type": "integer", + "default": 90, + "description": "持续时间 (小时或天数)`", + "name": "duration", + "in": "query" + }, + { + "enum": [ + "hour", + "day" + ], + "type": "string", + "default": "day", + "description": "精度: \"hour\", \"day\"", + "name": "precision", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "用户ID,可jj", + "name": "user_id", + "in": "query" + } + ], "responses": { "200": { "description": "OK", @@ -663,6 +692,35 @@ ], "summary": "获取时间统计信息", "operationId": "time-stat-dashboard", + "parameters": [ + { + "maximum": 90, + "minimum": 24, + "type": "integer", + "default": 90, + "description": "持续时间 (小时或天数)`", + "name": "duration", + "in": "query" + }, + { + "enum": [ + "hour", + "day" + ], + "type": "string", + "default": "day", + "description": "精度: \"hour\", \"day\"", + "name": "precision", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "用户ID,可jj", + "name": "user_id", + "in": "query" + } + ], "responses": { "200": { "description": "OK", @@ -699,6 +757,35 @@ ], "summary": "用户贡献榜", "operationId": "user-code-rank-dashboard", + "parameters": [ + { + "maximum": 90, + "minimum": 24, + "type": "integer", + "default": 90, + "description": "持续时间 (小时或天数)`", + "name": "duration", + "in": "query" + }, + { + "enum": [ + "hour", + "day" + ], + "type": "string", + "default": "day", + "description": "精度: \"hour\", \"day\"", + "name": "precision", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "用户ID,可jj", + "name": "user_id", + "in": "query" + } + ], "responses": { "200": { "description": "OK", @@ -740,11 +827,31 @@ "operationId": "user-events-dashboard", "parameters": [ { + "maximum": 90, + "minimum": 24, + "type": "integer", + "default": 90, + "description": "持续时间 (小时或天数)`", + "name": "duration", + "in": "query" + }, + { + "enum": [ + "hour", + "day" + ], "type": "string", - "description": "用户ID", - "name": "user_id", + "default": "day", + "description": "精度: \"hour\", \"day\"", + "name": "precision", "in": "query", "required": true + }, + { + "type": "string", + "description": "用户ID,可jj", + "name": "user_id", + "in": "query" } ], "responses": { @@ -833,8 +940,29 @@ "operationId": "user-stat-dashboard", "parameters": [ { + "maximum": 90, + "minimum": 24, + "type": "integer", + "default": 90, + "description": "持续时间 (小时或天数)`", + "name": "duration", + "in": "query" + }, + { + "enum": [ + "hour", + "day" + ], "type": "string", - "description": "用户ID", + "default": "day", + "description": "精度: \"hour\", \"day\"", + "name": "precision", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "用户ID,可jj", "name": "user_id", "in": "query" } diff --git a/backend/domain/dashboard.go b/backend/domain/dashboard.go index 8baec218..20b52d59 100644 --- a/backend/domain/dashboard.go +++ b/backend/domain/dashboard.go @@ -2,27 +2,28 @@ package domain import ( "context" + "time" "github.com/chaitin/MonkeyCode/backend/db" ) type DashboardUsecase interface { Statistics(ctx context.Context) (*Statistics, error) - CategoryStat(ctx context.Context) (*CategoryStat, error) - TimeStat(ctx context.Context) (*TimeStat, error) - UserCodeRank(ctx context.Context) ([]*UserCodeRank, error) - UserStat(ctx context.Context, userID string) (*UserStat, error) - UserEvents(ctx context.Context, userID string) ([]*UserEvent, error) + CategoryStat(ctx context.Context, req StatisticsFilter) (*CategoryStat, error) + TimeStat(ctx context.Context, req StatisticsFilter) (*TimeStat, error) + UserCodeRank(ctx context.Context, req StatisticsFilter) ([]*UserCodeRank, error) + UserStat(ctx context.Context, req StatisticsFilter) (*UserStat, error) + UserEvents(ctx context.Context, req StatisticsFilter) ([]*UserEvent, error) UserHeatmap(ctx context.Context, userID string) (*UserHeatmapResp, error) } type DashboardRepo interface { Statistics(ctx context.Context) (*Statistics, error) - CategoryStat(ctx context.Context) (*CategoryStat, error) - TimeStat(ctx context.Context) (*TimeStat, error) - UserCodeRank(ctx context.Context) ([]*UserCodeRank, error) - UserStat(ctx context.Context, userID string) (*UserStat, error) - UserEvents(ctx context.Context, userID string) ([]*UserEvent, error) + CategoryStat(ctx context.Context, req StatisticsFilter) (*CategoryStat, error) + TimeStat(ctx context.Context, req StatisticsFilter) (*TimeStat, error) + UserCodeRank(ctx context.Context, req StatisticsFilter) ([]*UserCodeRank, error) + UserStat(ctx context.Context, req StatisticsFilter) (*UserStat, error) + UserEvents(ctx context.Context, req StatisticsFilter) ([]*UserEvent, error) UserHeatmap(ctx context.Context, userID string) ([]*UserHeatmap, error) } @@ -31,6 +32,23 @@ type Statistics struct { DisabledUsers int64 `json:"disabled_users"` // 禁用用户数 } +type StatisticsFilter struct { + Precision string `json:"precision" query:"precision" validate:"required,oneof=hour day" default:"day"` // 精度: "hour", "day" + Duration int `json:"duration" query:"duration" validate:"gte=24,lte=90" default:"90"` // 持续时间 (小时或天数)` + UserID string `json:"user_id,omitempty" query:"user_id"` // 用户ID,可jj +} + +func (s StatisticsFilter) StartTime() time.Time { + switch s.Precision { + case "hour": + return time.Now().Add(-time.Duration(s.Duration) * time.Hour) + case "day": + return time.Now().AddDate(0, 0, -int(s.Duration)) + default: + return time.Now().AddDate(0, 0, -90) + } +} + type UserHeatmapResp struct { MaxCount int64 `json:"max_count"` Points []*UserHeatmap `json:"points"` diff --git a/backend/go.mod b/backend/go.mod index 153d31d5..34955bf2 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,7 +4,7 @@ go 1.23.7 require ( entgo.io/ent v0.14.4 - github.com/GoYoko/web v1.0.0 + github.com/GoYoko/web v1.1.0 github.com/golang-migrate/migrate/v4 v4.18.3 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 @@ -30,7 +30,7 @@ require ( github.com/go-openapi/inflect v0.21.2 // 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.26.0 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index 03da42c6..54d652cb 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -8,8 +8,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/GoYoko/web v1.0.0 h1:kcNxz8BvpKavE0/iqatOmUeCXVghaoD5xYDiHDulVaE= -github.com/GoYoko/web v1.0.0/go.mod h1:DL9/gvuUG2jcBE1XUIY+9QBrrhdshzPEdxMCzR9jUHo= +github.com/GoYoko/web v1.1.0 h1:nIbtol5z0Y03d0nHsvGjv+W0fgmFRGUL8fzPN3kmrOY= +github.com/GoYoko/web v1.1.0/go.mod h1:DL9/gvuUG2jcBE1XUIY+9QBrrhdshzPEdxMCzR9jUHo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -58,8 +58,8 @@ 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.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= -github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= diff --git a/backend/internal/dashboard/handler/v1/dashboard.go b/backend/internal/dashboard/handler/v1/dashboard.go index aded30ef..ed9ad2da 100644 --- a/backend/internal/dashboard/handler/v1/dashboard.go +++ b/backend/internal/dashboard/handler/v1/dashboard.go @@ -21,11 +21,11 @@ func NewDashboardHandler( g := w.Group("/api/v1/dashboard") g.Use(auth.Auth()) g.GET("/statistics", web.BaseHandler(h.Statistics)) - g.GET("/category-stat", web.BaseHandler(h.CategoryStat)) - g.GET("/time-stat", web.BaseHandler(h.TimeStat)) - g.GET("/user-stat", web.BaseHandler(h.UserStat)) - g.GET("/user-events", web.BaseHandler(h.UserEvents)) - g.GET("/user-code-rank", web.BaseHandler(h.UserCodeRank)) + g.GET("/category-stat", web.BindHandler(h.CategoryStat)) + g.GET("/time-stat", web.BindHandler(h.TimeStat)) + g.GET("/user-stat", web.BindHandler(h.UserStat)) + g.GET("/user-events", web.BindHandler(h.UserEvents)) + g.GET("/user-code-rank", web.BindHandler(h.UserCodeRank)) g.GET("/user-heatmap", web.BaseHandler(h.UserHeatmap)) return h @@ -57,10 +57,11 @@ func (h *DashboardHandler) Statistics(c *web.Context) error { // @ID category-stat-dashboard // @Accept json // @Produce json -// @Success 200 {object} web.Resp{data=domain.CategoryStat} +// @Param filter query domain.StatisticsFilter true "筛选参数" +// @Success 200 {object} web.Resp{data=domain.CategoryStat} // @Router /api/v1/dashboard/category-stat [get] -func (h *DashboardHandler) CategoryStat(c *web.Context) error { - categoryStat, err := h.usecase.CategoryStat(c.Request().Context()) +func (h *DashboardHandler) CategoryStat(c *web.Context, req domain.StatisticsFilter) error { + categoryStat, err := h.usecase.CategoryStat(c.Request().Context(), req) if err != nil { return err } @@ -75,10 +76,11 @@ func (h *DashboardHandler) CategoryStat(c *web.Context) error { // @ID time-stat-dashboard // @Accept json // @Produce json -// @Success 200 {object} web.Resp{data=domain.TimeStat} +// @Param filter query domain.StatisticsFilter true "筛选参数" +// @Success 200 {object} web.Resp{data=domain.TimeStat} // @Router /api/v1/dashboard/time-stat [get] -func (h *DashboardHandler) TimeStat(c *web.Context) error { - timeStat, err := h.usecase.TimeStat(c.Request().Context()) +func (h *DashboardHandler) TimeStat(c *web.Context, req domain.StatisticsFilter) error { + timeStat, err := h.usecase.TimeStat(c.Request().Context(), req) if err != nil { return err } @@ -93,11 +95,11 @@ func (h *DashboardHandler) TimeStat(c *web.Context) error { // @ID user-stat-dashboard // @Accept json // @Produce json -// @Param user_id query string false "用户ID" +// @Param filter query domain.StatisticsFilter true "筛选参数" // @Success 200 {object} web.Resp{data=domain.UserStat} // @Router /api/v1/dashboard/user-stat [get] -func (h *DashboardHandler) UserStat(c *web.Context) error { - userStat, err := h.usecase.UserStat(c.Request().Context(), c.QueryParam("user_id")) +func (h *DashboardHandler) UserStat(c *web.Context, req domain.StatisticsFilter) error { + userStat, err := h.usecase.UserStat(c.Request().Context(), req) if err != nil { return err } @@ -112,11 +114,11 @@ func (h *DashboardHandler) UserStat(c *web.Context) error { // @ID user-events-dashboard // @Accept json // @Produce json -// @Param user_id query string true "用户ID" +// @Param filter query domain.StatisticsFilter true "筛选参数" // @Success 200 {object} web.Resp{data=[]domain.UserEvent} // @Router /api/v1/dashboard/user-events [get] -func (h *DashboardHandler) UserEvents(c *web.Context) error { - userEvents, err := h.usecase.UserEvents(c.Request().Context(), c.QueryParam("user_id")) +func (h *DashboardHandler) UserEvents(c *web.Context, req domain.StatisticsFilter) error { + userEvents, err := h.usecase.UserEvents(c.Request().Context(), req) if err != nil { return err } @@ -131,10 +133,11 @@ func (h *DashboardHandler) UserEvents(c *web.Context) error { // @ID user-code-rank-dashboard // @Accept json // @Produce json -// @Success 200 {object} web.Resp{data=[]domain.UserCodeRank} +// @Param filter query domain.StatisticsFilter true "筛选参数" +// @Success 200 {object} web.Resp{data=[]domain.UserCodeRank} // @Router /api/v1/dashboard/user-code-rank [get] -func (h *DashboardHandler) UserCodeRank(c *web.Context) error { - userCodeRank, err := h.usecase.UserCodeRank(c.Request().Context()) +func (h *DashboardHandler) UserCodeRank(c *web.Context, req domain.StatisticsFilter) error { + userCodeRank, err := h.usecase.UserCodeRank(c.Request().Context(), req) if err != nil { return err } diff --git a/backend/internal/dashboard/repo/dashboard.go b/backend/internal/dashboard/repo/dashboard.go index d5dc1ba3..8994d3ad 100644 --- a/backend/internal/dashboard/repo/dashboard.go +++ b/backend/internal/dashboard/repo/dashboard.go @@ -2,18 +2,18 @@ package repo import ( "context" + "fmt" "time" "entgo.io/ent/dialect/sql" "github.com/google/uuid" - "github.com/chaitin/MonkeyCode/backend/pkg/cvt" - "github.com/chaitin/MonkeyCode/backend/consts" "github.com/chaitin/MonkeyCode/backend/db" "github.com/chaitin/MonkeyCode/backend/db/task" "github.com/chaitin/MonkeyCode/backend/db/user" "github.com/chaitin/MonkeyCode/backend/domain" + "github.com/chaitin/MonkeyCode/backend/pkg/cvt" ) type DashboardRepo struct { @@ -25,10 +25,10 @@ func NewDashboardRepo(db *db.Client) domain.DashboardRepo { } // CategoryStat implements domain.DashboardRepo. -func (d *DashboardRepo) CategoryStat(ctx context.Context) (*domain.CategoryStat, error) { +func (d *DashboardRepo) CategoryStat(ctx context.Context, req domain.StatisticsFilter) (*domain.CategoryStat, error) { var cs []domain.CategoryPoint if err := d.db.Task.Query(). - Where(task.CreatedAtGTE(time.Now().AddDate(0, 0, -90))). + Where(task.CreatedAtGTE(req.StartTime())). Where(task.WorkModeNEQ("")). Modify(func(s *sql.Selector) { s.Select( @@ -42,7 +42,7 @@ func (d *DashboardRepo) CategoryStat(ctx context.Context) (*domain.CategoryStat, } var ps []domain.CategoryPoint if err := d.db.Task.Query(). - Where(task.CreatedAtGTE(time.Now().AddDate(0, 0, -90))). + Where(task.CreatedAtGTE(req.StartTime())). Where(task.ProgramLanguageNEQ("")). Modify(func(s *sql.Selector) { s.Select( @@ -88,13 +88,13 @@ type DateValue struct { } // TimeStat implements domain.DashboardRepo. -func (d *DashboardRepo) TimeStat(ctx context.Context) (*domain.TimeStat, error) { +func (d *DashboardRepo) TimeStat(ctx context.Context, req domain.StatisticsFilter) (*domain.TimeStat, error) { ds := make([]DateValue, 0) if err := d.db.Task.Query(). - Where(task.CreatedAtGTE(time.Now().AddDate(0, 0, -90))). + Where(task.CreatedAtGTE(req.StartTime())). Modify(func(s *sql.Selector) { s.Select( - sql.As("date_trunc('day', created_at)", "date"), + sql.As(fmt.Sprintf("date_trunc('%s', created_at)", req.Precision), "date"), sql.As("COUNT(DISTINCT user_id)", "user_count"), sql.As("COUNT(*) FILTER (WHERE model_type = 'llm')", "llm_count"), sql.As("COUNT(*) FILTER (WHERE model_type = 'coder')", "code_count"), @@ -175,10 +175,11 @@ type UserCodeRank struct { } // UserCodeRank implements domain.DashboardRepo. -func (d *DashboardRepo) UserCodeRank(ctx context.Context) ([]*domain.UserCodeRank, error) { +func (d *DashboardRepo) UserCodeRank(ctx context.Context, req domain.StatisticsFilter) ([]*domain.UserCodeRank, error) { var rs []UserCodeRank if err := d.db.Task.Query(). - Where(task.CreatedAtGTE(time.Now().AddDate(0, 0, -90))). + Where(task.CreatedAtGTE(req.StartTime())). + Where(task.IsAccept(true)). Modify(func(s *sql.Selector) { s.Select( sql.As("user_id", "user_id"), @@ -211,14 +212,14 @@ func (d *DashboardRepo) UserCodeRank(ctx context.Context) ([]*domain.UserCodeRan } // UserEvents implements domain.DashboardRepo. -func (d *DashboardRepo) UserEvents(ctx context.Context, userID string) ([]*domain.UserEvent, error) { - id, err := uuid.Parse(userID) +func (d *DashboardRepo) UserEvents(ctx context.Context, req domain.StatisticsFilter) ([]*domain.UserEvent, error) { + id, err := uuid.Parse(req.UserID) if err != nil { return nil, err } rs, err := d.db.Task.Query(). WithUser(). - Where(task.CreatedAtGTE(time.Now().AddDate(0, 0, -90))). + Where(task.CreatedAtGTE(req.StartTime())). Where(task.HasUserWith(user.ID(id))). Order(task.ByCreatedAt(sql.OrderDesc())). Limit(100). @@ -236,18 +237,18 @@ func (d *DashboardRepo) UserEvents(ctx context.Context, userID string) ([]*domai } // UserStat implements domain.DashboardRepo. -func (d *DashboardRepo) UserStat(ctx context.Context, userID string) (*domain.UserStat, error) { - id, err := uuid.Parse(userID) +func (d *DashboardRepo) UserStat(ctx context.Context, req domain.StatisticsFilter) (*domain.UserStat, error) { + id, err := uuid.Parse(req.UserID) if err != nil { return nil, err } var ds []DateValue if err := d.db.Task.Query(). Where(task.HasUserWith(user.ID(id))). - Where(task.CreatedAtGTE(time.Now().AddDate(0, 0, -90))). + Where(task.CreatedAtGTE(req.StartTime())). Modify(func(s *sql.Selector) { s.Select( - sql.As("date_trunc('day', created_at)", "date"), + sql.As(fmt.Sprintf("date_trunc('%s', created_at)", req.Precision), "date"), sql.As("COUNT(DISTINCT user_id)", "user_count"), sql.As("COUNT(*) FILTER (WHERE model_type = 'llm')", "llm_count"), sql.As("COUNT(*) FILTER (WHERE model_type = 'coder')", "code_count"), @@ -262,7 +263,7 @@ func (d *DashboardRepo) UserStat(ctx context.Context, userID string) (*domain.Us var cs []domain.CategoryPoint if err := d.db.Task.Query(). - Where(task.CreatedAtGTE(time.Now().AddDate(0, 0, -90))). + Where(task.CreatedAtGTE(req.StartTime())). Where(task.HasUserWith(user.ID(id))). Where(task.WorkModeNotNil()). Modify(func(s *sql.Selector) { @@ -277,7 +278,7 @@ func (d *DashboardRepo) UserStat(ctx context.Context, userID string) (*domain.Us } var ps []domain.CategoryPoint if err := d.db.Task.Query(). - Where(task.CreatedAtGTE(time.Now().AddDate(0, 0, -90))). + Where(task.CreatedAtGTE(req.StartTime())). Where(task.HasUserWith(user.ID(id))). Where(task.ProgramLanguageNotNil()). Modify(func(s *sql.Selector) { diff --git a/backend/internal/dashboard/usecase/dashboard.go b/backend/internal/dashboard/usecase/dashboard.go index 4148e7f1..930f748e 100644 --- a/backend/internal/dashboard/usecase/dashboard.go +++ b/backend/internal/dashboard/usecase/dashboard.go @@ -21,24 +21,24 @@ func (u *DashboardUsecase) Statistics(ctx context.Context) (*domain.Statistics, return u.repo.Statistics(ctx) } -func (u *DashboardUsecase) CategoryStat(ctx context.Context) (*domain.CategoryStat, error) { - return u.repo.CategoryStat(ctx) +func (u *DashboardUsecase) CategoryStat(ctx context.Context, req domain.StatisticsFilter) (*domain.CategoryStat, error) { + return u.repo.CategoryStat(ctx, req) } -func (u *DashboardUsecase) TimeStat(ctx context.Context) (*domain.TimeStat, error) { - return u.repo.TimeStat(ctx) +func (u *DashboardUsecase) TimeStat(ctx context.Context, req domain.StatisticsFilter) (*domain.TimeStat, error) { + return u.repo.TimeStat(ctx, req) } -func (u *DashboardUsecase) UserStat(ctx context.Context, username string) (*domain.UserStat, error) { - return u.repo.UserStat(ctx, username) +func (u *DashboardUsecase) UserStat(ctx context.Context, req domain.StatisticsFilter) (*domain.UserStat, error) { + return u.repo.UserStat(ctx, req) } -func (u *DashboardUsecase) UserEvents(ctx context.Context, username string) ([]*domain.UserEvent, error) { - return u.repo.UserEvents(ctx, username) +func (u *DashboardUsecase) UserEvents(ctx context.Context, req domain.StatisticsFilter) ([]*domain.UserEvent, error) { + return u.repo.UserEvents(ctx, req) } -func (u *DashboardUsecase) UserCodeRank(ctx context.Context) ([]*domain.UserCodeRank, error) { - return u.repo.UserCodeRank(ctx) +func (u *DashboardUsecase) UserCodeRank(ctx context.Context, req domain.StatisticsFilter) ([]*domain.UserCodeRank, error) { + return u.repo.UserCodeRank(ctx, req) } func (u *DashboardUsecase) UserHeatmap(ctx context.Context, userID string) (*domain.UserHeatmapResp, error) {