Spaces:
Running
Running
| using FlowAPI.Application.DTOs; | |
| using FlowAPI.Application.Interfaces; | |
| using FlowAPI.Domain.Entities; | |
| using FlowAPI.Domain.Enums; | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| namespace FlowAPI.Application.Services | |
| { | |
| public class AchievementService : IAchievementService | |
| { | |
| private readonly IAchievementRepository _achievementRepo; | |
| private readonly IUserAchievementRepository _userAchievementRepo; | |
| private readonly IUserRepository _userRepo; | |
| public AchievementService(IAchievementRepository achievementRepo, IUserAchievementRepository userAchievementRepo, IUserRepository userRepo) | |
| { | |
| _achievementRepo = achievementRepo; | |
| _userAchievementRepo = userAchievementRepo; | |
| _userRepo = userRepo; | |
| } | |
| public async Task<IEnumerable<UserAchievementDto>> GetUserAchievementsAsync(Guid userId) | |
| { | |
| var allAchievements = await _achievementRepo.GetAllAsync(); | |
| var userAchievements = await _userAchievementRepo.GetByUserIdAsync(userId); | |
| var user = await _userRepo.GetByIdAsync(userId); | |
| var isAvatarUploaded = user != null && !string.IsNullOrEmpty(user.AvatarUrl); | |
| var result = new List<UserAchievementDto>(); | |
| foreach (var achievement in allAchievements) | |
| { | |
| var userRecord = userAchievements.FirstOrDefault(ua => ua.AchievementId == achievement.Id); | |
| // Retroactively unlock Self Portrait if user already has an avatar set but no record or locked record in DB | |
| if (achievement.Title == "Self Portrait" && isAvatarUploaded) | |
| { | |
| if (userRecord == null) | |
| { | |
| var newUa = new UserAchievement | |
| { | |
| UserId = userId, | |
| AchievementId = achievement.Id, | |
| CurrentValue = 1, | |
| IsUnlocked = true, | |
| UnlockedAt = DateTime.UtcNow | |
| }; | |
| userRecord = await _userAchievementRepo.CreateAsync(newUa); | |
| } | |
| else if (!userRecord.IsUnlocked) | |
| { | |
| userRecord.CurrentValue = 1; | |
| userRecord.IsUnlocked = true; | |
| userRecord.UnlockedAt = DateTime.UtcNow; | |
| userRecord = await _userAchievementRepo.UpdateAsync(userRecord); | |
| } | |
| } | |
| result.Add(new UserAchievementDto | |
| { | |
| Id = userRecord?.Id ?? Guid.Empty, | |
| AchievementId = achievement.Id, | |
| CurrentValue = userRecord?.CurrentValue ?? 0, | |
| IsUnlocked = userRecord?.IsUnlocked ?? false, | |
| UnlockedAt = userRecord?.UnlockedAt, | |
| Achievement = new AchievementDto | |
| { | |
| Id = achievement.Id, | |
| Title = achievement.Title, | |
| Description = achievement.Description, | |
| IconUrl = achievement.IconUrl, | |
| Category = achievement.Category, | |
| Type = achievement.Type, | |
| TargetValue = achievement.TargetValue, | |
| RewardPoints = achievement.RewardPoints | |
| } | |
| }); | |
| } | |
| return result; | |
| } | |
| public async Task ProcessEventAsync(Guid userId, string eventName, int amount = 1) | |
| { | |
| // 1. Find achievements triggered by this event | |
| var triggeredAchievements = await _achievementRepo.GetByTriggerEventAsync(eventName); | |
| if (!triggeredAchievements.Any()) return; | |
| // 2. Fetch user's current progress for these achievements | |
| var achievementIds = triggeredAchievements.Select(a => a.Id).ToList(); | |
| var userAchievements = await _userAchievementRepo.GetByUserAndAchievementIdsAsync(userId, achievementIds); | |
| foreach (var achievement in triggeredAchievements) | |
| { | |
| var ua = userAchievements.FirstOrDefault(u => u.AchievementId == achievement.Id); | |
| // If user doesn't have a tracking record yet, create it | |
| if (ua == null) | |
| { | |
| ua = new UserAchievement | |
| { | |
| UserId = userId, | |
| AchievementId = achievement.Id, | |
| CurrentValue = 0, | |
| IsUnlocked = false | |
| }; | |
| ua = await _userAchievementRepo.CreateAsync(ua); | |
| } | |
| // Skip if already unlocked | |
| if (ua.IsUnlocked) continue; | |
| // Update progress | |
| if (achievement.Type == AchievementType.Accumulative || achievement.Type == AchievementType.SingleAction) | |
| { | |
| ua.CurrentValue += amount; | |
| } | |
| // Check for unlock | |
| if (ua.CurrentValue >= achievement.TargetValue) | |
| { | |
| ua.CurrentValue = achievement.TargetValue; | |
| ua.IsUnlocked = true; | |
| ua.UnlockedAt = DateTime.UtcNow; | |
| } | |
| await _userAchievementRepo.UpdateAsync(ua); | |
| } | |
| } | |
| public async Task SeedAchievementsAsync() | |
| { | |
| var seedData = new List<Achievement> | |
| { | |
| new Achievement { Title = "Blank Canvas", Description = "Create your first project (Graph).", Category = "Projects", Type = AchievementType.SingleAction, TargetValue = 1, RewardPoints = 10, TriggerEventName = "GraphCreated" }, | |
| new Achievement { Title = "Connecting the Dots", Description = "Create 10 connections (Edges) between tasks.", Category = "Tasks", Type = AchievementType.Accumulative, TargetValue = 10, RewardPoints = 50, TriggerEventName = "EdgeCreated" }, | |
| new Achievement { Title = "Sketch Master", Description = "Create 100 task nodes overall.", Category = "Tasks", Type = AchievementType.Accumulative, TargetValue = 100, RewardPoints = 100, TriggerEventName = "NodeCreated" }, | |
| new Achievement { Title = "Daily Doodler", Description = "Track habits 7 times.", Category = "Habits", Type = AchievementType.Accumulative, TargetValue = 7, RewardPoints = 70, TriggerEventName = "HabitTracked" }, | |
| new Achievement { Title = "Decision Maker", Description = "Complete 50 tasks.", Category = "Tasks", Type = AchievementType.Accumulative, TargetValue = 50, RewardPoints = 50, TriggerEventName = "TaskCompleted" }, | |
| new Achievement { Title = "Clear Desk", Description = "Delete 5 projects.", Category = "System", Type = AchievementType.Accumulative, TargetValue = 5, RewardPoints = 20, TriggerEventName = "GraphDeleted" }, | |
| new Achievement { Title = "First Milestone", Description = "Complete your first task.", Category = "Tasks", Type = AchievementType.SingleAction, TargetValue = 1, RewardPoints = 15, TriggerEventName = "TaskCompleted" }, | |
| new Achievement { Title = "Self Portrait", Description = "Upload your profile avatar.", Category = "System", Type = AchievementType.SingleAction, TargetValue = 1, RewardPoints = 15, TriggerEventName = "AvatarUploaded" } | |
| }; | |
| var existing = await _achievementRepo.GetByTitlesAsync(seedData.Select(s => s.Title)); | |
| var existingTitles = existing.Select(e => e.Title).ToList(); | |
| var toAdd = seedData.Where(s => !existingTitles.Contains(s.Title)).ToList(); | |
| if (toAdd.Any()) | |
| { | |
| await _achievementRepo.AddRangeAsync(toAdd); | |
| } | |
| } | |
| } | |
| } | |