99 lines
3.0 KiB
Go
99 lines
3.0 KiB
Go
package processor
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/google/uuid"
|
||
|
|
|
||
|
|
"apskel-pos-be/internal/models"
|
||
|
|
"apskel-pos-be/internal/repository"
|
||
|
|
)
|
||
|
|
|
||
|
|
const (
|
||
|
|
// leaderboardLimit is how many ranks a leaderboard shows unless asked, and
|
||
|
|
// leaderboardMaxLimit the most it shows.
|
||
|
|
leaderboardLimit = 10
|
||
|
|
leaderboardMaxLimit = 100
|
||
|
|
)
|
||
|
|
|
||
|
|
// Leaderboard ranks the customers of the customer's organization by the EnakCoin they
|
||
|
|
// won from games in one week, Monday to Sunday in Asia/Jakarta: this week, or the one
|
||
|
|
// holding q.Date. A session counts in the week it started. Refunded, expired and
|
||
|
|
// flagged sessions count for nothing.
|
||
|
|
func (p *GameSessionProcessor) Leaderboard(ctx context.Context, customerID uuid.UUID, q models.EnakGameLeaderboardQuery) (*models.EnakGameLeaderboard, error) {
|
||
|
|
day := walletDay(p.now())
|
||
|
|
if s := strings.TrimSpace(q.Date); s != "" {
|
||
|
|
d, err := time.ParseInLocation("2006-01-02", s, walletDisplayLocation)
|
||
|
|
if err != nil {
|
||
|
|
return nil, gameSessionRejected("date must be YYYY-MM-DD")
|
||
|
|
}
|
||
|
|
day = d
|
||
|
|
}
|
||
|
|
weekStart := leaderboardWeekStart(day)
|
||
|
|
filter := repository.LeaderboardFilter{
|
||
|
|
CustomerID: customerID, From: weekStart, To: weekStart.AddDate(0, 0, 7), Limit: q.Limit,
|
||
|
|
}
|
||
|
|
if filter.Limit < 1 || filter.Limit > leaderboardMaxLimit {
|
||
|
|
filter.Limit = leaderboardLimit
|
||
|
|
}
|
||
|
|
if s := strings.TrimSpace(q.GameID); s != "" {
|
||
|
|
id, err := uuid.Parse(s)
|
||
|
|
if err != nil {
|
||
|
|
return nil, gameSessionRejected("game_id must be a UUID")
|
||
|
|
}
|
||
|
|
filter.GameID = &id
|
||
|
|
}
|
||
|
|
customer, err := p.customers.GetCustomer(ctx, customerID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
filter.OrganizationID = customer.OrganizationID
|
||
|
|
rows, err := p.sessions.Leaderboard(ctx, filter)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
board := &models.EnakGameLeaderboard{
|
||
|
|
WeekStart: weekStart.Format("2006-01-02"), WeekEnd: weekStart.AddDate(0, 0, 6).Format("2006-01-02"),
|
||
|
|
GameID: filter.GameID, Entries: []models.EnakGameLeaderboardEntry{},
|
||
|
|
}
|
||
|
|
for _, row := range rows {
|
||
|
|
entry := models.EnakGameLeaderboardEntry{
|
||
|
|
Rank: row.Rank, Name: maskCustomerName(row.Name), Coins: row.Coins, IsMe: row.CustomerID == customerID,
|
||
|
|
}
|
||
|
|
if row.Rank <= filter.Limit {
|
||
|
|
board.Entries = append(board.Entries, entry)
|
||
|
|
}
|
||
|
|
if entry.IsMe {
|
||
|
|
me := entry
|
||
|
|
board.Me = &me
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return board, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// leaderboardWeekStart is the Monday, at midnight in Asia/Jakarta, of the week of day.
|
||
|
|
func leaderboardWeekStart(day time.Time) time.Time {
|
||
|
|
day = walletDay(day)
|
||
|
|
return day.AddDate(0, 0, -((int(day.Weekday()) + 6) % 7))
|
||
|
|
}
|
||
|
|
|
||
|
|
// maskCustomerName keeps the first word of a name and the first letter of every other
|
||
|
|
// word: "Budi Santoso" is shown as "Budi S******". A word with a digit, as when a phone
|
||
|
|
// number was used for the name, keeps only its first character, the first word too.
|
||
|
|
func maskCustomerName(name string) string {
|
||
|
|
words := strings.Fields(name)
|
||
|
|
if len(words) == 0 {
|
||
|
|
return "Pemain"
|
||
|
|
}
|
||
|
|
for i, w := range words {
|
||
|
|
if i == 0 && !strings.ContainsAny(w, "0123456789") {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
r := []rune(w)
|
||
|
|
words[i] = string(r[0]) + strings.Repeat("*", len(r)-1)
|
||
|
|
}
|
||
|
|
return strings.Join(words, " ")
|
||
|
|
}
|