175 lines
5.2 KiB
Go
175 lines
5.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"financeiro-carvalho/internal/model"
|
|
)
|
|
|
|
type GameRepo interface {
|
|
GetProfile(ctx context.Context) (*model.PlayerProfile, error)
|
|
AddXP(ctx context.Context, eventType string, amount int, description string) (*model.PlayerProfile, error)
|
|
ListActiveQuests(ctx context.Context) ([]model.PlayerQuest, error)
|
|
EnsurePlayerQuest(ctx context.Context, questID int, period string) (int, error)
|
|
IncrementQuestCount(ctx context.Context, questType, period string, delta int) error
|
|
UpdateQuestPct(ctx context.Context, period string, pct float64) error
|
|
ClaimQuest(ctx context.Context, playerQuestID int) (int, error)
|
|
ListAchievements(ctx context.Context) ([]model.Achievement, error)
|
|
IsAchievementUnlocked(ctx context.Context, condition string) (bool, error)
|
|
UnlockAchievement(ctx context.Context, condition string) (int, error)
|
|
ListCosmetics(ctx context.Context) ([]model.Cosmetic, error)
|
|
EquipCosmetic(ctx context.Context, cosmeticID int) error
|
|
UnlockCosmeticsForLevel(ctx context.Context, level int) error
|
|
}
|
|
|
|
type GameService struct {
|
|
repo GameRepo
|
|
}
|
|
|
|
func NewGameService(repo GameRepo) *GameService {
|
|
return &GameService{repo: repo}
|
|
}
|
|
|
|
func (s *GameService) GetProfile(ctx context.Context) (*model.PlayerProfile, error) {
|
|
return s.repo.GetProfile(ctx)
|
|
}
|
|
|
|
func (s *GameService) AwardXP(ctx context.Context, eventType string, amount int, description string) (*model.PlayerProfile, error) {
|
|
prevProfile, _ := s.repo.GetProfile(ctx)
|
|
profile, err := s.repo.AddXP(ctx, eventType, amount, description)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// unlock cosmetics if levelled up
|
|
if prevProfile != nil && profile.Level > prevProfile.Level {
|
|
_ = s.repo.UnlockCosmeticsForLevel(ctx, profile.Level)
|
|
if profile.Level >= 5 {
|
|
s.tryUnlockAchievement(ctx, "level_5")
|
|
}
|
|
if profile.Level >= 10 {
|
|
s.tryUnlockAchievement(ctx, "level_10")
|
|
}
|
|
}
|
|
return profile, nil
|
|
}
|
|
|
|
func (s *GameService) tryUnlockAchievement(ctx context.Context, condition string) {
|
|
unlocked, _ := s.repo.IsAchievementUnlocked(ctx, condition)
|
|
if unlocked {
|
|
return
|
|
}
|
|
xp, err := s.repo.UnlockAchievement(ctx, condition)
|
|
if err == nil && xp > 0 {
|
|
_, _ = s.repo.AddXP(ctx, "achievement_"+condition, xp, "Achievement desbloqueado")
|
|
}
|
|
}
|
|
|
|
func (s *GameService) CheckAndUnlockAchievement(ctx context.Context, condition string) {
|
|
s.tryUnlockAchievement(ctx, condition)
|
|
}
|
|
|
|
func (s *GameService) GetSummary(ctx context.Context) (*model.GameSummary, error) {
|
|
profile, err := s.repo.GetProfile(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
quests, err := s.repo.ListActiveQuests(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if quests == nil {
|
|
quests = []model.PlayerQuest{}
|
|
}
|
|
|
|
achievements, err := s.repo.ListAchievements(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
recent := []model.Achievement{}
|
|
for _, a := range achievements {
|
|
if a.Earned {
|
|
recent = append(recent, a)
|
|
if len(recent) >= 3 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
cosmetics, err := s.repo.ListCosmetics(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
equipped := []model.Cosmetic{}
|
|
for _, c := range cosmetics {
|
|
if c.Equipped {
|
|
equipped = append(equipped, c)
|
|
}
|
|
}
|
|
|
|
return &model.GameSummary{
|
|
Profile: *profile,
|
|
ActiveQuests: quests,
|
|
RecentAchievements: recent,
|
|
EquippedCosmetics: equipped,
|
|
}, nil
|
|
}
|
|
|
|
func (s *GameService) ClaimQuest(ctx context.Context, playerQuestID int) error {
|
|
xp, err := s.repo.ClaimQuest(ctx, playerQuestID)
|
|
if err != nil || xp == 0 {
|
|
return err
|
|
}
|
|
_, err = s.AwardXP(ctx, "quest_claimed", xp, "Quest concluída")
|
|
return err
|
|
}
|
|
|
|
func (s *GameService) ListAllAchievements(ctx context.Context) ([]model.Achievement, error) {
|
|
return s.repo.ListAchievements(ctx)
|
|
}
|
|
|
|
func (s *GameService) ListCosmetics(ctx context.Context) ([]model.Cosmetic, error) {
|
|
return s.repo.ListCosmetics(ctx)
|
|
}
|
|
|
|
func (s *GameService) EquipCosmetic(ctx context.Context, cosmeticID int) error {
|
|
return s.repo.EquipCosmetic(ctx, cosmeticID)
|
|
}
|
|
|
|
// NotifyAction is called by other handlers after key financial actions to award XP and update quests.
|
|
func (s *GameService) NotifyAction(ctx context.Context, action string, extra map[string]any) {
|
|
now := time.Now()
|
|
monthlyPeriod := now.Format("2006-01")
|
|
weeklyPeriod := now.Format("2006") + "-W" + now.Format("01")
|
|
dailyPeriod := now.Format("2006-01-02")
|
|
|
|
switch action {
|
|
case "transaction_created":
|
|
_, _ = s.AwardXP(ctx, "transaction_created", 5, "Transação registrada")
|
|
if catID, ok := extra["category_id"]; ok && catID != nil {
|
|
_, _ = s.AwardXP(ctx, "transaction_categorized", 10, "Transação categorizada")
|
|
_ = s.repo.IncrementQuestCount(ctx, "daily", dailyPeriod, 1)
|
|
_ = s.repo.IncrementQuestCount(ctx, "weekly", weeklyPeriod, 1)
|
|
}
|
|
s.tryUnlockAchievement(ctx, "first_transaction")
|
|
|
|
case "import_confirmed":
|
|
count, _ := extra["count"].(int)
|
|
if count < 1 {
|
|
count = 1
|
|
}
|
|
_, _ = s.AwardXP(ctx, "import_confirmed", 50, "Extrato importado")
|
|
_ = s.repo.IncrementQuestCount(ctx, "weekly", weeklyPeriod, 1)
|
|
s.tryUnlockAchievement(ctx, "first_import")
|
|
|
|
case "savings_checked":
|
|
pct, _ := extra["pct"].(float64)
|
|
_ = s.repo.UpdateQuestPct(ctx, monthlyPeriod, pct)
|
|
if pct >= 40 {
|
|
_, _ = s.AwardXP(ctx, "savings_goal_met", 200, "Meta de 40% atingida!")
|
|
s.tryUnlockAchievement(ctx, "first_40pct")
|
|
}
|
|
}
|
|
}
|