diff --git a/internal/api/user.go b/internal/api/user.go index ea52f25..ba5d5b1 100644 --- a/internal/api/user.go +++ b/internal/api/user.go @@ -228,10 +228,24 @@ func listUsersHandler(userSvc user.Service) http.HandlerFunc { func getActiveUserListHandler(userSvc user.Service) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { - ctx := req.Context() + + quarterStr := req.URL.Query().Get("quarter") + yearStr := req.URL.Query().Get("year") + + quarter, err := strconv.Atoi(quarterStr) + if err != nil || quarter < 1 || quarter > 4 { + http.Error(rw, "Invalid quarter", http.StatusBadRequest) + return + } + year, err := strconv.Atoi(yearStr) + if err != nil || year < 2024 { + http.Error(rw, "Invalid year", http.StatusBadRequest) + return + } + log.Info(ctx, "getActiveUserListHandler: req: ", req) - resp, err := userSvc.GetActiveUserList(req.Context()) + resp, err := userSvc.GetActiveUserList(ctx, quarter, year) if err != nil { dto.ErrorRepsonse(rw, err) return @@ -241,6 +255,7 @@ func getActiveUserListHandler(userSvc user.Service) http.HandlerFunc { dto.SuccessRepsonse(rw, http.StatusOK, "Active Users list", resp) } } + func getUserByIdHandler(userSvc user.Service) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { diff --git a/internal/app/users/service.go b/internal/app/users/service.go index 2342685..62fe1a3 100644 --- a/internal/app/users/service.go +++ b/internal/app/users/service.go @@ -37,7 +37,7 @@ type Service interface { ListUsers(ctx context.Context, reqData dto.ListUsersReq) (resp dto.ListUsersResp, err error) GetUserById(ctx context.Context) (user dto.GetUserByIdResp, err error) UpdateRewardQuota(ctx context.Context) (err error) - GetActiveUserList(ctx context.Context) ([]dto.ActiveUser, error) + GetActiveUserList(ctx context.Context, quarter int, year int) ([]dto.ActiveUser, error) GetTop10Users(ctx context.Context) (users []dto.Top10User, err error) AdminLogin(ctx context.Context, loginReq dto.AdminLoginReq) (resp dto.LoginUserResp, err error) NotificationByAdmin(ctx context.Context, notificationReq dto.AdminNotificationReq) (err error) @@ -483,8 +483,9 @@ func (us *service) GetUserById(ctx context.Context) (user dto.GetUserByIdResp, e return } -func (us *service) GetActiveUserList(ctx context.Context) ([]dto.ActiveUser, error) { - activeUserDb, err := us.userRepo.GetActiveUserList(ctx, nil) +func (us *service) GetActiveUserList(ctx context.Context, quarter int, year int) ([]dto.ActiveUser, error) { + quarterStart, quarterEnd := getQuarterRangeUnixTime(quarter, year) + activeUserDb, err := us.userRepo.GetActiveUserList(ctx, nil, quarterStart, quarterEnd) if err != nil { logger.Errorf(ctx, "userService: GetActiveUserList: err: %v", err) return []dto.ActiveUser{}, err @@ -496,6 +497,34 @@ func (us *service) GetActiveUserList(ctx context.Context) ([]dto.ActiveUser, err } return res, nil } + +func getQuarterRangeUnixTime(quarter int, year int) (start int64, end int64) { + var startMonth, endMonth time.Month + startYear := year + endYear := year + switch quarter { + case 1: + startMonth = time.March + endMonth = time.June + case 2: + startMonth = time.June + endMonth = time.September + case 3: + startMonth = time.September + endMonth = time.December + case 4: + startMonth = time.December + endMonth = time.March + endYear = year + 1 // Q4 ends next year's March + default: + startMonth = time.January + endMonth = time.January + } + startTime := time.Date(startYear, startMonth, 1, 0, 0, 0, 0, time.UTC) + endTime := time.Date(endYear, endMonth, 1, 0, 0, 0, 0, time.UTC) + return startTime.UnixMilli(), endTime.UnixMilli() +} + func (us *service) UpdateRewardQuota(ctx context.Context) error { err := us.userRepo.UpdateRewardQuota(ctx, nil) return err diff --git a/internal/repository/postgresdb/user.go b/internal/repository/postgresdb/user.go index 241c7e0..982ebbf 100644 --- a/internal/repository/postgresdb/user.go +++ b/internal/repository/postgresdb/user.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "time" "github.com/Masterminds/squirrel" "github.com/jmoiron/sqlx" @@ -262,72 +263,92 @@ func (us *userStore) ListUsers(ctx context.Context, reqData dto.ListUsersReq) (r return } -func (us *userStore) GetActiveUserList(ctx context.Context, tx repository.Transaction) (activeUsers []repository.ActiveUser, err error) { +func getQuarterRangeUnixTime(quarter int, year int) (start int64, end int64) { + var startMonth, endMonth time.Month + switch quarter { + case 1: + startMonth = time.March + endMonth = time.June + case 2: + startMonth = time.June + endMonth = time.September + case 3: + startMonth = time.September + endMonth = time.December + case 4: + startMonth = time.December + endMonth = time.March + year += 1 // Q4 ends next year's March + default: + startMonth = time.January + endMonth = time.January + } + startTime := time.Date(year, startMonth, 1, 0, 0, 0, 0, time.UTC) + endTime := time.Date(year, endMonth, 1, 0, 0, 0, 0, time.UTC) + return startTime.Unix(), endTime.Unix() +} + +func (us *userStore) GetActiveUserList(ctx context.Context, tx repository.Transaction, quarterStart int64, quarterEnd int64) (activeUsers []repository.ActiveUser, err error) { queryExecutor := us.InitiateQueryExecutor(tx) - afterTime := GetQuarterStartUnixTime() query := `WITH user_points AS ( - SELECT - u.id AS user_id, - COALESCE(received.total_received_appreciations, 0) AS total_received_appreciations, - COALESCE(sent.total_sent_appreciations, 0) AS total_sent_appreciations, - COALESCE(given.total_given_rewards, 0) AS total_given_rewards, - (3 * COALESCE(sent.total_sent_appreciations, 0) + 2 * COALESCE(received.total_received_appreciations, 0) + COALESCE(given.total_given_rewards, 0)) AS active_user_points - FROM - users u - LEFT JOIN - (SELECT receiver AS user_id, COUNT(*) AS total_received_appreciations - FROM appreciations - WHERE - Appreciations.is_valid = true AND appreciations.created_at >=$1 - GROUP BY receiver - ) AS received ON u.id = received.user_id - LEFT JOIN - (SELECT sender AS user_id, COUNT(*) AS total_sent_appreciations - FROM appreciations - WHERE - Appreciations.is_valid = true AND appreciations.created_at >=$2 - GROUP BY sender) AS sent ON u.id = sent.user_id - LEFT JOIN - (SELECT sender AS user_id, COUNT(*) AS total_given_rewards - FROM rewards - WHERE - rewards.created_at >=$3 - GROUP BY sender) AS given ON u.id = given.user_id - WHERE - COALESCE(received.total_received_appreciations, 0) > 0 OR - COALESCE(sent.total_sent_appreciations, 0) > 0 OR - COALESCE(given.total_given_rewards, 0) > 0 - ORDER BY - active_user_points DESC, - total_sent_appreciations DESC, - total_given_rewards DESC, - total_received_appreciations DESC -) -SELECT - up.user_id, - u.first_name , - u.last_name , - u.profile_image_url, - b.name AS badge, - COALESCE(ap.appreciation_points, 0) AS appreciation_points -FROM - user_points up -JOIN - users u ON up.user_id = u.id -LEFT JOIN - (SELECT receiver, SUM(total_reward_points) AS appreciation_points - FROM appreciations - GROUP BY receiver) AS ap ON u.id = ap.receiver -LEFT JOIN - (SELECT ub.user_id, b.name - FROM user_badges ub - JOIN badges b ON ub.badge_id = b.id - WHERE ub.id = (SELECT MAX(id) FROM user_badges WHERE user_id = ub.user_id)) AS b ON u.id = b.user_id -LIMIT 10; -` - logger.Info(ctx, "afterTime: ", afterTime) - - rows, err := queryExecutor.Query(query, afterTime, afterTime, afterTime) + SELECT + u.id AS user_id, + COALESCE(received.total_received_appreciations, 0) AS total_received_appreciations, + COALESCE(sent.total_sent_appreciations, 0) AS total_sent_appreciations, + COALESCE(given.total_given_rewards, 0) AS total_given_rewards, + (3 * COALESCE(sent.total_sent_appreciations, 0) + 2 * COALESCE(received.total_received_appreciations, 0) + COALESCE(given.total_given_rewards, 0)) AS active_user_points + FROM + users u + LEFT JOIN + (SELECT receiver AS user_id, COUNT(*) AS total_received_appreciations + FROM appreciations + WHERE appreciations.is_valid = true AND appreciations.created_at >= $1 AND appreciations.created_at < $2 + GROUP BY receiver + ) AS received ON u.id = received.user_id + LEFT JOIN + (SELECT sender AS user_id, COUNT(*) AS total_sent_appreciations + FROM appreciations + WHERE appreciations.is_valid = true AND appreciations.created_at >= $1 AND appreciations.created_at < $2 + GROUP BY sender) AS sent ON u.id = sent.user_id + LEFT JOIN + (SELECT sender AS user_id, COUNT(*) AS total_given_rewards + FROM rewards + WHERE rewards.created_at >= $1 AND rewards.created_at < $2 + GROUP BY sender) AS given ON u.id = given.user_id + WHERE + COALESCE(received.total_received_appreciations, 0) > 0 OR + COALESCE(sent.total_sent_appreciations, 0) > 0 OR + COALESCE(given.total_given_rewards, 0) > 0 + ORDER BY + active_user_points DESC, + total_sent_appreciations DESC, + total_given_rewards DESC, + total_received_appreciations DESC + ) + SELECT + up.user_id, + u.first_name , + u.last_name , + u.profile_image_url, + b.name AS badge, + COALESCE(ap.appreciation_points, 0) AS appreciation_points + FROM + user_points up + JOIN + users u ON up.user_id = u.id + LEFT JOIN + (SELECT receiver, SUM(total_reward_points) AS appreciation_points + FROM appreciations + GROUP BY receiver) AS ap ON u.id = ap.receiver + LEFT JOIN + (SELECT ub.user_id, b.name + FROM user_badges ub + JOIN badges b ON ub.badge_id = b.id + WHERE ub.id = (SELECT MAX(id) FROM user_badges WHERE user_id = ub.user_id)) AS b ON u.id = b.user_id + LIMIT 10;` + logger.Info(ctx, "quarterStart: ", quarterStart, ", quarterEnd: ", quarterEnd) + + rows, err := queryExecutor.Query(query, quarterStart, quarterEnd) if err != nil { logger.Error(ctx, "err: userStore ", err.Error()) return []repository.ActiveUser{}, err diff --git a/internal/repository/user.go b/internal/repository/user.go index c1054ff..66e82b4 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -19,7 +19,7 @@ type UserStorer interface { ListUsers(ctx context.Context, reqData dto.ListUsersReq) (resp []User, count int64, err error) UpdateRewardQuota(ctx context.Context, tx Transaction) (err error) - GetActiveUserList(ctx context.Context, tx Transaction) (activeUsers []ActiveUser, err error) + GetActiveUserList(ctx context.Context, tx Transaction, quarterStart int64, quarterEnd int64) (activeUsers []ActiveUser, err error) GetUserById(ctx context.Context, reqData dto.GetUserByIdReq) (user dto.GetUserByIdResp, err error) GetTop10Users(ctx context.Context, quarterTimestamp int64) (users []Top10Users, err error) GetGradeById(ctx context.Context, id int64) (grade Grade, err error)