From 59b2cecaf22600676209cc123cc0ede9684cf026 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Fri, 13 Mar 2026 20:24:06 +0700 Subject: [PATCH] feat(P1): add 57 validators + 10 missing handlers across 13 services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../CreateReportCommandValidator.cs | 38 +++++ .../Validations/AddFundsCommandValidator.cs | 24 +++ .../ChargeAdvertiserCommandValidator.cs | 38 +++++ .../CreateBillingAccountCommandValidator.cs | 26 +++ .../Queries/AudienceQueryHandlers.cs | 115 +++++++++++++ .../ActivateCampaignCommandValidator.cs | 18 ++ .../Validations/ApproveAdCommandValidator.cs | 18 ++ .../Validations/CreateAdCommandValidator.cs | 57 +++++++ .../CreateAdSetCommandValidator.cs | 66 ++++++++ .../CreateCampaignCommandValidator.cs | 59 +++++++ .../DeleteCampaignCommandValidator.cs | 18 ++ .../PauseCampaignCommandValidator.cs | 18 ++ .../Validations/RejectAdCommandValidator.cs | 24 +++ .../SubmitAdForReviewCommandValidator.cs | 18 ++ .../UpdateCampaignCommandValidator.cs | 29 ++++ .../RecordConversionCommandValidator.cs | 42 +++++ .../TrackPixelEventCommandValidator.cs | 42 +++++ .../AcceptCircleInviteCommandValidator.cs | 20 +++ .../AdjustMinerPointsCommandValidator.cs | 24 +++ .../ApplyReferralCodeCommandValidator.cs | 21 +++ .../Validations/BanMinerCommandValidator.cs | 21 +++ .../ClaimMiningRewardCommandValidator.cs | 17 ++ .../CreateCircleCommandValidator.cs | 21 +++ .../InviteToCircleCommandValidator.cs | 24 +++ .../RemoveCircleMemberCommandValidator.cs | 24 +++ .../ResetMinerStreakCommandValidator.cs | 21 +++ .../RestoreMinerCommandValidator.cs | 17 ++ .../StartMiningCommandValidator.cs | 17 ++ .../SuspendMinerCommandValidator.cs | 21 +++ .../UpdateMiningConfigCommandValidator.cs | 26 +++ .../UpdateReferralConfigCommandValidator.cs | 22 +++ .../UpdateStreakConfigCommandValidator.cs | 18 ++ .../UpdateSystemConfigCommandValidator.cs | 40 +++++ .../Queries/MissionQueryHandlers.cs | 110 ++++++++++++ .../ClaimTaskRewardCommandValidator.cs | 20 +++ .../PerformCheckInCommandValidator.cs | 17 ++ .../StartMissionTaskCommandValidator.cs | 20 +++ .../UpdateTaskProgressCommandValidator.cs | 42 +++++ .../Queries/ConversationQueryHandlers.cs | 86 ++++++++++ .../Queries/CustomerQueryHandlers.cs | 67 ++++++++ .../Commands/VoucherCommandHandlers.cs | 157 ++++++++++++++++++ .../Application/Queries/AdminQueryHandlers.cs | 120 +++++++++++++ .../Queries/PromotionQueryHandlers.cs | 51 ++++++ .../ActivateCampaignCommandValidator.cs | 17 ++ .../CancelCampaignCommandValidator.cs | 17 ++ .../ClaimVoucherCommandValidator.cs | 20 +++ .../CompleteCampaignCommandValidator.cs | 17 ++ .../CreateCampaignCommandValidator.cs | 63 +++++++ .../DeleteCampaignCommandValidator.cs | 17 ++ .../ExchangeVoucherCommandValidator.cs | 23 +++ .../ExtendVoucherExpiryCommandValidator.cs | 21 +++ .../PauseCampaignCommandValidator.cs | 17 ++ .../PurchaseVoucherCommandValidator.cs | 23 +++ .../RedeemVoucherCommandValidator.cs | 24 +++ .../RevokeVoucherCommandValidator.cs | 21 +++ .../UpdateCampaignCommandValidator.cs | 34 ++++ .../AdminDeleteBlockCommandValidator.cs | 17 ++ ...AdminDeleteRelationshipCommandValidator.cs | 17 ++ .../Validations/BlockUserCommandValidator.cs | 28 ++++ .../Validations/FollowUserCommandValidator.cs | 24 +++ .../RespondToFriendRequestCommandValidator.cs | 20 +++ .../SendFriendRequestCommandValidator.cs | 24 +++ .../UnblockUserCommandValidator.cs | 24 +++ .../UnfollowUserCommandValidator.cs | 24 +++ 64 files changed, 2186 insertions(+) create mode 100644 services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/CreateReportCommandValidator.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/AddFundsCommandValidator.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/ChargeAdvertiserCommandValidator.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/CreateBillingAccountCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/AudienceQueryHandlers.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/ActivateCampaignCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/ApproveAdCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateAdCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateAdSetCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateCampaignCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/DeleteCampaignCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/PauseCampaignCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/RejectAdCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/SubmitAdForReviewCommandValidator.cs create mode 100644 services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/UpdateCampaignCommandValidator.cs create mode 100644 services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/RecordConversionCommandValidator.cs create mode 100644 services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/TrackPixelEventCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/AcceptCircleInviteCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/AdjustMinerPointsCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/ApplyReferralCodeCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/BanMinerCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/ClaimMiningRewardCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/CreateCircleCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/InviteToCircleCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/RemoveCircleMemberCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/ResetMinerStreakCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/RestoreMinerCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/StartMiningCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/SuspendMinerCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/UpdateMiningConfigCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/UpdateReferralConfigCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/UpdateStreakConfigCommandValidator.cs create mode 100644 services/mining-service-net/src/MiningService.API/Application/Validations/UpdateSystemConfigCommandValidator.cs create mode 100644 services/mission-service-net/src/MissionService.API/Application/Validations/ClaimTaskRewardCommandValidator.cs create mode 100644 services/mission-service-net/src/MissionService.API/Application/Validations/PerformCheckInCommandValidator.cs create mode 100644 services/mission-service-net/src/MissionService.API/Application/Validations/StartMissionTaskCommandValidator.cs create mode 100644 services/mission-service-net/src/MissionService.API/Application/Validations/UpdateTaskProgressCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/ActivateCampaignCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/CancelCampaignCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/ClaimVoucherCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/CompleteCampaignCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/CreateCampaignCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/DeleteCampaignCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/ExchangeVoucherCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/ExtendVoucherExpiryCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/PauseCampaignCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/PurchaseVoucherCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/RedeemVoucherCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/RevokeVoucherCommandValidator.cs create mode 100644 services/promotion-service-net/src/PromotionService.API/Application/Validations/UpdateCampaignCommandValidator.cs create mode 100644 services/social-service-net/src/SocialService.API/Application/Validations/AdminDeleteBlockCommandValidator.cs create mode 100644 services/social-service-net/src/SocialService.API/Application/Validations/AdminDeleteRelationshipCommandValidator.cs create mode 100644 services/social-service-net/src/SocialService.API/Application/Validations/BlockUserCommandValidator.cs create mode 100644 services/social-service-net/src/SocialService.API/Application/Validations/FollowUserCommandValidator.cs create mode 100644 services/social-service-net/src/SocialService.API/Application/Validations/RespondToFriendRequestCommandValidator.cs create mode 100644 services/social-service-net/src/SocialService.API/Application/Validations/SendFriendRequestCommandValidator.cs create mode 100644 services/social-service-net/src/SocialService.API/Application/Validations/UnblockUserCommandValidator.cs create mode 100644 services/social-service-net/src/SocialService.API/Application/Validations/UnfollowUserCommandValidator.cs diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/CreateReportCommandValidator.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/CreateReportCommandValidator.cs new file mode 100644 index 00000000..80265f3f --- /dev/null +++ b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/CreateReportCommandValidator.cs @@ -0,0 +1,38 @@ +using FluentValidation; +using AdsAnalyticsService.API.Application.Commands; + +namespace AdsAnalyticsService.API.Application.Validations; + +/// +/// EN: Validator for CreateReportCommand. +/// VI: Validator cho CreateReportCommand. +/// +public class CreateReportCommandValidator : AbstractValidator +{ + public CreateReportCommandValidator() + { + RuleFor(x => x.AdvertiserId) + .NotEmpty() + .WithMessage("Advertiser ID is required / Advertiser ID là bắt buộc"); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Report name is required / Tên báo cáo là bắt buộc") + .MaximumLength(200) + .WithMessage("Report name max 200 characters / Tên báo cáo tối đa 200 ký tự"); + + RuleFor(x => x.ReportType) + .IsInEnum() + .WithMessage("Report type is invalid / Loại báo cáo không hợp lệ"); + + RuleFor(x => x.StartDate) + .NotEmpty() + .WithMessage("Start date is required / Ngày bắt đầu là bắt buộc"); + + RuleFor(x => x.EndDate) + .NotEmpty() + .WithMessage("End date is required / Ngày kết thúc là bắt buộc") + .GreaterThan(x => x.StartDate) + .WithMessage("End date must be after start date / Ngày kết thúc phải sau ngày bắt đầu"); + } +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/AddFundsCommandValidator.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/AddFundsCommandValidator.cs new file mode 100644 index 00000000..fbdf3519 --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/AddFundsCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using AdsBillingService.API.Application.Commands; + +namespace AdsBillingService.API.Application.Validations; + +/// +/// EN: Validator for AddFundsCommand. +/// VI: Validator cho AddFundsCommand. +/// +public class AddFundsCommandValidator : AbstractValidator +{ + public AddFundsCommandValidator() + { + RuleFor(x => x.AccountId) + .NotEmpty() + .WithMessage("Account ID is required / Account ID là bắt buộc"); + + RuleFor(x => x.Amount) + .GreaterThan(0) + .WithMessage("Amount must be greater than 0 / Số tiền phải lớn hơn 0") + .LessThanOrEqualTo(1_000_000_000) + .WithMessage("Amount must not exceed 1,000,000,000 / Số tiền không được vượt quá 1,000,000,000"); + } +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/ChargeAdvertiserCommandValidator.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/ChargeAdvertiserCommandValidator.cs new file mode 100644 index 00000000..395b6476 --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/ChargeAdvertiserCommandValidator.cs @@ -0,0 +1,38 @@ +using FluentValidation; +using AdsBillingService.API.Application.Commands; + +namespace AdsBillingService.API.Application.Validations; + +/// +/// EN: Validator for ChargeAdvertiserCommand. +/// VI: Validator cho ChargeAdvertiserCommand. +/// +public class ChargeAdvertiserCommandValidator : AbstractValidator +{ + private static readonly string[] ValidChargeTypes = ["impression", "click"]; + + public ChargeAdvertiserCommandValidator() + { + RuleFor(x => x.AdvertiserId) + .NotEmpty() + .WithMessage("Advertiser ID is required / Advertiser ID là bắt buộc"); + + RuleFor(x => x.CampaignId) + .NotEmpty() + .WithMessage("Campaign ID is required / Campaign ID là bắt buộc"); + + RuleFor(x => x.AdId) + .NotEmpty() + .WithMessage("Ad ID is required / Ad ID là bắt buộc"); + + RuleFor(x => x.ChargeType) + .NotEmpty() + .WithMessage("Charge type is required / Loại charge là bắt buộc") + .Must(ct => ValidChargeTypes.Contains(ct)) + .WithMessage("Charge type must be 'impression' or 'click' / Loại charge phải là 'impression' hoặc 'click'"); + + RuleFor(x => x.Amount) + .GreaterThan(0) + .WithMessage("Amount must be greater than 0 / Số tiền phải lớn hơn 0"); + } +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/CreateBillingAccountCommandValidator.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/CreateBillingAccountCommandValidator.cs new file mode 100644 index 00000000..1f4d29ca --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/CreateBillingAccountCommandValidator.cs @@ -0,0 +1,26 @@ +using FluentValidation; +using AdsBillingService.API.Application.Commands; + +namespace AdsBillingService.API.Application.Validations; + +/// +/// EN: Validator for CreateBillingAccountCommand. +/// VI: Validator cho CreateBillingAccountCommand. +/// +public class CreateBillingAccountCommandValidator : AbstractValidator +{ + private static readonly string[] ValidPaymentMethods = ["prepaid", "postpaid"]; + + public CreateBillingAccountCommandValidator() + { + RuleFor(x => x.AdvertiserId) + .NotEmpty() + .WithMessage("Advertiser ID is required / Advertiser ID là bắt buộc"); + + RuleFor(x => x.PaymentMethod) + .NotEmpty() + .WithMessage("Payment method is required / Phương thức thanh toán là bắt buộc") + .Must(pm => ValidPaymentMethods.Contains(pm)) + .WithMessage("Payment method must be 'prepaid' or 'postpaid' / Phương thức thanh toán phải là 'prepaid' hoặc 'postpaid'"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/AudienceQueryHandlers.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/AudienceQueryHandlers.cs new file mode 100644 index 00000000..53bd71de --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Queries/AudienceQueryHandlers.cs @@ -0,0 +1,115 @@ +using AdsManagerService.API.Controllers; +using AdsManagerService.Domain.AggregatesModel.AudienceAggregate; +using AdsManagerService.Infrastructure; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace AdsManagerService.API.Application.Queries; + +/// +/// EN: Handler for ListAudiencesQuery - returns audiences for an advertiser. +/// VI: Handler cho ListAudiencesQuery - trả về audiences cho nhà quảng cáo. +/// +public class ListAudiencesQueryHandler : IRequestHandler> +{ + private readonly AdsManagerServiceContext _context; + + public ListAudiencesQueryHandler(AdsManagerServiceContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task> Handle(ListAudiencesQuery request, CancellationToken cancellationToken) + { + // EN: Get custom audiences for the advertiser / VI: Lấy custom audiences cho nhà quảng cáo + var customAudiences = await _context.CustomAudiences + .AsNoTracking() + .Where(a => a.AdvertiserId == request.AdvertiserId) + .OrderByDescending(a => a.CreatedAt) + .Select(a => new AudienceDto + { + Id = a.Id, + Name = a.Name, + Type = "custom", + Size = a.Size, + CreatedAt = a.CreatedAt + }) + .ToListAsync(cancellationToken); + + // EN: Get lookalike audiences for the advertiser / VI: Lấy lookalike audiences cho nhà quảng cáo + var lookalikeAudiences = await _context.LookalikeAudiences + .AsNoTracking() + .Where(a => a.AdvertiserId == request.AdvertiserId) + .OrderByDescending(a => a.CreatedAt) + .Select(a => new AudienceDto + { + Id = a.Id, + Name = a.Name, + Type = "lookalike", + Size = a.Size, + CreatedAt = a.CreatedAt + }) + .ToListAsync(cancellationToken); + + // EN: Merge and sort by creation date / VI: Gộp và sắp xếp theo ngày tạo + var allAudiences = customAudiences + .Concat(lookalikeAudiences) + .OrderByDescending(a => a.CreatedAt) + .ToList(); + + return allAudiences; + } +} + +/// +/// EN: Handler for GetAudienceByIdQuery - returns a single audience by ID. +/// VI: Handler cho GetAudienceByIdQuery - trả về một audience theo ID. +/// +public class GetAudienceByIdQueryHandler : IRequestHandler +{ + private readonly AdsManagerServiceContext _context; + + public GetAudienceByIdQueryHandler(AdsManagerServiceContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task Handle(GetAudienceByIdQuery request, CancellationToken cancellationToken) + { + // EN: Try custom audience first / VI: Thử custom audience trước + var customAudience = await _context.CustomAudiences + .AsNoTracking() + .FirstOrDefaultAsync(a => a.Id == request.AudienceId, cancellationToken); + + if (customAudience != null) + { + return new AudienceDto + { + Id = customAudience.Id, + Name = customAudience.Name, + Type = "custom", + Size = customAudience.Size, + CreatedAt = customAudience.CreatedAt + }; + } + + // EN: Try lookalike audience / VI: Thử lookalike audience + var lookalikeAudience = await _context.LookalikeAudiences + .AsNoTracking() + .FirstOrDefaultAsync(a => a.Id == request.AudienceId, cancellationToken); + + if (lookalikeAudience != null) + { + return new AudienceDto + { + Id = lookalikeAudience.Id, + Name = lookalikeAudience.Name, + Type = "lookalike", + Size = lookalikeAudience.Size, + CreatedAt = lookalikeAudience.CreatedAt + }; + } + + return null; + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/ActivateCampaignCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/ActivateCampaignCommandValidator.cs new file mode 100644 index 00000000..6fe3f8b4 --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/ActivateCampaignCommandValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using AdsManagerService.API.Application.Commands; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for ActivateCampaignCommand. +/// VI: Validator cho ActivateCampaignCommand. +/// +public class ActivateCampaignCommandValidator : AbstractValidator +{ + public ActivateCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty() + .WithMessage("Campaign ID is required / Campaign ID là bắt buộc"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/ApproveAdCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/ApproveAdCommandValidator.cs new file mode 100644 index 00000000..2ee34b62 --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/ApproveAdCommandValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using AdsManagerService.API.Controllers; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for ApproveAdCommand. +/// VI: Validator cho ApproveAdCommand. +/// +public class ApproveAdCommandValidator : AbstractValidator +{ + public ApproveAdCommandValidator() + { + RuleFor(x => x.AdId) + .NotEmpty() + .WithMessage("Ad ID is required / Ad ID là bắt buộc"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateAdCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateAdCommandValidator.cs new file mode 100644 index 00000000..02bae277 --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateAdCommandValidator.cs @@ -0,0 +1,57 @@ +using FluentValidation; +using AdsManagerService.API.Application.Commands; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for CreateAdCommand. +/// VI: Validator cho CreateAdCommand. +/// +public class CreateAdCommandValidator : AbstractValidator +{ + private static readonly string[] ValidFormats = ["single_image", "single_video", "carousel", "collection", "stories"]; + + public CreateAdCommandValidator() + { + RuleFor(x => x.AdSetId) + .NotEmpty() + .WithMessage("Ad set ID is required / Ad set ID là bắt buộc"); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Ad name is required / Tên quảng cáo là bắt buộc") + .MaximumLength(200) + .WithMessage("Ad name max 200 characters / Tên quảng cáo tối đa 200 ký tự"); + + RuleFor(x => x.Format) + .NotEmpty() + .WithMessage("Ad format is required / Định dạng quảng cáo là bắt buộc") + .Must(f => ValidFormats.Contains(f)) + .WithMessage("Invalid format. Must be one of: single_image, single_video, carousel, collection, stories / Định dạng không hợp lệ"); + + RuleFor(x => x.Headline) + .MaximumLength(100) + .When(x => x.Headline != null) + .WithMessage("Headline max 100 characters / Tiêu đề tối đa 100 ký tự"); + + RuleFor(x => x.PrimaryText) + .MaximumLength(500) + .When(x => x.PrimaryText != null) + .WithMessage("Primary text max 500 characters / Nội dung chính tối đa 500 ký tự"); + + RuleFor(x => x.CallToAction) + .MaximumLength(50) + .When(x => x.CallToAction != null) + .WithMessage("Call to action max 50 characters / Lời kêu gọi tối đa 50 ký tự"); + + RuleFor(x => x.DestinationUrl) + .MaximumLength(2000) + .When(x => x.DestinationUrl != null) + .WithMessage("Destination URL max 2000 characters / URL đích tối đa 2000 ký tự"); + + RuleFor(x => x.CreativeUrl) + .MaximumLength(2000) + .When(x => x.CreativeUrl != null) + .WithMessage("Creative URL max 2000 characters / URL sáng tạo tối đa 2000 ký tự"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateAdSetCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateAdSetCommandValidator.cs new file mode 100644 index 00000000..f73b834e --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateAdSetCommandValidator.cs @@ -0,0 +1,66 @@ +using FluentValidation; +using AdsManagerService.API.Application.Commands; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for CreateAdSetCommand. +/// VI: Validator cho CreateAdSetCommand. +/// +public class CreateAdSetCommandValidator : AbstractValidator +{ + private static readonly string[] ValidBidTypes = ["cpc", "cpm", "ocpm", "automatic"]; + + public CreateAdSetCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty() + .WithMessage("Campaign ID is required / Campaign ID là bắt buộc"); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Ad set name is required / Tên ad set là bắt buộc") + .MaximumLength(200) + .WithMessage("Ad set name max 200 characters / Tên ad set tối đa 200 ký tự"); + + RuleFor(x => x.DailyBudget) + .GreaterThan(0) + .WithMessage("Daily budget must be greater than 0 / Ngân sách hàng ngày phải lớn hơn 0"); + + RuleFor(x => x.BidType) + .NotEmpty() + .WithMessage("Bid type is required / Loại bid là bắt buộc") + .Must(bt => ValidBidTypes.Contains(bt)) + .WithMessage("Bid type must be one of: cpc, cpm, ocpm, automatic / Loại bid phải là cpc, cpm, ocpm hoặc automatic"); + + RuleFor(x => x.BidAmount) + .GreaterThan(0) + .When(x => x.BidAmount.HasValue) + .WithMessage("Bid amount must be greater than 0 / Giá bid phải lớn hơn 0"); + + RuleFor(x => x.MinAge) + .InclusiveBetween(13, 65) + .When(x => x.MinAge.HasValue) + .WithMessage("Min age must be between 13 and 65 / Tuổi tối thiểu phải từ 13 đến 65"); + + RuleFor(x => x.MaxAge) + .InclusiveBetween(13, 65) + .When(x => x.MaxAge.HasValue) + .WithMessage("Max age must be between 13 and 65 / Tuổi tối đa phải từ 13 đến 65"); + + RuleFor(x => x.MaxAge) + .GreaterThanOrEqualTo(x => x.MinAge) + .When(x => x.MinAge.HasValue && x.MaxAge.HasValue) + .WithMessage("Max age must be greater than or equal to min age / Tuổi tối đa phải lớn hơn hoặc bằng tuổi tối thiểu"); + + RuleFor(x => x.Locations) + .MaximumLength(2000) + .When(x => x.Locations != null) + .WithMessage("Locations max 2000 characters / Vị trí tối đa 2000 ký tự"); + + RuleFor(x => x.Interests) + .MaximumLength(2000) + .When(x => x.Interests != null) + .WithMessage("Interests max 2000 characters / Sở thích tối đa 2000 ký tự"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateCampaignCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateCampaignCommandValidator.cs new file mode 100644 index 00000000..75ed59ce --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/CreateCampaignCommandValidator.cs @@ -0,0 +1,59 @@ +using FluentValidation; +using AdsManagerService.API.Application.Commands; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for CreateCampaignCommand. +/// VI: Validator cho CreateCampaignCommand. +/// +public class CreateCampaignCommandValidator : AbstractValidator +{ + private static readonly string[] ValidObjectives = ["awareness", "traffic", "conversion", "engagement", "app_install", "lead_generation"]; + private static readonly string[] ValidBudgetTypes = ["daily", "lifetime"]; + + public CreateCampaignCommandValidator() + { + RuleFor(x => x.AdvertiserId) + .NotEmpty() + .WithMessage("Advertiser ID is required / Advertiser ID là bắt buộc"); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Campaign name is required / Tên chiến dịch là bắt buộc") + .MaximumLength(200) + .WithMessage("Campaign name max 200 characters / Tên chiến dịch tối đa 200 ký tự"); + + RuleFor(x => x.Description) + .MaximumLength(1000) + .When(x => x.Description != null) + .WithMessage("Description max 1000 characters / Mô tả tối đa 1000 ký tự"); + + RuleFor(x => x.Objective) + .NotEmpty() + .WithMessage("Objective is required / Mục tiêu là bắt buộc") + .Must(o => ValidObjectives.Contains(o)) + .WithMessage("Invalid objective. Must be one of: awareness, traffic, conversion, engagement, app_install, lead_generation / Mục tiêu không hợp lệ"); + + RuleFor(x => x.BudgetType) + .NotEmpty() + .WithMessage("Budget type is required / Loại ngân sách là bắt buộc") + .Must(bt => ValidBudgetTypes.Contains(bt)) + .WithMessage("Budget type must be 'daily' or 'lifetime' / Loại ngân sách phải là 'daily' hoặc 'lifetime'"); + + RuleFor(x => x.BudgetAmount) + .GreaterThan(0) + .WithMessage("Budget amount must be greater than 0 / Ngân sách phải lớn hơn 0"); + + RuleFor(x => x.Currency) + .NotEmpty() + .WithMessage("Currency is required / Đơn vị tiền tệ là bắt buộc") + .MaximumLength(3) + .WithMessage("Currency code max 3 characters / Mã tiền tệ tối đa 3 ký tự"); + + RuleFor(x => x.EndDate) + .GreaterThan(x => x.StartDate) + .When(x => x.StartDate.HasValue && x.EndDate.HasValue) + .WithMessage("End date must be after start date / Ngày kết thúc phải sau ngày bắt đầu"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/DeleteCampaignCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/DeleteCampaignCommandValidator.cs new file mode 100644 index 00000000..24b3648c --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/DeleteCampaignCommandValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using AdsManagerService.API.Application.Commands; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for DeleteCampaignCommand. +/// VI: Validator cho DeleteCampaignCommand. +/// +public class DeleteCampaignCommandValidator : AbstractValidator +{ + public DeleteCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty() + .WithMessage("Campaign ID is required / Campaign ID là bắt buộc"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/PauseCampaignCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/PauseCampaignCommandValidator.cs new file mode 100644 index 00000000..a1467172 --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/PauseCampaignCommandValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using AdsManagerService.API.Application.Commands; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for PauseCampaignCommand. +/// VI: Validator cho PauseCampaignCommand. +/// +public class PauseCampaignCommandValidator : AbstractValidator +{ + public PauseCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty() + .WithMessage("Campaign ID is required / Campaign ID là bắt buộc"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/RejectAdCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/RejectAdCommandValidator.cs new file mode 100644 index 00000000..5f14467c --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/RejectAdCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using AdsManagerService.API.Controllers; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for RejectAdCommand. +/// VI: Validator cho RejectAdCommand. +/// +public class RejectAdCommandValidator : AbstractValidator +{ + public RejectAdCommandValidator() + { + RuleFor(x => x.AdId) + .NotEmpty() + .WithMessage("Ad ID is required / Ad ID là bắt buộc"); + + RuleFor(x => x.Reason) + .NotEmpty() + .WithMessage("Rejection reason is required / Lý do từ chối là bắt buộc") + .MaximumLength(1000) + .WithMessage("Rejection reason max 1000 characters / Lý do từ chối tối đa 1000 ký tự"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/SubmitAdForReviewCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/SubmitAdForReviewCommandValidator.cs new file mode 100644 index 00000000..a15c1ac5 --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/SubmitAdForReviewCommandValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using AdsManagerService.API.Application.Commands; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for SubmitAdForReviewCommand. +/// VI: Validator cho SubmitAdForReviewCommand. +/// +public class SubmitAdForReviewCommandValidator : AbstractValidator +{ + public SubmitAdForReviewCommandValidator() + { + RuleFor(x => x.AdId) + .NotEmpty() + .WithMessage("Ad ID is required / Ad ID là bắt buộc"); + } +} diff --git a/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/UpdateCampaignCommandValidator.cs b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/UpdateCampaignCommandValidator.cs new file mode 100644 index 00000000..5b728ca7 --- /dev/null +++ b/services/ads-manager-service-net/src/AdsManagerService.API/Application/Validations/UpdateCampaignCommandValidator.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using AdsManagerService.API.Application.Commands; + +namespace AdsManagerService.API.Application.Validations; + +/// +/// EN: Validator for UpdateCampaignCommand. +/// VI: Validator cho UpdateCampaignCommand. +/// +public class UpdateCampaignCommandValidator : AbstractValidator +{ + public UpdateCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty() + .WithMessage("Campaign ID is required / Campaign ID là bắt buộc"); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Campaign name is required / Tên chiến dịch là bắt buộc") + .MaximumLength(200) + .WithMessage("Campaign name max 200 characters / Tên chiến dịch tối đa 200 ký tự"); + + RuleFor(x => x.Description) + .MaximumLength(1000) + .When(x => x.Description != null) + .WithMessage("Description max 1000 characters / Mô tả tối đa 1000 ký tự"); + } +} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/RecordConversionCommandValidator.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/RecordConversionCommandValidator.cs new file mode 100644 index 00000000..32319e81 --- /dev/null +++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/RecordConversionCommandValidator.cs @@ -0,0 +1,42 @@ +using FluentValidation; +using AdsTrackingService.API.Application.Commands; + +namespace AdsTrackingService.API.Application.Validations; + +/// +/// EN: Validator for RecordConversionCommand. +/// VI: Validator cho RecordConversionCommand. +/// +public class RecordConversionCommandValidator : AbstractValidator +{ + public RecordConversionCommandValidator() + { + RuleFor(x => x.AdvertiserId) + .NotEmpty() + .WithMessage("Advertiser ID is required / Advertiser ID là bắt buộc"); + + RuleFor(x => x.CampaignId) + .NotEmpty() + .WithMessage("Campaign ID is required / Campaign ID là bắt buộc"); + + RuleFor(x => x.UserId) + .NotEmpty() + .WithMessage("User ID is required / User ID là bắt buộc"); + + RuleFor(x => x.ConversionType) + .NotEmpty() + .WithMessage("Conversion type is required / Loại conversion là bắt buộc") + .MaximumLength(100) + .WithMessage("Conversion type max 100 characters / Loại conversion tối đa 100 ký tự"); + + RuleFor(x => x.ConversionValue) + .GreaterThanOrEqualTo(0) + .WithMessage("Conversion value must be >= 0 / Giá trị conversion phải >= 0"); + + RuleFor(x => x.Currency) + .NotEmpty() + .WithMessage("Currency is required / Đơn vị tiền tệ là bắt buộc") + .MaximumLength(3) + .WithMessage("Currency code max 3 characters / Mã tiền tệ tối đa 3 ký tự"); + } +} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/TrackPixelEventCommandValidator.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/TrackPixelEventCommandValidator.cs new file mode 100644 index 00000000..4484f88a --- /dev/null +++ b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/TrackPixelEventCommandValidator.cs @@ -0,0 +1,42 @@ +using FluentValidation; +using AdsTrackingService.API.Application.Commands; + +namespace AdsTrackingService.API.Application.Validations; + +/// +/// EN: Validator for TrackPixelEventCommand. +/// VI: Validator cho TrackPixelEventCommand. +/// +public class TrackPixelEventCommandValidator : AbstractValidator +{ + public TrackPixelEventCommandValidator() + { + RuleFor(x => x.PixelCode) + .NotEmpty() + .WithMessage("Pixel code is required / Mã pixel là bắt buộc") + .MaximumLength(100) + .WithMessage("Pixel code max 100 characters / Mã pixel tối đa 100 ký tự"); + + RuleFor(x => x.AdId) + .NotEmpty() + .WithMessage("Ad ID is required / Ad ID là bắt buộc"); + + RuleFor(x => x.UserId) + .NotEmpty() + .WithMessage("User ID is required / User ID là bắt buộc"); + + RuleFor(x => x.EventType) + .IsInEnum() + .WithMessage("Event type is invalid / Loại sự kiện không hợp lệ"); + + RuleFor(x => x.UserAgent) + .MaximumLength(500) + .When(x => x.UserAgent != null) + .WithMessage("User agent max 500 characters / User agent tối đa 500 ký tự"); + + RuleFor(x => x.IpAddress) + .MaximumLength(45) + .When(x => x.IpAddress != null) + .WithMessage("IP address max 45 characters / Địa chỉ IP tối đa 45 ký tự"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/AcceptCircleInviteCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/AcceptCircleInviteCommandValidator.cs new file mode 100644 index 00000000..7a661cc5 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/AcceptCircleInviteCommandValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for AcceptCircleInviteCommand. +/// VI: Validator cho AcceptCircleInviteCommand. +/// +public class AcceptCircleInviteCommandValidator : AbstractValidator +{ + public AcceptCircleInviteCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.InviteId) + .NotEmpty().WithMessage("Invite ID is required / ID lời mời là bắt buộc"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/AdjustMinerPointsCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/AdjustMinerPointsCommandValidator.cs new file mode 100644 index 00000000..47c53c49 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/AdjustMinerPointsCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for AdjustMinerPointsCommand. +/// VI: Validator cho AdjustMinerPointsCommand. +/// +public class AdjustMinerPointsCommandValidator : AbstractValidator +{ + public AdjustMinerPointsCommandValidator() + { + RuleFor(x => x.MinerId) + .NotEmpty().WithMessage("Miner ID is required / ID thợ đào là bắt buộc"); + + RuleFor(x => x.Amount) + .NotEqual(0).WithMessage("Amount must not be zero / Số lượng không được bằng 0"); + + RuleFor(x => x.Reason) + .NotEmpty().WithMessage("Reason is required / Lý do là bắt buộc") + .MaximumLength(500).WithMessage("Reason max 500 chars / Lý do tối đa 500 ký tự"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/ApplyReferralCodeCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/ApplyReferralCodeCommandValidator.cs new file mode 100644 index 00000000..db60aa94 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/ApplyReferralCodeCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for ApplyReferralCodeCommand. +/// VI: Validator cho ApplyReferralCodeCommand. +/// +public class ApplyReferralCodeCommandValidator : AbstractValidator +{ + public ApplyReferralCodeCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.ReferralCode) + .NotEmpty().WithMessage("Referral code is required / Mã giới thiệu là bắt buộc") + .MaximumLength(50).WithMessage("Referral code max 50 chars / Mã giới thiệu tối đa 50 ký tự"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/BanMinerCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/BanMinerCommandValidator.cs new file mode 100644 index 00000000..0c9b9adc --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/BanMinerCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for BanMinerCommand. +/// VI: Validator cho BanMinerCommand. +/// +public class BanMinerCommandValidator : AbstractValidator +{ + public BanMinerCommandValidator() + { + RuleFor(x => x.MinerId) + .NotEmpty().WithMessage("Miner ID is required / ID thợ đào là bắt buộc"); + + RuleFor(x => x.Reason) + .NotEmpty().WithMessage("Reason is required / Lý do là bắt buộc") + .MaximumLength(500).WithMessage("Reason max 500 chars / Lý do tối đa 500 ký tự"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/ClaimMiningRewardCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/ClaimMiningRewardCommandValidator.cs new file mode 100644 index 00000000..1bbae7ab --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/ClaimMiningRewardCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for ClaimMiningRewardCommand. +/// VI: Validator cho ClaimMiningRewardCommand. +/// +public class ClaimMiningRewardCommandValidator : AbstractValidator +{ + public ClaimMiningRewardCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/CreateCircleCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/CreateCircleCommandValidator.cs new file mode 100644 index 00000000..00e6b2f5 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/CreateCircleCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for CreateCircleCommand. +/// VI: Validator cho CreateCircleCommand. +/// +public class CreateCircleCommandValidator : AbstractValidator +{ + public CreateCircleCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Circle name is required / Tên vòng tròn là bắt buộc") + .MaximumLength(100).WithMessage("Circle name max 100 chars / Tên vòng tròn tối đa 100 ký tự"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/InviteToCircleCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/InviteToCircleCommandValidator.cs new file mode 100644 index 00000000..827a8f41 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/InviteToCircleCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for InviteToCircleCommand. +/// VI: Validator cho InviteToCircleCommand. +/// +public class InviteToCircleCommandValidator : AbstractValidator +{ + public InviteToCircleCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.TargetMinerId) + .NotEmpty().WithMessage("Target miner ID is required / ID thợ đào mục tiêu là bắt buộc"); + + RuleFor(x => x) + .Must(x => x.UserId != x.TargetMinerId) + .WithMessage("Cannot invite yourself to circle / Không thể mời chính mình vào vòng tròn"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/RemoveCircleMemberCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/RemoveCircleMemberCommandValidator.cs new file mode 100644 index 00000000..1a517a02 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/RemoveCircleMemberCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for RemoveCircleMemberCommand. +/// VI: Validator cho RemoveCircleMemberCommand. +/// +public class RemoveCircleMemberCommandValidator : AbstractValidator +{ + public RemoveCircleMemberCommandValidator() + { + RuleFor(x => x.OwnerId) + .NotEmpty().WithMessage("Owner ID is required / ID chủ sở hữu là bắt buộc"); + + RuleFor(x => x.MemberId) + .NotEmpty().WithMessage("Member ID is required / ID thành viên là bắt buộc"); + + RuleFor(x => x) + .Must(x => x.OwnerId != x.MemberId) + .WithMessage("Cannot remove yourself from circle / Không thể xóa chính mình khỏi vòng tròn"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/ResetMinerStreakCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/ResetMinerStreakCommandValidator.cs new file mode 100644 index 00000000..b9077672 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/ResetMinerStreakCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for ResetMinerStreakCommand. +/// VI: Validator cho ResetMinerStreakCommand. +/// +public class ResetMinerStreakCommandValidator : AbstractValidator +{ + public ResetMinerStreakCommandValidator() + { + RuleFor(x => x.MinerId) + .NotEmpty().WithMessage("Miner ID is required / ID thợ đào là bắt buộc"); + + RuleFor(x => x.Reason) + .NotEmpty().WithMessage("Reason is required / Lý do là bắt buộc") + .MaximumLength(500).WithMessage("Reason max 500 chars / Lý do tối đa 500 ký tự"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/RestoreMinerCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/RestoreMinerCommandValidator.cs new file mode 100644 index 00000000..95d05515 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/RestoreMinerCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for RestoreMinerCommand. +/// VI: Validator cho RestoreMinerCommand. +/// +public class RestoreMinerCommandValidator : AbstractValidator +{ + public RestoreMinerCommandValidator() + { + RuleFor(x => x.MinerId) + .NotEmpty().WithMessage("Miner ID is required / ID thợ đào là bắt buộc"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/StartMiningCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/StartMiningCommandValidator.cs new file mode 100644 index 00000000..0f514e86 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/StartMiningCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for StartMiningCommand. +/// VI: Validator cho StartMiningCommand. +/// +public class StartMiningCommandValidator : AbstractValidator +{ + public StartMiningCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/SuspendMinerCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/SuspendMinerCommandValidator.cs new file mode 100644 index 00000000..0a9232a3 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/SuspendMinerCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for SuspendMinerCommand. +/// VI: Validator cho SuspendMinerCommand. +/// +public class SuspendMinerCommandValidator : AbstractValidator +{ + public SuspendMinerCommandValidator() + { + RuleFor(x => x.MinerId) + .NotEmpty().WithMessage("Miner ID is required / ID thợ đào là bắt buộc"); + + RuleFor(x => x.Reason) + .NotEmpty().WithMessage("Reason is required / Lý do là bắt buộc") + .MaximumLength(500).WithMessage("Reason max 500 chars / Lý do tối đa 500 ký tự"); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateMiningConfigCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateMiningConfigCommandValidator.cs new file mode 100644 index 00000000..fefec9b0 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateMiningConfigCommandValidator.cs @@ -0,0 +1,26 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for UpdateMiningConfigCommand. +/// VI: Validator cho UpdateMiningConfigCommand. +/// +public class UpdateMiningConfigCommandValidator : AbstractValidator +{ + public UpdateMiningConfigCommandValidator() + { + RuleFor(x => x.BaseRate) + .GreaterThan(0).WithMessage("Base rate must be positive / Tỷ lệ cơ bản phải dương") + .When(x => x.BaseRate.HasValue); + + RuleFor(x => x.SessionDurationHours) + .InclusiveBetween(1, 24).WithMessage("Session duration must be 1-24 hours / Thời lượng phiên phải từ 1-24 giờ") + .When(x => x.SessionDurationHours.HasValue); + + RuleFor(x => x.MaxSessionsPerDay) + .InclusiveBetween(1, 10).WithMessage("Max sessions per day must be 1-10 / Số phiên tối đa mỗi ngày phải từ 1-10") + .When(x => x.MaxSessionsPerDay.HasValue); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateReferralConfigCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateReferralConfigCommandValidator.cs new file mode 100644 index 00000000..21abdb65 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateReferralConfigCommandValidator.cs @@ -0,0 +1,22 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for UpdateReferralConfigCommand. +/// VI: Validator cho UpdateReferralConfigCommand. +/// +public class UpdateReferralConfigCommandValidator : AbstractValidator +{ + public UpdateReferralConfigCommandValidator() + { + RuleFor(x => x.BonusPerReferral) + .GreaterThan(0).WithMessage("Bonus per referral must be positive / Thưởng mỗi giới thiệu phải dương") + .When(x => x.BonusPerReferral.HasValue); + + RuleFor(x => x.MaxBonusCap) + .GreaterThan(0).WithMessage("Max bonus cap must be positive / Giới hạn thưởng tối đa phải dương") + .When(x => x.MaxBonusCap.HasValue); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateStreakConfigCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateStreakConfigCommandValidator.cs new file mode 100644 index 00000000..9191a520 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateStreakConfigCommandValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for UpdateStreakConfigCommand. +/// VI: Validator cho UpdateStreakConfigCommand. +/// +public class UpdateStreakConfigCommandValidator : AbstractValidator +{ + public UpdateStreakConfigCommandValidator() + { + RuleFor(x => x.GracePeriodDays) + .InclusiveBetween(0, 7).WithMessage("Grace period must be 0-7 days / Thời gian gia hạn phải từ 0-7 ngày") + .When(x => x.GracePeriodDays.HasValue); + } +} diff --git a/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateSystemConfigCommandValidator.cs b/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateSystemConfigCommandValidator.cs new file mode 100644 index 00000000..46cd71a6 --- /dev/null +++ b/services/mining-service-net/src/MiningService.API/Application/Validations/UpdateSystemConfigCommandValidator.cs @@ -0,0 +1,40 @@ +using FluentValidation; +using MiningService.API.Application.Commands; + +namespace MiningService.API.Application.Validations; + +/// +/// EN: Validator for UpdateSystemConfigCommand. +/// VI: Validator cho UpdateSystemConfigCommand. +/// +public class UpdateSystemConfigCommandValidator : AbstractValidator +{ + public UpdateSystemConfigCommandValidator() + { + RuleFor(x => x) + .Must(x => x.Mining != null || x.Streak != null || x.Referral != null) + .WithMessage("At least one config section must be provided / Phải cung cấp ít nhất một mục cấu hình"); + + When(x => x.Mining != null, () => + { + RuleFor(x => x.Mining!.BaseRate) + .GreaterThan(0).WithMessage("Base rate must be positive / Tỷ lệ cơ bản phải dương") + .When(x => x.Mining!.BaseRate.HasValue); + + RuleFor(x => x.Mining!.SessionDurationHours) + .InclusiveBetween(1, 24).WithMessage("Session duration must be 1-24 hours / Thời lượng phiên phải từ 1-24 giờ") + .When(x => x.Mining!.SessionDurationHours.HasValue); + }); + + When(x => x.Referral != null, () => + { + RuleFor(x => x.Referral!.BonusPerReferral) + .GreaterThan(0).WithMessage("Bonus per referral must be positive / Thưởng mỗi giới thiệu phải dương") + .When(x => x.Referral!.BonusPerReferral.HasValue); + + RuleFor(x => x.Referral!.MaxBonusCap) + .GreaterThan(0).WithMessage("Max bonus cap must be positive / Giới hạn thưởng tối đa phải dương") + .When(x => x.Referral!.MaxBonusCap.HasValue); + }); + } +} diff --git a/services/mission-service-net/src/MissionService.API/Application/Queries/MissionQueryHandlers.cs b/services/mission-service-net/src/MissionService.API/Application/Queries/MissionQueryHandlers.cs index 8c7e5770..c9054c56 100644 --- a/services/mission-service-net/src/MissionService.API/Application/Queries/MissionQueryHandlers.cs +++ b/services/mission-service-net/src/MissionService.API/Application/Queries/MissionQueryHandlers.cs @@ -163,3 +163,113 @@ public class GetMissionsByCategoryQueryHandler : IRequestHandler +/// 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. +/// +public class GetUserMissionProgressQueryHandler : IRequestHandler +{ + private readonly IMissionRepository _missionRepository; + private readonly IUserTaskRepository _taskRepository; + + public GetUserMissionProgressQueryHandler( + IMissionRepository missionRepository, + IUserTaskRepository taskRepository) + { + _missionRepository = missionRepository; + _taskRepository = taskRepository; + } + + public async Task 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); + } +} diff --git a/services/mission-service-net/src/MissionService.API/Application/Validations/ClaimTaskRewardCommandValidator.cs b/services/mission-service-net/src/MissionService.API/Application/Validations/ClaimTaskRewardCommandValidator.cs new file mode 100644 index 00000000..4699d273 --- /dev/null +++ b/services/mission-service-net/src/MissionService.API/Application/Validations/ClaimTaskRewardCommandValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using MissionService.API.Application.Commands; + +namespace MissionService.API.Application.Validations; + +/// +/// EN: Validator for ClaimTaskRewardCommand. +/// VI: Validator cho ClaimTaskRewardCommand. +/// +public class ClaimTaskRewardCommandValidator : AbstractValidator +{ + public ClaimTaskRewardCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.TaskId) + .NotEmpty().WithMessage("Task ID is required / ID task là bắt buộc"); + } +} diff --git a/services/mission-service-net/src/MissionService.API/Application/Validations/PerformCheckInCommandValidator.cs b/services/mission-service-net/src/MissionService.API/Application/Validations/PerformCheckInCommandValidator.cs new file mode 100644 index 00000000..9f2361c8 --- /dev/null +++ b/services/mission-service-net/src/MissionService.API/Application/Validations/PerformCheckInCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using MissionService.API.Application.Commands; + +namespace MissionService.API.Application.Validations; + +/// +/// EN: Validator for PerformCheckInCommand. +/// VI: Validator cho PerformCheckInCommand. +/// +public class PerformCheckInCommandValidator : AbstractValidator +{ + public PerformCheckInCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + } +} diff --git a/services/mission-service-net/src/MissionService.API/Application/Validations/StartMissionTaskCommandValidator.cs b/services/mission-service-net/src/MissionService.API/Application/Validations/StartMissionTaskCommandValidator.cs new file mode 100644 index 00000000..25db4710 --- /dev/null +++ b/services/mission-service-net/src/MissionService.API/Application/Validations/StartMissionTaskCommandValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using MissionService.API.Application.Commands; + +namespace MissionService.API.Application.Validations; + +/// +/// EN: Validator for StartMissionTaskCommand. +/// VI: Validator cho StartMissionTaskCommand. +/// +public class StartMissionTaskCommandValidator : AbstractValidator +{ + public StartMissionTaskCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.MissionId) + .NotEmpty().WithMessage("Mission ID is required / ID nhiệm vụ là bắt buộc"); + } +} diff --git a/services/mission-service-net/src/MissionService.API/Application/Validations/UpdateTaskProgressCommandValidator.cs b/services/mission-service-net/src/MissionService.API/Application/Validations/UpdateTaskProgressCommandValidator.cs new file mode 100644 index 00000000..46449736 --- /dev/null +++ b/services/mission-service-net/src/MissionService.API/Application/Validations/UpdateTaskProgressCommandValidator.cs @@ -0,0 +1,42 @@ +using FluentValidation; +using MissionService.API.Application.Commands; + +namespace MissionService.API.Application.Validations; + +/// +/// EN: Validator for UpdateTaskProgressCommand. +/// VI: Validator cho UpdateTaskProgressCommand. +/// +public class UpdateTaskProgressCommandValidator : AbstractValidator +{ + public UpdateTaskProgressCommandValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.TaskId) + .NotEmpty().WithMessage("Task ID is required / ID task là bắt buộc"); + + RuleFor(x => x.CurrentValue) + .GreaterThanOrEqualTo(0).WithMessage("Current value must be non-negative / Giá trị hiện tại không được âm"); + + When(x => x.Evidence != null, () => + { + RuleFor(x => x.Evidence!.Type) + .NotEmpty().WithMessage("Evidence type is required / Loại bằng chứng là bắt buộc") + .MaximumLength(50).WithMessage("Evidence type max 50 chars / Loại bằng chứng tối đa 50 ký tự"); + + RuleFor(x => x.Evidence!.Data) + .NotEmpty().WithMessage("Evidence data is required / Dữ liệu bằng chứng là bắt buộc") + .MaximumLength(2000).WithMessage("Evidence data max 2000 chars / Dữ liệu bằng chứng tối đa 2000 ký tự"); + + RuleFor(x => x.Evidence!.ScreenshotUrl) + .MaximumLength(500).WithMessage("Screenshot URL max 500 chars / URL ảnh chụp tối đa 500 ký tự") + .When(x => x.Evidence!.ScreenshotUrl != null); + + RuleFor(x => x.Evidence!.VideoUrl) + .MaximumLength(500).WithMessage("Video URL max 500 chars / URL video tối đa 500 ký tự") + .When(x => x.Evidence!.VideoUrl != null); + }); + } +} diff --git a/services/mkt-facebook-service-net/src/FacebookService.API/Application/Queries/ConversationQueryHandlers.cs b/services/mkt-facebook-service-net/src/FacebookService.API/Application/Queries/ConversationQueryHandlers.cs index 00479345..3d762eda 100644 --- a/services/mkt-facebook-service-net/src/FacebookService.API/Application/Queries/ConversationQueryHandlers.cs +++ b/services/mkt-facebook-service-net/src/FacebookService.API/Application/Queries/ConversationQueryHandlers.cs @@ -1,9 +1,95 @@ using MediatR; +using Microsoft.EntityFrameworkCore; using FacebookService.API.Application.Dtos; using FacebookService.Domain.AggregatesModel.ConversationAggregate; +using FacebookService.Domain.AggregatesModel.CustomerAggregate; +using FacebookService.Infrastructure; namespace FacebookService.API.Application.Queries; +/// +/// EN: Handler for GetConversationsQuery - returns paginated conversations list. +/// VI: Handler cho GetConversationsQuery - trả về danh sách conversations phân trang. +/// +public class GetConversationsQueryHandler : IRequestHandler +{ + private readonly FacebookServiceContext _context; + + public GetConversationsQueryHandler(FacebookServiceContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task Handle( + GetConversationsQuery request, + CancellationToken cancellationToken) + { + // EN: Build base query with status inclusion / VI: Xây dựng query cơ bản với status + var query = _context.Conversations + .AsNoTracking() + .Include(c => c.Status) + .AsQueryable(); + + // EN: Filter by page ID (shop) if provided / VI: Lọc theo page ID (shop) nếu có + if (request.ShopId.HasValue) + { + var pageId = request.ShopId.Value.ToString(); + query = query.Where(c => EF.Property(c, "_pageId") == pageId); + } + + // EN: Filter by status if provided / VI: Lọc theo status nếu có + if (!string.IsNullOrEmpty(request.Status)) + { + var statusId = request.Status.ToLower() switch + { + "active" => ConversationStatus.Active.Id, + "closed" => ConversationStatus.Closed.Id, + "archived" => ConversationStatus.Archived.Id, + _ => 0 + }; + if (statusId > 0) + query = query.Where(c => c.StatusId == statusId); + } + + // EN: Get total count for pagination / VI: Lấy tổng số cho phân trang + var totalCount = await query.CountAsync(cancellationToken); + + // EN: Get paginated conversations ordered by last message time + // VI: Lấy conversations phân trang sắp xếp theo thời gian tin nhắn cuối + var conversations = await query + .OrderByDescending(c => EF.Property(c, "_lastMessageAt")) + .Skip(request.Skip) + .Take(request.Take) + .ToListAsync(cancellationToken); + + // EN: Load customer names for summaries / VI: Tải tên khách hàng cho tóm tắt + var customerIds = conversations.Select(c => c.CustomerId).Distinct().ToList(); + var customers = await _context.Customers + .AsNoTracking() + .Where(c => customerIds.Contains(c.Id)) + .ToDictionaryAsync(c => c.Id, c => c.Name, cancellationToken); + + // EN: Map to summary DTOs / VI: Map sang DTO tóm tắt + var summaries = conversations.Select(c => + { + customers.TryGetValue(c.CustomerId, out var customerName); + var lastMessage = c.Messages.OrderByDescending(m => m.SentAt).FirstOrDefault(); + + return new ConversationSummaryDto( + c.Id, + c.CustomerId, + customerName, + c.PageId, + c.Status.Name, + lastMessage?.Content, + c.LastMessageAt, + c.CreatedAt); + }).ToList(); + + return new GetConversationsQueryResult(summaries, totalCount); + } +} + /// /// EN: Handler for GetConversationByIdQuery. /// VI: Handler cho GetConversationByIdQuery. diff --git a/services/mkt-facebook-service-net/src/FacebookService.API/Application/Queries/CustomerQueryHandlers.cs b/services/mkt-facebook-service-net/src/FacebookService.API/Application/Queries/CustomerQueryHandlers.cs index c2ca5094..388956b3 100644 --- a/services/mkt-facebook-service-net/src/FacebookService.API/Application/Queries/CustomerQueryHandlers.cs +++ b/services/mkt-facebook-service-net/src/FacebookService.API/Application/Queries/CustomerQueryHandlers.cs @@ -1,9 +1,76 @@ using MediatR; +using Microsoft.EntityFrameworkCore; using FacebookService.API.Application.Dtos; using FacebookService.Domain.AggregatesModel.CustomerAggregate; +using FacebookService.Infrastructure; namespace FacebookService.API.Application.Queries; +/// +/// EN: Handler for GetCustomersQuery - returns paginated customers list. +/// VI: Handler cho GetCustomersQuery - trả về danh sách customers phân trang. +/// +public class GetCustomersQueryHandler : IRequestHandler +{ + private readonly FacebookServiceContext _context; + + public GetCustomersQueryHandler(FacebookServiceContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task Handle( + GetCustomersQuery request, + CancellationToken cancellationToken) + { + // EN: Build base query / VI: Xây dựng query cơ bản + var query = _context.Customers.AsNoTracking().AsQueryable(); + + // EN: Filter by search term (name match) / VI: Lọc theo từ khóa tìm kiếm (tên) + if (!string.IsNullOrWhiteSpace(request.Search)) + { + var searchTerm = request.Search.Trim(); + query = query.Where(c => + EF.Property(c, "_name") != null && + EF.Property(c, "_name")!.Contains(searchTerm)); + } + + // EN: Filter by tags if provided / VI: Lọc theo tags nếu có + if (request.Tags != null && request.Tags.Any()) + { + var tagList = request.Tags.ToList(); + foreach (var tag in tagList) + { + // EN: Each tag must be present / VI: Mỗi tag phải có mặt + query = query.Where(c => c.Tags.Contains(tag)); + } + } + + // EN: Get total count for pagination / VI: Lấy tổng số cho phân trang + var totalCount = await query.CountAsync(cancellationToken); + + // EN: Get paginated customers ordered by last interaction + // VI: Lấy customers phân trang sắp xếp theo tương tác cuối + var customers = await query + .OrderByDescending(c => EF.Property(c, "_lastInteractionAt")) + .Skip(request.Skip) + .Take(request.Take) + .ToListAsync(cancellationToken); + + // EN: Map to summary DTOs / VI: Map sang DTO tóm tắt + var summaries = customers.Select(c => new CustomerSummaryDto( + c.Id, + c.FacebookUserId, + c.Name, + c.ProfilePicUrl, + c.Tags, + c.LastInteractionAt + )).ToList(); + + return new GetCustomersQueryResult(summaries, totalCount); + } +} + /// /// EN: Handler for GetCustomerByIdQuery. /// VI: Handler cho GetCustomerByIdQuery. diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Commands/VoucherCommandHandlers.cs b/services/promotion-service-net/src/PromotionService.API/Application/Commands/VoucherCommandHandlers.cs index 3c0a976c..ab292d7e 100644 --- a/services/promotion-service-net/src/PromotionService.API/Application/Commands/VoucherCommandHandlers.cs +++ b/services/promotion-service-net/src/PromotionService.API/Application/Commands/VoucherCommandHandlers.cs @@ -9,6 +9,163 @@ using PromotionService.Domain.Exceptions; namespace PromotionService.API.Application.Commands; +/// +/// EN: Handler for ExchangeVoucherCommand (exchange points for voucher). +/// VI: Handler cho ExchangeVoucherCommand (đổi điểm lấy voucher). +/// +public class ExchangeVoucherCommandHandler : IRequestHandler +{ + private readonly ICampaignRepository _campaignRepository; + private readonly IWalletServiceClient _walletService; + private readonly ILogger _logger; + + public ExchangeVoucherCommandHandler( + ICampaignRepository campaignRepository, + IWalletServiceClient walletService, + ILogger logger) + { + _campaignRepository = campaignRepository; + _walletService = walletService; + _logger = logger; + } + + public async Task Handle(ExchangeVoucherCommand request, CancellationToken cancellationToken) + { + // EN: Find and validate campaign / VI: Tìm và xác thực chiến dịch + var campaign = await _campaignRepository.GetByIdAsync(request.CampaignId) + ?? throw new PromotionDomainException($"Campaign {request.CampaignId} not found"); + + // EN: Verify it's a point-exchange campaign / VI: Xác nhận là chiến dịch đổi điểm + if (campaign.AcquisitionTypeId != AcquisitionType.ExchangePoints.Id) + throw new PromotionDomainException("This campaign does not support point exchange"); + + // EN: Deduct points from user's wallet / VI: Trừ điểm từ ví người dùng + var pointsRequired = campaign.AcquisitionPrice; + var holdResult = await _walletService.CreateHoldAsync( + request.UserWalletId, + pointsRequired, + campaign.BackingAssetCode, + "VOUCHER_EXCHANGE", + campaign.Id, + $"Exchange points for voucher in campaign {campaign.Name}", + cancellationToken); + + // EN: Execute the hold immediately (deduct points) / VI: Thực thi hold ngay (trừ điểm) + await _walletService.ExecuteHoldAsync( + request.UserWalletId, + holdResult.HoldId, + pointsRequired, + $"EXCHANGE:{campaign.Id}:{request.UserId}", + cancellationToken); + + // EN: Issue voucher to user / VI: Phát voucher cho người dùng + var voucher = campaign.IssueVoucher(request.UserId); + _campaignRepository.Update(campaign); + await _campaignRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); + + _logger.LogInformation( + "Voucher {VoucherCode} exchanged by user {UserId} for {Points} points", + voucher.Code, request.UserId, pointsRequired); + + return MapToDto(voucher); + } + + private static VoucherDto MapToDto(Voucher voucher) => new( + voucher.Id, + voucher.CampaignId, + voucher.Code, + voucher.OwnerId, + voucher.FaceValue, + voucher.RemainingValue, + voucher.StatusId == VoucherStatus.Available.Id ? "Available" + : voucher.StatusId == VoucherStatus.Claimed.Id ? "Claimed" + : voucher.StatusId == VoucherStatus.PartiallyRedeemed.Id ? "PartiallyRedeemed" + : voucher.StatusId == VoucherStatus.FullyRedeemed.Id ? "FullyRedeemed" + : "Expired", + voucher.ClaimedAt, + voucher.ExpiresAt, + voucher.RedeemedAt); +} + +/// +/// EN: Handler for PurchaseVoucherCommand (buy voucher with currency). +/// VI: Handler cho PurchaseVoucherCommand (mua voucher bằng tiền). +/// +public class PurchaseVoucherCommandHandler : IRequestHandler +{ + private readonly ICampaignRepository _campaignRepository; + private readonly IWalletServiceClient _walletService; + private readonly ILogger _logger; + + public PurchaseVoucherCommandHandler( + ICampaignRepository campaignRepository, + IWalletServiceClient walletService, + ILogger logger) + { + _campaignRepository = campaignRepository; + _walletService = walletService; + _logger = logger; + } + + public async Task Handle(PurchaseVoucherCommand request, CancellationToken cancellationToken) + { + // EN: Find and validate campaign / VI: Tìm và xác thực chiến dịch + var campaign = await _campaignRepository.GetByIdAsync(request.CampaignId) + ?? throw new PromotionDomainException($"Campaign {request.CampaignId} not found"); + + // EN: Verify it's a purchase campaign / VI: Xác nhận là chiến dịch mua + if (campaign.AcquisitionTypeId != AcquisitionType.Purchase.Id) + throw new PromotionDomainException("This campaign does not support purchase"); + + // EN: Create hold and execute payment from user's wallet + // VI: Tạo hold và thực thi thanh toán từ ví người dùng + var purchaseAmount = campaign.AcquisitionPrice; + var holdResult = await _walletService.CreateHoldAsync( + request.UserWalletId, + purchaseAmount, + campaign.BackingAssetCode, + "VOUCHER_PURCHASE", + campaign.Id, + $"Purchase voucher in campaign {campaign.Name}", + cancellationToken); + + // EN: Execute the hold immediately (deduct funds) / VI: Thực thi hold ngay (trừ tiền) + await _walletService.ExecuteHoldAsync( + request.UserWalletId, + holdResult.HoldId, + purchaseAmount, + $"PURCHASE:{campaign.Id}:{request.UserId}", + cancellationToken); + + // EN: Issue voucher to user / VI: Phát voucher cho người dùng + var voucher = campaign.IssueVoucher(request.UserId); + _campaignRepository.Update(campaign); + await _campaignRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); + + _logger.LogInformation( + "Voucher {VoucherCode} purchased by user {UserId} for {Amount} {Currency}", + voucher.Code, request.UserId, purchaseAmount, campaign.BackingAssetCode); + + return MapToDto(voucher); + } + + private static VoucherDto MapToDto(Voucher voucher) => new( + voucher.Id, + voucher.CampaignId, + voucher.Code, + voucher.OwnerId, + voucher.FaceValue, + voucher.RemainingValue, + voucher.StatusId == VoucherStatus.Available.Id ? "Available" + : voucher.StatusId == VoucherStatus.Claimed.Id ? "Claimed" + : voucher.StatusId == VoucherStatus.PartiallyRedeemed.Id ? "PartiallyRedeemed" + : voucher.StatusId == VoucherStatus.FullyRedeemed.Id ? "FullyRedeemed" + : "Expired", + voucher.ClaimedAt, + voucher.ExpiresAt, + voucher.RedeemedAt); +} + /// /// EN: Handler for ClaimVoucherCommand (free vouchers). /// VI: Handler cho ClaimVoucherCommand (voucher miễn phí). diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Queries/AdminQueryHandlers.cs b/services/promotion-service-net/src/PromotionService.API/Application/Queries/AdminQueryHandlers.cs index 81225d52..15d614db 100644 --- a/services/promotion-service-net/src/PromotionService.API/Application/Queries/AdminQueryHandlers.cs +++ b/services/promotion-service-net/src/PromotionService.API/Application/Queries/AdminQueryHandlers.cs @@ -258,6 +258,126 @@ public class GetAllRedemptionsQueryHandler : IRequestHandler +/// EN: Handler for SearchVouchersQuery - search vouchers by code pattern. +/// VI: Handler cho SearchVouchersQuery - tìm kiếm vouchers theo mẫu mã. +/// +public class SearchVouchersQueryHandler : IRequestHandler> +{ + private readonly PromotionServiceContext _context; + + public SearchVouchersQueryHandler(PromotionServiceContext context) + { + _context = context; + } + + public async Task> Handle(SearchVouchersQuery request, CancellationToken cancellationToken) + { + // EN: Search vouchers by code pattern (case-insensitive) + // VI: Tìm kiếm vouchers theo mẫu mã (không phân biệt hoa thường) + if (string.IsNullOrWhiteSpace(request.SearchTerm)) + return Enumerable.Empty(); + + var searchTerm = request.SearchTerm.Trim(); + + var items = await _context.Vouchers + .AsNoTracking() + .Where(v => v.Code.Contains(searchTerm)) + .OrderByDescending(v => v.CreatedAt) + .Take(50) // EN: Limit search results / VI: Giới hạn kết quả tìm kiếm + .Join(_context.Campaigns, + v => v.CampaignId, + c => c.Id, + (v, c) => new AdminVoucherListDto( + v.Id, + v.CampaignId, + c.Name, + v.Code, + v.OwnerId, + null, + v.FaceValue, + v.RemainingValue, + v.StatusId == 1 ? "Available" : v.StatusId == 2 ? "Claimed" : v.StatusId == 3 ? "PartiallyRedeemed" : v.StatusId == 4 ? "FullyRedeemed" : "Expired", + v.ClaimedAt, + v.ExpiresAt, + v.RedeemedAt, + v.CreatedAt)) + .ToListAsync(cancellationToken); + + return items; + } +} + +/// +/// EN: Handler for GetCampaignVouchersQuery - return vouchers for a specific campaign with pagination. +/// VI: Handler cho GetCampaignVouchersQuery - trả về vouchers của chiến dịch cụ thể với phân trang. +/// +public class GetCampaignVouchersQueryHandler : IRequestHandler> +{ + private readonly PromotionServiceContext _context; + + public GetCampaignVouchersQueryHandler(PromotionServiceContext context) + { + _context = context; + } + + public async Task> Handle(GetCampaignVouchersQuery request, CancellationToken cancellationToken) + { + // EN: Build query for campaign vouchers / VI: Xây dựng query cho vouchers chiến dịch + var query = _context.Vouchers + .AsNoTracking() + .Where(v => v.CampaignId == request.CampaignId); + + // EN: Apply status filter if provided / VI: Áp dụng filter status nếu có + if (!string.IsNullOrEmpty(request.Status)) + { + var statusId = request.Status.ToLower() switch + { + "available" => 1, + "claimed" => 2, + "partiallyredeemed" => 3, + "fullyredeemed" => 4, + "expired" => 5, + _ => 0 + }; + if (statusId > 0) + query = query.Where(v => v.StatusId == statusId); + } + + var totalCount = await query.CountAsync(cancellationToken); + var totalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize); + + // EN: Get campaign name for DTO / VI: Lấy tên chiến dịch cho DTO + var campaignName = await _context.Campaigns + .AsNoTracking() + .Where(c => c.Id == request.CampaignId) + .Select(c => c.Name) + .FirstOrDefaultAsync(cancellationToken) ?? "Unknown"; + + var items = await query + .OrderByDescending(v => v.CreatedAt) + .Skip((request.PageNumber - 1) * request.PageSize) + .Take(request.PageSize) + .Select(v => new AdminVoucherListDto( + v.Id, + v.CampaignId, + campaignName, + v.Code, + v.OwnerId, + null, + v.FaceValue, + v.RemainingValue, + v.StatusId == 1 ? "Available" : v.StatusId == 2 ? "Claimed" : v.StatusId == 3 ? "PartiallyRedeemed" : v.StatusId == 4 ? "FullyRedeemed" : "Expired", + v.ClaimedAt, + v.ExpiresAt, + v.RedeemedAt, + v.CreatedAt)) + .ToListAsync(cancellationToken); + + return new PaginatedResponse(items, totalCount, request.PageNumber, request.PageSize, totalPages); + } +} + /// /// EN: Handler for GetRedemptionStatisticsQuery. /// VI: Handler cho GetRedemptionStatisticsQuery. diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Queries/PromotionQueryHandlers.cs b/services/promotion-service-net/src/PromotionService.API/Application/Queries/PromotionQueryHandlers.cs index 88fdfa19..039f830a 100644 --- a/services/promotion-service-net/src/PromotionService.API/Application/Queries/PromotionQueryHandlers.cs +++ b/services/promotion-service-net/src/PromotionService.API/Application/Queries/PromotionQueryHandlers.cs @@ -1,6 +1,9 @@ using MediatR; +using Microsoft.EntityFrameworkCore; using PromotionService.API.Application.DTOs; using PromotionService.Domain.AggregatesModel.CampaignAggregate; +using PromotionService.Domain.AggregatesModel.RedemptionAggregate; +using PromotionService.Infrastructure; namespace PromotionService.API.Application.Queries; @@ -136,3 +139,51 @@ public class GetUserVouchersQueryHandler : IRequestHandler +/// EN: Handler for GetCampaignStatisticsQuery - returns campaign stats (total vouchers, redeemed count, revenue). +/// VI: Handler cho GetCampaignStatisticsQuery - trả về thống kê chiến dịch (tổng voucher, số đã dùng, doanh thu). +/// +public class GetCampaignStatisticsQueryHandler : IRequestHandler +{ + private readonly ICampaignRepository _campaignRepository; + private readonly IRedemptionRepository _redemptionRepository; + + public GetCampaignStatisticsQueryHandler( + ICampaignRepository campaignRepository, + IRedemptionRepository redemptionRepository) + { + _campaignRepository = campaignRepository; + _redemptionRepository = redemptionRepository; + } + + public async Task Handle(GetCampaignStatisticsQuery request, CancellationToken cancellationToken) + { + // EN: Load campaign with vouchers / VI: Tải chiến dịch với vouchers + var campaign = await _campaignRepository.GetByIdAsync(request.CampaignId); + if (campaign == null) return null; + + // EN: Calculate voucher statistics / VI: Tính thống kê voucher + var vouchers = campaign.Vouchers; + var claimedCount = vouchers.Count(v => v.OwnerId.HasValue); + var redeemedCount = campaign.RedeemedVoucherCount; + var totalFaceValue = campaign.FaceValue * campaign.TotalVouchers; + var totalRedeemedValue = campaign.TotalRedeemedValue; + + // EN: Calculate utilization rate / VI: Tính tỷ lệ sử dụng + var utilizationRate = campaign.TotalVouchers > 0 + ? (decimal)redeemedCount / campaign.TotalVouchers * 100 + : 0; + + return new CampaignStatisticsDto( + CampaignId: campaign.Id, + CampaignName: campaign.Name, + TotalVouchers: campaign.TotalVouchers, + AvailableVouchers: campaign.AvailableVoucherCount, + ClaimedVouchers: claimedCount, + RedeemedVouchers: redeemedCount, + TotalFaceValue: totalFaceValue, + TotalRedeemedValue: totalRedeemedValue, + UtilizationRate: utilizationRate); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/ActivateCampaignCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/ActivateCampaignCommandValidator.cs new file mode 100644 index 00000000..bbbd5840 --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/ActivateCampaignCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for ActivateCampaignCommand. +/// VI: Validator cho ActivateCampaignCommand. +/// +public class ActivateCampaignCommandValidator : AbstractValidator +{ + public ActivateCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/CancelCampaignCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/CancelCampaignCommandValidator.cs new file mode 100644 index 00000000..2495dbeb --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/CancelCampaignCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for CancelCampaignCommand. +/// VI: Validator cho CancelCampaignCommand. +/// +public class CancelCampaignCommandValidator : AbstractValidator +{ + public CancelCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/ClaimVoucherCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/ClaimVoucherCommandValidator.cs new file mode 100644 index 00000000..07793e7a --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/ClaimVoucherCommandValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for ClaimVoucherCommand. +/// VI: Validator cho ClaimVoucherCommand. +/// +public class ClaimVoucherCommandValidator : AbstractValidator +{ + public ClaimVoucherCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/CompleteCampaignCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/CompleteCampaignCommandValidator.cs new file mode 100644 index 00000000..0705b681 --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/CompleteCampaignCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for CompleteCampaignCommand. +/// VI: Validator cho CompleteCampaignCommand. +/// +public class CompleteCampaignCommandValidator : AbstractValidator +{ + public CompleteCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/CreateCampaignCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/CreateCampaignCommandValidator.cs new file mode 100644 index 00000000..858ee615 --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/CreateCampaignCommandValidator.cs @@ -0,0 +1,63 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for CreateCampaignCommand. +/// VI: Validator cho CreateCampaignCommand. +/// +public class CreateCampaignCommandValidator : AbstractValidator +{ + public CreateCampaignCommandValidator() + { + RuleFor(x => x.MerchantId) + .NotEmpty().WithMessage("Merchant ID is required / ID merchant là bắt buộc"); + + RuleFor(x => x.MerchantWalletId) + .NotEmpty().WithMessage("Merchant wallet ID is required / ID ví merchant là bắt buộc"); + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Campaign name is required / Tên chiến dịch là bắt buộc") + .MaximumLength(200).WithMessage("Campaign name max 200 chars / Tên chiến dịch tối đa 200 ký tự"); + + RuleFor(x => x.Description) + .MaximumLength(1000).WithMessage("Description max 1000 chars / Mô tả tối đa 1000 ký tự") + .When(x => x.Description != null); + + RuleFor(x => x.BackingAssetType) + .NotEmpty().WithMessage("Backing asset type is required / Loại tài sản đảm bảo là bắt buộc") + .MaximumLength(50).WithMessage("Backing asset type max 50 chars / Loại tài sản đảm bảo tối đa 50 ký tự"); + + RuleFor(x => x.BackingAssetCode) + .NotEmpty().WithMessage("Backing asset code is required / Mã tài sản đảm bảo là bắt buộc") + .MaximumLength(50).WithMessage("Backing asset code max 50 chars / Mã tài sản đảm bảo tối đa 50 ký tự"); + + RuleFor(x => x.FaceValue) + .GreaterThan(0).WithMessage("Face value must be positive / Mệnh giá phải dương"); + + RuleFor(x => x.AcquisitionType) + .NotEmpty().WithMessage("Acquisition type is required / Loại nhận là bắt buộc") + .MaximumLength(50).WithMessage("Acquisition type max 50 chars / Loại nhận tối đa 50 ký tự"); + + RuleFor(x => x.AcquisitionPrice) + .GreaterThanOrEqualTo(0).WithMessage("Acquisition price must be non-negative / Giá nhận không được âm"); + + RuleFor(x => x.TotalVouchers) + .GreaterThan(0).WithMessage("Total vouchers must be positive / Tổng voucher phải dương") + .LessThanOrEqualTo(1000000).WithMessage("Total vouchers max 1,000,000 / Tổng voucher tối đa 1.000.000"); + + RuleFor(x => x.StartDate) + .NotEmpty().WithMessage("Start date is required / Ngày bắt đầu là bắt buộc"); + + RuleFor(x => x.EndDate) + .NotEmpty().WithMessage("End date is required / Ngày kết thúc là bắt buộc") + .GreaterThan(x => x.StartDate).WithMessage("End date must be after start date / Ngày kết thúc phải sau ngày bắt đầu"); + + RuleFor(x => x.VoucherValidityDays) + .InclusiveBetween(1, 365).WithMessage("Voucher validity must be 1-365 days / Hiệu lực voucher phải từ 1-365 ngày"); + + RuleFor(x => x.MaxPerUser) + .InclusiveBetween(1, 100).WithMessage("Max per user must be 1-100 / Tối đa mỗi người phải từ 1-100"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/DeleteCampaignCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/DeleteCampaignCommandValidator.cs new file mode 100644 index 00000000..3e22ce4e --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/DeleteCampaignCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for DeleteCampaignCommand. +/// VI: Validator cho DeleteCampaignCommand. +/// +public class DeleteCampaignCommandValidator : AbstractValidator +{ + public DeleteCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/ExchangeVoucherCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/ExchangeVoucherCommandValidator.cs new file mode 100644 index 00000000..ac4bb442 --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/ExchangeVoucherCommandValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for ExchangeVoucherCommand. +/// VI: Validator cho ExchangeVoucherCommand. +/// +public class ExchangeVoucherCommandValidator : AbstractValidator +{ + public ExchangeVoucherCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.UserWalletId) + .NotEmpty().WithMessage("User wallet ID is required / ID ví người dùng là bắt buộc"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/ExtendVoucherExpiryCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/ExtendVoucherExpiryCommandValidator.cs new file mode 100644 index 00000000..b2ec65ed --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/ExtendVoucherExpiryCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for ExtendVoucherExpiryCommand. +/// VI: Validator cho ExtendVoucherExpiryCommand. +/// +public class ExtendVoucherExpiryCommandValidator : AbstractValidator +{ + public ExtendVoucherExpiryCommandValidator() + { + RuleFor(x => x.VoucherId) + .NotEmpty().WithMessage("Voucher ID is required / ID voucher là bắt buộc"); + + RuleFor(x => x.AdditionalDays) + .GreaterThan(0).WithMessage("Additional days must be positive / Số ngày gia hạn phải dương") + .LessThanOrEqualTo(365).WithMessage("Additional days max 365 / Số ngày gia hạn tối đa 365"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/PauseCampaignCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/PauseCampaignCommandValidator.cs new file mode 100644 index 00000000..baf224bd --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/PauseCampaignCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for PauseCampaignCommand. +/// VI: Validator cho PauseCampaignCommand. +/// +public class PauseCampaignCommandValidator : AbstractValidator +{ + public PauseCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/PurchaseVoucherCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/PurchaseVoucherCommandValidator.cs new file mode 100644 index 00000000..7ba9bf05 --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/PurchaseVoucherCommandValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for PurchaseVoucherCommand. +/// VI: Validator cho PurchaseVoucherCommand. +/// +public class PurchaseVoucherCommandValidator : AbstractValidator +{ + public PurchaseVoucherCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.UserWalletId) + .NotEmpty().WithMessage("User wallet ID is required / ID ví người dùng là bắt buộc"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/RedeemVoucherCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/RedeemVoucherCommandValidator.cs new file mode 100644 index 00000000..6e28ac1b --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/RedeemVoucherCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for RedeemVoucherCommand. +/// VI: Validator cho RedeemVoucherCommand. +/// +public class RedeemVoucherCommandValidator : AbstractValidator +{ + public RedeemVoucherCommandValidator() + { + RuleFor(x => x.VoucherCode) + .NotEmpty().WithMessage("Voucher code is required / Mã voucher là bắt buộc") + .MaximumLength(50).WithMessage("Voucher code max 50 chars / Mã voucher tối đa 50 ký tự"); + + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + + RuleFor(x => x.OrderAmount) + .GreaterThan(0).WithMessage("Order amount must be positive / Số tiền đơn hàng phải dương"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/RevokeVoucherCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/RevokeVoucherCommandValidator.cs new file mode 100644 index 00000000..26bd87f4 --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/RevokeVoucherCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for RevokeVoucherCommand. +/// VI: Validator cho RevokeVoucherCommand. +/// +public class RevokeVoucherCommandValidator : AbstractValidator +{ + public RevokeVoucherCommandValidator() + { + RuleFor(x => x.VoucherId) + .NotEmpty().WithMessage("Voucher ID is required / ID voucher là bắt buộc"); + + RuleFor(x => x.Reason) + .NotEmpty().WithMessage("Reason is required / Lý do là bắt buộc") + .MaximumLength(500).WithMessage("Reason max 500 chars / Lý do tối đa 500 ký tự"); + } +} diff --git a/services/promotion-service-net/src/PromotionService.API/Application/Validations/UpdateCampaignCommandValidator.cs b/services/promotion-service-net/src/PromotionService.API/Application/Validations/UpdateCampaignCommandValidator.cs new file mode 100644 index 00000000..6b806a73 --- /dev/null +++ b/services/promotion-service-net/src/PromotionService.API/Application/Validations/UpdateCampaignCommandValidator.cs @@ -0,0 +1,34 @@ +using FluentValidation; +using PromotionService.API.Application.Commands; + +namespace PromotionService.API.Application.Validations; + +/// +/// EN: Validator for UpdateCampaignCommand. +/// VI: Validator cho UpdateCampaignCommand. +/// +public class UpdateCampaignCommandValidator : AbstractValidator +{ + public UpdateCampaignCommandValidator() + { + RuleFor(x => x.CampaignId) + .NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc"); + + RuleFor(x => x.Name) + .MaximumLength(200).WithMessage("Campaign name max 200 chars / Tên chiến dịch tối đa 200 ký tự") + .When(x => x.Name != null); + + RuleFor(x => x.Description) + .MaximumLength(1000).WithMessage("Description max 1000 chars / Mô tả tối đa 1000 ký tự") + .When(x => x.Description != null); + + RuleFor(x => x.EndDate) + .GreaterThan(x => x.StartDate!.Value) + .WithMessage("End date must be after start date / Ngày kết thúc phải sau ngày bắt đầu") + .When(x => x.StartDate.HasValue && x.EndDate.HasValue); + + RuleFor(x => x.MaxPerUser) + .InclusiveBetween(1, 100).WithMessage("Max per user must be 1-100 / Tối đa mỗi người phải từ 1-100") + .When(x => x.MaxPerUser.HasValue); + } +} diff --git a/services/social-service-net/src/SocialService.API/Application/Validations/AdminDeleteBlockCommandValidator.cs b/services/social-service-net/src/SocialService.API/Application/Validations/AdminDeleteBlockCommandValidator.cs new file mode 100644 index 00000000..bb2a7bdd --- /dev/null +++ b/services/social-service-net/src/SocialService.API/Application/Validations/AdminDeleteBlockCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using SocialService.API.Application.Commands; + +namespace SocialService.API.Application.Validations; + +/// +/// EN: Validator for AdminDeleteBlockCommand. +/// VI: Validator cho AdminDeleteBlockCommand. +/// +public class AdminDeleteBlockCommandValidator : AbstractValidator +{ + public AdminDeleteBlockCommandValidator() + { + RuleFor(x => x.BlockId) + .NotEmpty().WithMessage("Block ID is required / ID block là bắt buộc"); + } +} diff --git a/services/social-service-net/src/SocialService.API/Application/Validations/AdminDeleteRelationshipCommandValidator.cs b/services/social-service-net/src/SocialService.API/Application/Validations/AdminDeleteRelationshipCommandValidator.cs new file mode 100644 index 00000000..294c4bb5 --- /dev/null +++ b/services/social-service-net/src/SocialService.API/Application/Validations/AdminDeleteRelationshipCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using SocialService.API.Application.Commands; + +namespace SocialService.API.Application.Validations; + +/// +/// EN: Validator for AdminDeleteRelationshipCommand. +/// VI: Validator cho AdminDeleteRelationshipCommand. +/// +public class AdminDeleteRelationshipCommandValidator : AbstractValidator +{ + public AdminDeleteRelationshipCommandValidator() + { + RuleFor(x => x.RelationshipId) + .NotEmpty().WithMessage("Relationship ID is required / ID quan hệ là bắt buộc"); + } +} diff --git a/services/social-service-net/src/SocialService.API/Application/Validations/BlockUserCommandValidator.cs b/services/social-service-net/src/SocialService.API/Application/Validations/BlockUserCommandValidator.cs new file mode 100644 index 00000000..28d8bca9 --- /dev/null +++ b/services/social-service-net/src/SocialService.API/Application/Validations/BlockUserCommandValidator.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using SocialService.API.Application.Commands; + +namespace SocialService.API.Application.Validations; + +/// +/// EN: Validator for BlockUserCommand. +/// VI: Validator cho BlockUserCommand. +/// +public class BlockUserCommandValidator : AbstractValidator +{ + public BlockUserCommandValidator() + { + RuleFor(x => x.BlockerId) + .NotEmpty().WithMessage("Blocker ID is required / ID người block là bắt buộc"); + + RuleFor(x => x.BlockedId) + .NotEmpty().WithMessage("Blocked user ID is required / ID người bị block là bắt buộc"); + + RuleFor(x => x) + .Must(x => x.BlockerId != x.BlockedId) + .WithMessage("Cannot block yourself / Không thể block chính mình"); + + RuleFor(x => x.Reason) + .MaximumLength(500).WithMessage("Reason max 500 chars / Lý do tối đa 500 ký tự") + .When(x => x.Reason != null); + } +} diff --git a/services/social-service-net/src/SocialService.API/Application/Validations/FollowUserCommandValidator.cs b/services/social-service-net/src/SocialService.API/Application/Validations/FollowUserCommandValidator.cs new file mode 100644 index 00000000..2da2efd2 --- /dev/null +++ b/services/social-service-net/src/SocialService.API/Application/Validations/FollowUserCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using SocialService.API.Application.Commands; + +namespace SocialService.API.Application.Validations; + +/// +/// EN: Validator for FollowUserCommand. +/// VI: Validator cho FollowUserCommand. +/// +public class FollowUserCommandValidator : AbstractValidator +{ + public FollowUserCommandValidator() + { + RuleFor(x => x.FollowerId) + .NotEmpty().WithMessage("Follower ID is required / ID người theo dõi là bắt buộc"); + + RuleFor(x => x.FolloweeId) + .NotEmpty().WithMessage("Followee ID is required / ID người được theo dõi là bắt buộc"); + + RuleFor(x => x) + .Must(x => x.FollowerId != x.FolloweeId) + .WithMessage("Cannot follow yourself / Không thể theo dõi chính mình"); + } +} diff --git a/services/social-service-net/src/SocialService.API/Application/Validations/RespondToFriendRequestCommandValidator.cs b/services/social-service-net/src/SocialService.API/Application/Validations/RespondToFriendRequestCommandValidator.cs new file mode 100644 index 00000000..d1ad1966 --- /dev/null +++ b/services/social-service-net/src/SocialService.API/Application/Validations/RespondToFriendRequestCommandValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using SocialService.API.Application.Commands; + +namespace SocialService.API.Application.Validations; + +/// +/// EN: Validator for RespondToFriendRequestCommand. +/// VI: Validator cho RespondToFriendRequestCommand. +/// +public class RespondToFriendRequestCommandValidator : AbstractValidator +{ + public RespondToFriendRequestCommandValidator() + { + RuleFor(x => x.RelationshipId) + .NotEmpty().WithMessage("Relationship ID is required / ID quan hệ là bắt buộc"); + + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc"); + } +} diff --git a/services/social-service-net/src/SocialService.API/Application/Validations/SendFriendRequestCommandValidator.cs b/services/social-service-net/src/SocialService.API/Application/Validations/SendFriendRequestCommandValidator.cs new file mode 100644 index 00000000..d108bfc6 --- /dev/null +++ b/services/social-service-net/src/SocialService.API/Application/Validations/SendFriendRequestCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using SocialService.API.Application.Commands; + +namespace SocialService.API.Application.Validations; + +/// +/// EN: Validator for SendFriendRequestCommand. +/// VI: Validator cho SendFriendRequestCommand. +/// +public class SendFriendRequestCommandValidator : AbstractValidator +{ + public SendFriendRequestCommandValidator() + { + RuleFor(x => x.RequesterId) + .NotEmpty().WithMessage("Requester ID is required / ID người gửi là bắt buộc"); + + RuleFor(x => x.AddresseeId) + .NotEmpty().WithMessage("Addressee ID is required / ID người nhận là bắt buộc"); + + RuleFor(x => x) + .Must(x => x.RequesterId != x.AddresseeId) + .WithMessage("Cannot send friend request to yourself / Không thể gửi yêu cầu kết bạn cho chính mình"); + } +} diff --git a/services/social-service-net/src/SocialService.API/Application/Validations/UnblockUserCommandValidator.cs b/services/social-service-net/src/SocialService.API/Application/Validations/UnblockUserCommandValidator.cs new file mode 100644 index 00000000..6d18293d --- /dev/null +++ b/services/social-service-net/src/SocialService.API/Application/Validations/UnblockUserCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using SocialService.API.Application.Commands; + +namespace SocialService.API.Application.Validations; + +/// +/// EN: Validator for UnblockUserCommand. +/// VI: Validator cho UnblockUserCommand. +/// +public class UnblockUserCommandValidator : AbstractValidator +{ + public UnblockUserCommandValidator() + { + RuleFor(x => x.BlockerId) + .NotEmpty().WithMessage("Blocker ID is required / ID người block là bắt buộc"); + + RuleFor(x => x.BlockedId) + .NotEmpty().WithMessage("Blocked user ID is required / ID người bị block là bắt buộc"); + + RuleFor(x => x) + .Must(x => x.BlockerId != x.BlockedId) + .WithMessage("Cannot unblock yourself / Không thể bỏ block chính mình"); + } +} diff --git a/services/social-service-net/src/SocialService.API/Application/Validations/UnfollowUserCommandValidator.cs b/services/social-service-net/src/SocialService.API/Application/Validations/UnfollowUserCommandValidator.cs new file mode 100644 index 00000000..e4511edd --- /dev/null +++ b/services/social-service-net/src/SocialService.API/Application/Validations/UnfollowUserCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using SocialService.API.Application.Commands; + +namespace SocialService.API.Application.Validations; + +/// +/// EN: Validator for UnfollowUserCommand. +/// VI: Validator cho UnfollowUserCommand. +/// +public class UnfollowUserCommandValidator : AbstractValidator +{ + public UnfollowUserCommandValidator() + { + RuleFor(x => x.FollowerId) + .NotEmpty().WithMessage("Follower ID is required / ID người theo dõi là bắt buộc"); + + RuleFor(x => x.FolloweeId) + .NotEmpty().WithMessage("Followee ID is required / ID người được theo dõi là bắt buộc"); + + RuleFor(x => x) + .Must(x => x.FollowerId != x.FolloweeId) + .WithMessage("Cannot unfollow yourself / Không thể bỏ theo dõi chính mình"); + } +}