using Application.Abstractions.Cache; using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Features.Config.Get; using Domain.Entities.Members.ValueObject; using Microsoft.EntityFrameworkCore; namespace Application.Features.Api.Rewards.GetToday; internal sealed class Handler(IAppDbContext db, ICacheService cache) : IQueryHandler { private static readonly TimeZoneInfo KstZone = ResolveKst(); public async Task Handle(Query request, CancellationToken ct) { var today = DateOnly.FromDateTime(TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, KstZone)); var counters = await db.MemberDailyRewardCounter.AsNoTracking() .Where(c => c.MemberID == request.MemberID && c.RewardDate == today) .ToListAsync(ct); var config = await LoadConfigAsync(ct); var actions = new List { BuildSummary(counters, RewardActionType.Post, "게시글", config?.PostDailyCount ?? 0, config?.PostDailyExp ?? 0, config?.PostDailyPoint ?? 0), BuildSummary(counters, RewardActionType.Comment, "댓글", config?.CommentDailyCount ?? 0, config?.CommentDailyExp ?? 0, config?.CommentDailyPoint ?? 0), BuildSummary(counters, RewardActionType.LikeGiven, "추천(누름)", config?.LikeGivenDailyCount ?? 0, config?.LikeGivenDailyExp ?? 0, 0), BuildSummary(counters, RewardActionType.LikeReceived, "추천(받음)", config?.LikeReceivedDailyCount ?? 0, 0, config?.LikeReceivedDailyPoint ?? 0), BuildSummary(counters, RewardActionType.Chat, "채팅", config?.ChatDailyCount ?? 0, config?.ChatDailyExp ?? 0, 0) }; var totalExp = counters.Sum(c => c.ExpTotal); var totalPoint = counters.Sum(c => c.PointTotal); return new Response(today.ToString("yyyy-MM-dd"), totalExp, totalPoint, actions); } private static ActionSummary BuildSummary( List counters, RewardActionType action, string name, int countCap, int expCap, int pointCap) { var c = counters.FirstOrDefault(x => x.ActionType == action); var count = c?.Count ?? 0; var exp = c?.ExpTotal ?? 0; var point = c?.PointTotal ?? 0; return new ActionSummary( (byte)action, name, count, exp, point, countCap > 0 ? Math.Max(0, countCap - count) : null, expCap > 0 ? Math.Max(0, expCap - exp) : null, pointCap > 0 ? Math.Max(0, pointCap - point) : null ); } private async Task LoadConfigAsync(CancellationToken ct) { var cached = await cache.GetAsync(CacheKeys.Config, ct); if (cached is not null) { var r = cached.Reward; return new Response.RewardConfigView( r.PostDailyCount, r.PostDailyExp, r.PostDailyPoint, r.CommentDailyCount, r.CommentDailyExp, r.CommentDailyPoint, r.LikeGivenDailyCount, r.LikeGivenDailyExp, r.LikeReceivedDailyCount, r.LikeReceivedDailyPoint, r.ChatDailyCount, r.ChatDailyExp); } var entity = await db.Config.AsNoTracking().OrderByDescending(c => c.ID).Select(c => c.Reward).FirstOrDefaultAsync(ct); if (entity is null) { return null; } return new Response.RewardConfigView( entity.PostDailyCount, entity.PostDailyExp, entity.PostDailyPoint, entity.CommentDailyCount, entity.CommentDailyExp, entity.CommentDailyPoint, entity.LikeGivenDailyCount, entity.LikeGivenDailyExp, entity.LikeReceivedDailyCount, entity.LikeReceivedDailyPoint, entity.ChatDailyCount, entity.ChatDailyExp); } private static TimeZoneInfo ResolveKst() { if (TimeZoneInfo.TryFindSystemTimeZoneById("Asia/Seoul", out var tz)) { return tz; } if (TimeZoneInfo.TryFindSystemTimeZoneById("Korea Standard Time", out tz)) { return tz; } return TimeZoneInfo.CreateCustomTimeZone("KST", TimeSpan.FromHours(9), "KST", "KST"); } }