Files
pos-system/services/mission-service-net/src/MissionService.API/Application/Queries/MissionQueryHandlers.cs
Ho Ngoc Hai 59b2cecaf2 feat(P1): add 57 validators + 10 missing handlers across 13 services
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>
2026-03-13 20:24:06 +07:00

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