Wave 2 — 3 parallel agents fixing P1 issues: Validators (57 new FluentValidation validators): - ads-manager: 10 validators for all commands - ads-billing: 3 validators for all commands - ads-tracking: 2 validators for missing commands - ads-analytics: 1 validator for CreateReport - social: 8 validators for all commands - mining: 16 validators for all commands - mission: 4 validators for all commands - promotion: 13 validators for all commands Missing handlers (10 implemented): - promotion: ExchangeVoucher, PurchaseVoucher, SearchVouchers, GetCampaignStatistics, GetCampaignVouchers - mission: GetUserMissionProgress - mkt-facebook: GetConversations, GetCustomers - ads-manager: ListAudiences, GetAudienceById All validators use bilingual messages (EN/VI) and are auto-registered via MediatR ValidatorBehavior pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
276 lines
12 KiB
C#
276 lines
12 KiB
C#
using MediatR;
|
|
using MissionService.Domain.AggregatesModel.MissionAggregate;
|
|
using MissionService.Domain.AggregatesModel.TaskAggregate;
|
|
using TaskStatus = MissionService.Domain.AggregatesModel.TaskAggregate.TaskStatus;
|
|
|
|
namespace MissionService.API.Application.Queries;
|
|
|
|
/// <summary>
|
|
/// EN: Handler for GetAvailableMissionsQuery
|
|
/// VI: Handler cho GetAvailableMissionsQuery
|
|
/// </summary>
|
|
public class GetAvailableMissionsQueryHandler : IRequestHandler<GetAvailableMissionsQuery, MissionsListResult>
|
|
{
|
|
private readonly IMissionRepository _missionRepository;
|
|
private readonly IUserTaskRepository _taskRepository;
|
|
|
|
public GetAvailableMissionsQueryHandler(
|
|
IMissionRepository missionRepository,
|
|
IUserTaskRepository taskRepository)
|
|
{
|
|
_missionRepository = missionRepository;
|
|
_taskRepository = taskRepository;
|
|
}
|
|
|
|
public async Task<MissionsListResult> Handle(GetAvailableMissionsQuery request, CancellationToken cancellationToken)
|
|
{
|
|
var missions = await _missionRepository.GetActiveMissionsAsync(cancellationToken);
|
|
var userTasks = await _taskRepository.GetByUserIdAsync(request.UserId, cancellationToken);
|
|
|
|
var summaries = missions.Select(m => MapToSummary(m, userTasks)).ToList();
|
|
return new MissionsListResult(summaries);
|
|
}
|
|
|
|
private static MissionSummaryDto MapToSummary(Mission mission, IReadOnlyList<UserTask> userTasks)
|
|
{
|
|
var userTask = userTasks.FirstOrDefault(t => t.MissionId == mission.Id &&
|
|
(t.Status == TaskStatus.InProgress || t.Status == TaskStatus.PendingVerification));
|
|
|
|
return new MissionSummaryDto(
|
|
Id: mission.Id,
|
|
Code: mission.Code,
|
|
Title: mission.TitleEn,
|
|
Description: mission.DescriptionEn,
|
|
Type: mission.Type.Name,
|
|
Category: mission.Category.Name,
|
|
RewardPoints: mission.Reward.Points,
|
|
Frequency: mission.Frequency.Name,
|
|
MaxCompletions: mission.MaxCompletions,
|
|
IsAvailable: mission.IsAvailable(),
|
|
UserProgress: userTask != null ? MapToProgress(userTask) : null);
|
|
}
|
|
|
|
private static UserTaskProgressDto MapToProgress(UserTask task)
|
|
{
|
|
return new UserTaskProgressDto(
|
|
TaskId: task.Id,
|
|
CurrentValue: task.Progress.CurrentValue,
|
|
TargetValue: task.Progress.TargetValue,
|
|
PercentComplete: task.Progress.PercentComplete,
|
|
Status: task.Status.Name,
|
|
StartedAt: task.StartedAt,
|
|
CompletedAt: task.CompletedAt,
|
|
RewardClaimed: task.RewardClaimed);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// EN: Handler for GetMissionDetailsQuery
|
|
/// VI: Handler cho GetMissionDetailsQuery
|
|
/// </summary>
|
|
public class GetMissionDetailsQueryHandler : IRequestHandler<GetMissionDetailsQuery, MissionDetailsResult?>
|
|
{
|
|
private readonly IMissionRepository _missionRepository;
|
|
private readonly IUserTaskRepository _taskRepository;
|
|
|
|
public GetMissionDetailsQueryHandler(
|
|
IMissionRepository missionRepository,
|
|
IUserTaskRepository taskRepository)
|
|
{
|
|
_missionRepository = missionRepository;
|
|
_taskRepository = taskRepository;
|
|
}
|
|
|
|
public async Task<MissionDetailsResult?> Handle(GetMissionDetailsQuery request, CancellationToken cancellationToken)
|
|
{
|
|
var mission = await _missionRepository.GetByIdAsync(request.MissionId, cancellationToken);
|
|
if (mission == null) return null;
|
|
|
|
UserTaskProgressDto? userProgress = null;
|
|
if (request.UserId.HasValue)
|
|
{
|
|
var userTask = await _taskRepository.GetByUserAndMissionAsync(
|
|
request.UserId.Value, request.MissionId, cancellationToken);
|
|
if (userTask != null)
|
|
{
|
|
userProgress = new UserTaskProgressDto(
|
|
TaskId: userTask.Id,
|
|
CurrentValue: userTask.Progress.CurrentValue,
|
|
TargetValue: userTask.Progress.TargetValue,
|
|
PercentComplete: userTask.Progress.PercentComplete,
|
|
Status: userTask.Status.Name,
|
|
StartedAt: userTask.StartedAt,
|
|
CompletedAt: userTask.CompletedAt,
|
|
RewardClaimed: userTask.RewardClaimed);
|
|
}
|
|
}
|
|
|
|
return new MissionDetailsResult(
|
|
Id: mission.Id,
|
|
Code: mission.Code,
|
|
TitleEn: mission.TitleEn,
|
|
TitleVi: mission.TitleVi,
|
|
DescriptionEn: mission.DescriptionEn,
|
|
DescriptionVi: mission.DescriptionVi,
|
|
Type: mission.Type.Name,
|
|
Category: mission.Category.Name,
|
|
Reward: new MissionRewardDto(
|
|
mission.Reward.Points,
|
|
mission.Reward.MiningBoostPercent,
|
|
mission.Reward.ExperiencePoints,
|
|
mission.Reward.BadgeId),
|
|
Frequency: mission.Frequency.Name,
|
|
MaxCompletions: mission.MaxCompletions,
|
|
StartDate: mission.StartDate,
|
|
EndDate: mission.EndDate,
|
|
Status: mission.Status.Name,
|
|
Rules: mission.Rules.Select(r => new MissionRuleDto(r.RuleType, r.Operator, r.Value)).ToList(),
|
|
UserProgress: userProgress);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// EN: Handler for GetMissionsByCategoryQuery
|
|
/// VI: Handler cho GetMissionsByCategoryQuery
|
|
/// </summary>
|
|
public class GetMissionsByCategoryQueryHandler : IRequestHandler<GetMissionsByCategoryQuery, MissionsListResult>
|
|
{
|
|
private readonly IMissionRepository _missionRepository;
|
|
|
|
public GetMissionsByCategoryQueryHandler(IMissionRepository missionRepository)
|
|
{
|
|
_missionRepository = missionRepository;
|
|
}
|
|
|
|
public async Task<MissionsListResult> Handle(GetMissionsByCategoryQuery request, CancellationToken cancellationToken)
|
|
{
|
|
var category = MissionCategory.FromDisplayName<MissionCategory>(request.Category);
|
|
var missions = await _missionRepository.GetByCategoryAsync(category, cancellationToken);
|
|
|
|
var summaries = missions.Select(m => new MissionSummaryDto(
|
|
Id: m.Id,
|
|
Code: m.Code,
|
|
Title: m.TitleEn,
|
|
Description: m.DescriptionEn,
|
|
Type: m.Type.Name,
|
|
Category: m.Category.Name,
|
|
RewardPoints: m.Reward.Points,
|
|
Frequency: m.Frequency.Name,
|
|
MaxCompletions: m.MaxCompletions,
|
|
IsAvailable: m.IsAvailable(),
|
|
UserProgress: null)).ToList();
|
|
|
|
return new MissionsListResult(summaries);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// EN: Handler for GetUserMissionProgressQuery - returns user's overall mission progress.
|
|
/// VI: Handler cho GetUserMissionProgressQuery - trả về tiến độ tổng thể của user trên các mission.
|
|
/// </summary>
|
|
public class GetUserMissionProgressQueryHandler : IRequestHandler<GetUserMissionProgressQuery, UserMissionProgressResult>
|
|
{
|
|
private readonly IMissionRepository _missionRepository;
|
|
private readonly IUserTaskRepository _taskRepository;
|
|
|
|
public GetUserMissionProgressQueryHandler(
|
|
IMissionRepository missionRepository,
|
|
IUserTaskRepository taskRepository)
|
|
{
|
|
_missionRepository = missionRepository;
|
|
_taskRepository = taskRepository;
|
|
}
|
|
|
|
public async Task<UserMissionProgressResult> Handle(GetUserMissionProgressQuery request, CancellationToken cancellationToken)
|
|
{
|
|
// EN: Get all active missions and user's tasks / VI: Lấy tất cả missions đang hoạt động và tasks của user
|
|
var missions = await _missionRepository.GetActiveMissionsAsync(cancellationToken);
|
|
var userTasks = await _taskRepository.GetByUserIdAsync(request.UserId, cancellationToken);
|
|
|
|
// EN: Separate active and completed tasks / VI: Phân loại tasks đang hoạt động và đã hoàn thành
|
|
var activeTasks = userTasks
|
|
.Where(t => t.Status == TaskStatus.InProgress || t.Status == TaskStatus.PendingVerification)
|
|
.ToList();
|
|
|
|
var completedTasks = userTasks
|
|
.Where(t => t.Status == TaskStatus.Completed)
|
|
.ToList();
|
|
|
|
// EN: Calculate total points earned from claimed rewards
|
|
// VI: Tính tổng điểm đã nhận từ phần thưởng đã claim
|
|
var completedMissionIds = completedTasks.Select(t => t.MissionId).ToHashSet();
|
|
var totalPointsEarned = 0m;
|
|
foreach (var mission in missions.Where(m => completedMissionIds.Contains(m.Id)))
|
|
{
|
|
var taskCount = completedTasks.Count(t => t.MissionId == mission.Id && t.RewardClaimed);
|
|
totalPointsEarned += mission.Reward.Points * taskCount;
|
|
}
|
|
|
|
// EN: Map active missions with progress / VI: Map các mission đang hoạt động với tiến độ
|
|
var activeMissionSummaries = missions
|
|
.Where(m => activeTasks.Any(t => t.MissionId == m.Id))
|
|
.Select(m =>
|
|
{
|
|
var task = activeTasks.First(t => t.MissionId == m.Id);
|
|
return new MissionSummaryDto(
|
|
Id: m.Id,
|
|
Code: m.Code,
|
|
Title: m.TitleEn,
|
|
Description: m.DescriptionEn,
|
|
Type: m.Type.Name,
|
|
Category: m.Category.Name,
|
|
RewardPoints: m.Reward.Points,
|
|
Frequency: m.Frequency.Name,
|
|
MaxCompletions: m.MaxCompletions,
|
|
IsAvailable: m.IsAvailable(),
|
|
UserProgress: new UserTaskProgressDto(
|
|
TaskId: task.Id,
|
|
CurrentValue: task.Progress.CurrentValue,
|
|
TargetValue: task.Progress.TargetValue,
|
|
PercentComplete: task.Progress.PercentComplete,
|
|
Status: task.Status.Name,
|
|
StartedAt: task.StartedAt,
|
|
CompletedAt: task.CompletedAt,
|
|
RewardClaimed: task.RewardClaimed));
|
|
})
|
|
.ToList();
|
|
|
|
// EN: Map completed missions / VI: Map các mission đã hoàn thành
|
|
var completedMissionSummaries = missions
|
|
.Where(m => completedMissionIds.Contains(m.Id))
|
|
.Select(m =>
|
|
{
|
|
var task = completedTasks.First(t => t.MissionId == m.Id);
|
|
return new MissionSummaryDto(
|
|
Id: m.Id,
|
|
Code: m.Code,
|
|
Title: m.TitleEn,
|
|
Description: m.DescriptionEn,
|
|
Type: m.Type.Name,
|
|
Category: m.Category.Name,
|
|
RewardPoints: m.Reward.Points,
|
|
Frequency: m.Frequency.Name,
|
|
MaxCompletions: m.MaxCompletions,
|
|
IsAvailable: m.IsAvailable(),
|
|
UserProgress: new UserTaskProgressDto(
|
|
TaskId: task.Id,
|
|
CurrentValue: task.Progress.CurrentValue,
|
|
TargetValue: task.Progress.TargetValue,
|
|
PercentComplete: task.Progress.PercentComplete,
|
|
Status: task.Status.Name,
|
|
StartedAt: task.StartedAt,
|
|
CompletedAt: task.CompletedAt,
|
|
RewardClaimed: task.RewardClaimed));
|
|
})
|
|
.ToList();
|
|
|
|
return new UserMissionProgressResult(
|
|
TotalMissions: missions.Count,
|
|
CompletedMissionsCount: completedMissionIds.Count,
|
|
InProgressMissions: activeTasks.Count,
|
|
TotalPointsEarned: totalPointsEarned,
|
|
ActiveMissions: activeMissionSummaries,
|
|
CompletedMissions: completedMissionSummaries);
|
|
}
|
|
}
|