Files
carvalho-finances/apps/api/internal/service/game.go
T

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")
}
}
}