Spaces:
Running
Running
| using FlowAPI.Application.DTOs.Habit; | |
| using FlowAPI.Application.Interfaces; | |
| using FlowAPI.Domain.Entities; | |
| namespace FlowAPI.Application.Services | |
| { | |
| public class HabitService : IHabitService | |
| { | |
| private readonly IHabitRecordRepository _repo; | |
| private readonly IUserRepository _userRepo; | |
| private readonly IAchievementService _achievementService; | |
| public HabitService(IHabitRecordRepository repo, IUserRepository userRepo, IAchievementService achievementService) | |
| { | |
| _repo = repo; | |
| _userRepo = userRepo; | |
| _achievementService = achievementService; | |
| } | |
| public async Task<IEnumerable<HabitRecordResponseDto>> GetAllByUserIdAsync(Guid userId) | |
| { | |
| var records = await _repo.GetAllByUserIdAsync(userId); | |
| return records.Select(MapToDto); | |
| } | |
| public async Task<HabitRecordResponseDto> RecordHabitAsync(CreateHabitRecordDto dto) | |
| { | |
| var user = await _userRepo.GetByIdAsync(dto.UserId); | |
| if (user == null) throw new Exception("User not found."); | |
| var targetDate = dto.CheckedDate ?? DateTime.UtcNow; | |
| var existing = await _repo.GetRecordForDateAsync(dto.UserId, dto.HabitName, targetDate.Date); | |
| if (existing is not null) return MapToDto(existing); | |
| var tier = user.SubscriptionTier?.Trim(); | |
| bool isFree = string.IsNullOrEmpty(tier) || tier == "Free"; | |
| if (isFree) | |
| { | |
| var allRecords = await _repo.GetAllByUserIdAsync(dto.UserId); | |
| var distinctHabits = allRecords.Select(r => r.HabitName).Distinct().ToList(); | |
| if (!distinctHabits.Contains(dto.HabitName) && distinctHabits.Count >= 3) | |
| { | |
| throw new InvalidOperationException("Free tier is limited to 3 habits."); | |
| } | |
| } | |
| var record = new HabitRecord | |
| { | |
| Id = Guid.NewGuid(), | |
| UserId = dto.UserId, | |
| HabitName = dto.HabitName, | |
| CheckedDate = targetDate | |
| }; | |
| var created = await _repo.CreateAsync(record); | |
| await _achievementService.ProcessEventAsync(dto.UserId, "HabitTracked"); | |
| return MapToDto(created); | |
| } | |
| public async Task<int> GetStreakAsync(Guid userId, string habitName) | |
| { | |
| var records = await _repo.GetAllByUserIdAsync(userId); | |
| var habitRecords = records | |
| .Where(r => r.HabitName == habitName) | |
| .OrderByDescending(r => r.CheckedDate) | |
| .ToList(); | |
| if (!habitRecords.Any()) return 0; | |
| int streak = 0; | |
| DateTime expectedDate = DateTime.UtcNow.Date; | |
| foreach (var record in habitRecords) | |
| { | |
| if (record.CheckedDate.Date == expectedDate) | |
| { | |
| streak++; | |
| expectedDate = expectedDate.AddDays(-1); | |
| } | |
| else if (record.CheckedDate.Date > expectedDate) | |
| { | |
| continue; | |
| } | |
| else | |
| { | |
| break; | |
| } | |
| } | |
| return streak; | |
| } | |
| private static HabitRecordResponseDto MapToDto(HabitRecord record) => new() | |
| { | |
| Id = record.Id, | |
| UserId = record.UserId, | |
| HabitName = record.HabitName, | |
| CheckedDate = record.CheckedDate | |
| }; | |
| } | |
| } | |