feat(P1): add 57 validators + 10 missing handlers across 13 services
Wave 2 — 3 parallel agents fixing P1 issues: Validators (57 new FluentValidation validators): - ads-manager: 10 validators for all commands - ads-billing: 3 validators for all commands - ads-tracking: 2 validators for missing commands - ads-analytics: 1 validator for CreateReport - social: 8 validators for all commands - mining: 16 validators for all commands - mission: 4 validators for all commands - promotion: 13 validators for all commands Missing handlers (10 implemented): - promotion: ExchangeVoucher, PurchaseVoucher, SearchVouchers, GetCampaignStatistics, GetCampaignVouchers - mission: GetUserMissionProgress - mkt-facebook: GetConversations, GetCustomers - ads-manager: ListAudiences, GetAudienceById All validators use bilingual messages (EN/VI) and are auto-registered via MediatR ValidatorBehavior pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
using FluentValidation;
|
||||
using AdsAnalyticsService.API.Application.Commands;
|
||||
|
||||
namespace AdsAnalyticsService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CreateReportCommand.
|
||||
/// VI: Validator cho CreateReportCommand.
|
||||
/// </summary>
|
||||
public class CreateReportCommandValidator : AbstractValidator<CreateReportCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using AdsBillingService.API.Application.Commands;
|
||||
|
||||
namespace AdsBillingService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for AddFundsCommand.
|
||||
/// VI: Validator cho AddFundsCommand.
|
||||
/// </summary>
|
||||
public class AddFundsCommandValidator : AbstractValidator<AddFundsCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using FluentValidation;
|
||||
using AdsBillingService.API.Application.Commands;
|
||||
|
||||
namespace AdsBillingService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ChargeAdvertiserCommand.
|
||||
/// VI: Validator cho ChargeAdvertiserCommand.
|
||||
/// </summary>
|
||||
public class ChargeAdvertiserCommandValidator : AbstractValidator<ChargeAdvertiserCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using FluentValidation;
|
||||
using AdsBillingService.API.Application.Commands;
|
||||
|
||||
namespace AdsBillingService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CreateBillingAccountCommand.
|
||||
/// VI: Validator cho CreateBillingAccountCommand.
|
||||
/// </summary>
|
||||
public class CreateBillingAccountCommandValidator : AbstractValidator<CreateBillingAccountCommand>
|
||||
{
|
||||
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'");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for ListAudiencesQuery - returns audiences for an advertiser.
|
||||
/// VI: Handler cho ListAudiencesQuery - trả về audiences cho nhà quảng cáo.
|
||||
/// </summary>
|
||||
public class ListAudiencesQueryHandler : IRequestHandler<ListAudiencesQuery, List<AudienceDto>>
|
||||
{
|
||||
private readonly AdsManagerServiceContext _context;
|
||||
|
||||
public ListAudiencesQueryHandler(AdsManagerServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<List<AudienceDto>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetAudienceByIdQuery - returns a single audience by ID.
|
||||
/// VI: Handler cho GetAudienceByIdQuery - trả về một audience theo ID.
|
||||
/// </summary>
|
||||
public class GetAudienceByIdQueryHandler : IRequestHandler<GetAudienceByIdQuery, AudienceDto?>
|
||||
{
|
||||
private readonly AdsManagerServiceContext _context;
|
||||
|
||||
public GetAudienceByIdQueryHandler(AdsManagerServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<AudienceDto?> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ActivateCampaignCommand.
|
||||
/// VI: Validator cho ActivateCampaignCommand.
|
||||
/// </summary>
|
||||
public class ActivateCampaignCommandValidator : AbstractValidator<ActivateCampaignCommand>
|
||||
{
|
||||
public ActivateCampaignCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CampaignId)
|
||||
.NotEmpty()
|
||||
.WithMessage("Campaign ID is required / Campaign ID là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Controllers;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ApproveAdCommand.
|
||||
/// VI: Validator cho ApproveAdCommand.
|
||||
/// </summary>
|
||||
public class ApproveAdCommandValidator : AbstractValidator<ApproveAdCommand>
|
||||
{
|
||||
public ApproveAdCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.AdId)
|
||||
.NotEmpty()
|
||||
.WithMessage("Ad ID is required / Ad ID là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CreateAdCommand.
|
||||
/// VI: Validator cho CreateAdCommand.
|
||||
/// </summary>
|
||||
public class CreateAdCommandValidator : AbstractValidator<CreateAdCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CreateAdSetCommand.
|
||||
/// VI: Validator cho CreateAdSetCommand.
|
||||
/// </summary>
|
||||
public class CreateAdSetCommandValidator : AbstractValidator<CreateAdSetCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CreateCampaignCommand.
|
||||
/// VI: Validator cho CreateCampaignCommand.
|
||||
/// </summary>
|
||||
public class CreateCampaignCommandValidator : AbstractValidator<CreateCampaignCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for DeleteCampaignCommand.
|
||||
/// VI: Validator cho DeleteCampaignCommand.
|
||||
/// </summary>
|
||||
public class DeleteCampaignCommandValidator : AbstractValidator<DeleteCampaignCommand>
|
||||
{
|
||||
public DeleteCampaignCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CampaignId)
|
||||
.NotEmpty()
|
||||
.WithMessage("Campaign ID is required / Campaign ID là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for PauseCampaignCommand.
|
||||
/// VI: Validator cho PauseCampaignCommand.
|
||||
/// </summary>
|
||||
public class PauseCampaignCommandValidator : AbstractValidator<PauseCampaignCommand>
|
||||
{
|
||||
public PauseCampaignCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CampaignId)
|
||||
.NotEmpty()
|
||||
.WithMessage("Campaign ID is required / Campaign ID là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Controllers;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for RejectAdCommand.
|
||||
/// VI: Validator cho RejectAdCommand.
|
||||
/// </summary>
|
||||
public class RejectAdCommandValidator : AbstractValidator<RejectAdCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for SubmitAdForReviewCommand.
|
||||
/// VI: Validator cho SubmitAdForReviewCommand.
|
||||
/// </summary>
|
||||
public class SubmitAdForReviewCommandValidator : AbstractValidator<SubmitAdForReviewCommand>
|
||||
{
|
||||
public SubmitAdForReviewCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.AdId)
|
||||
.NotEmpty()
|
||||
.WithMessage("Ad ID is required / Ad ID là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using FluentValidation;
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
|
||||
namespace AdsManagerService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UpdateCampaignCommand.
|
||||
/// VI: Validator cho UpdateCampaignCommand.
|
||||
/// </summary>
|
||||
public class UpdateCampaignCommandValidator : AbstractValidator<UpdateCampaignCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using FluentValidation;
|
||||
using AdsTrackingService.API.Application.Commands;
|
||||
|
||||
namespace AdsTrackingService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for RecordConversionCommand.
|
||||
/// VI: Validator cho RecordConversionCommand.
|
||||
/// </summary>
|
||||
public class RecordConversionCommandValidator : AbstractValidator<RecordConversionCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using FluentValidation;
|
||||
using AdsTrackingService.API.Application.Commands;
|
||||
|
||||
namespace AdsTrackingService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for TrackPixelEventCommand.
|
||||
/// VI: Validator cho TrackPixelEventCommand.
|
||||
/// </summary>
|
||||
public class TrackPixelEventCommandValidator : AbstractValidator<TrackPixelEventCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for AcceptCircleInviteCommand.
|
||||
/// VI: Validator cho AcceptCircleInviteCommand.
|
||||
/// </summary>
|
||||
public class AcceptCircleInviteCommandValidator : AbstractValidator<AcceptCircleInviteCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for AdjustMinerPointsCommand.
|
||||
/// VI: Validator cho AdjustMinerPointsCommand.
|
||||
/// </summary>
|
||||
public class AdjustMinerPointsCommandValidator : AbstractValidator<AdjustMinerPointsCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ApplyReferralCodeCommand.
|
||||
/// VI: Validator cho ApplyReferralCodeCommand.
|
||||
/// </summary>
|
||||
public class ApplyReferralCodeCommandValidator : AbstractValidator<ApplyReferralCodeCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for BanMinerCommand.
|
||||
/// VI: Validator cho BanMinerCommand.
|
||||
/// </summary>
|
||||
public class BanMinerCommandValidator : AbstractValidator<BanMinerCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ClaimMiningRewardCommand.
|
||||
/// VI: Validator cho ClaimMiningRewardCommand.
|
||||
/// </summary>
|
||||
public class ClaimMiningRewardCommandValidator : AbstractValidator<ClaimMiningRewardCommand>
|
||||
{
|
||||
public ClaimMiningRewardCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CreateCircleCommand.
|
||||
/// VI: Validator cho CreateCircleCommand.
|
||||
/// </summary>
|
||||
public class CreateCircleCommandValidator : AbstractValidator<CreateCircleCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for InviteToCircleCommand.
|
||||
/// VI: Validator cho InviteToCircleCommand.
|
||||
/// </summary>
|
||||
public class InviteToCircleCommandValidator : AbstractValidator<InviteToCircleCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for RemoveCircleMemberCommand.
|
||||
/// VI: Validator cho RemoveCircleMemberCommand.
|
||||
/// </summary>
|
||||
public class RemoveCircleMemberCommandValidator : AbstractValidator<RemoveCircleMemberCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ResetMinerStreakCommand.
|
||||
/// VI: Validator cho ResetMinerStreakCommand.
|
||||
/// </summary>
|
||||
public class ResetMinerStreakCommandValidator : AbstractValidator<ResetMinerStreakCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for RestoreMinerCommand.
|
||||
/// VI: Validator cho RestoreMinerCommand.
|
||||
/// </summary>
|
||||
public class RestoreMinerCommandValidator : AbstractValidator<RestoreMinerCommand>
|
||||
{
|
||||
public RestoreMinerCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.MinerId)
|
||||
.NotEmpty().WithMessage("Miner ID is required / ID thợ đào là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for StartMiningCommand.
|
||||
/// VI: Validator cho StartMiningCommand.
|
||||
/// </summary>
|
||||
public class StartMiningCommandValidator : AbstractValidator<StartMiningCommand>
|
||||
{
|
||||
public StartMiningCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for SuspendMinerCommand.
|
||||
/// VI: Validator cho SuspendMinerCommand.
|
||||
/// </summary>
|
||||
public class SuspendMinerCommandValidator : AbstractValidator<SuspendMinerCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UpdateMiningConfigCommand.
|
||||
/// VI: Validator cho UpdateMiningConfigCommand.
|
||||
/// </summary>
|
||||
public class UpdateMiningConfigCommandValidator : AbstractValidator<UpdateMiningConfigCommand>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UpdateReferralConfigCommand.
|
||||
/// VI: Validator cho UpdateReferralConfigCommand.
|
||||
/// </summary>
|
||||
public class UpdateReferralConfigCommandValidator : AbstractValidator<UpdateReferralConfigCommand>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UpdateStreakConfigCommand.
|
||||
/// VI: Validator cho UpdateStreakConfigCommand.
|
||||
/// </summary>
|
||||
public class UpdateStreakConfigCommandValidator : AbstractValidator<UpdateStreakConfigCommand>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using FluentValidation;
|
||||
using MiningService.API.Application.Commands;
|
||||
|
||||
namespace MiningService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UpdateSystemConfigCommand.
|
||||
/// VI: Validator cho UpdateSystemConfigCommand.
|
||||
/// </summary>
|
||||
public class UpdateSystemConfigCommandValidator : AbstractValidator<UpdateSystemConfigCommand>
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -163,3 +163,113 @@ public class GetMissionsByCategoryQueryHandler : IRequestHandler<GetMissionsByCa
|
||||
return new MissionsListResult(summaries);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetUserMissionProgressQuery - returns user's overall mission progress.
|
||||
/// VI: Handler cho GetUserMissionProgressQuery - trả về tiến độ tổng thể của user trên các mission.
|
||||
/// </summary>
|
||||
public class GetUserMissionProgressQueryHandler : IRequestHandler<GetUserMissionProgressQuery, UserMissionProgressResult>
|
||||
{
|
||||
private readonly IMissionRepository _missionRepository;
|
||||
private readonly IUserTaskRepository _taskRepository;
|
||||
|
||||
public GetUserMissionProgressQueryHandler(
|
||||
IMissionRepository missionRepository,
|
||||
IUserTaskRepository taskRepository)
|
||||
{
|
||||
_missionRepository = missionRepository;
|
||||
_taskRepository = taskRepository;
|
||||
}
|
||||
|
||||
public async Task<UserMissionProgressResult> Handle(GetUserMissionProgressQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// EN: Get all active missions and user's tasks / VI: Lấy tất cả missions đang hoạt động và tasks của user
|
||||
var missions = await _missionRepository.GetActiveMissionsAsync(cancellationToken);
|
||||
var userTasks = await _taskRepository.GetByUserIdAsync(request.UserId, cancellationToken);
|
||||
|
||||
// EN: Separate active and completed tasks / VI: Phân loại tasks đang hoạt động và đã hoàn thành
|
||||
var activeTasks = userTasks
|
||||
.Where(t => t.Status == TaskStatus.InProgress || t.Status == TaskStatus.PendingVerification)
|
||||
.ToList();
|
||||
|
||||
var completedTasks = userTasks
|
||||
.Where(t => t.Status == TaskStatus.Completed)
|
||||
.ToList();
|
||||
|
||||
// EN: Calculate total points earned from claimed rewards
|
||||
// VI: Tính tổng điểm đã nhận từ phần thưởng đã claim
|
||||
var completedMissionIds = completedTasks.Select(t => t.MissionId).ToHashSet();
|
||||
var totalPointsEarned = 0m;
|
||||
foreach (var mission in missions.Where(m => completedMissionIds.Contains(m.Id)))
|
||||
{
|
||||
var taskCount = completedTasks.Count(t => t.MissionId == mission.Id && t.RewardClaimed);
|
||||
totalPointsEarned += mission.Reward.Points * taskCount;
|
||||
}
|
||||
|
||||
// EN: Map active missions with progress / VI: Map các mission đang hoạt động với tiến độ
|
||||
var activeMissionSummaries = missions
|
||||
.Where(m => activeTasks.Any(t => t.MissionId == m.Id))
|
||||
.Select(m =>
|
||||
{
|
||||
var task = activeTasks.First(t => t.MissionId == m.Id);
|
||||
return new MissionSummaryDto(
|
||||
Id: m.Id,
|
||||
Code: m.Code,
|
||||
Title: m.TitleEn,
|
||||
Description: m.DescriptionEn,
|
||||
Type: m.Type.Name,
|
||||
Category: m.Category.Name,
|
||||
RewardPoints: m.Reward.Points,
|
||||
Frequency: m.Frequency.Name,
|
||||
MaxCompletions: m.MaxCompletions,
|
||||
IsAvailable: m.IsAvailable(),
|
||||
UserProgress: new UserTaskProgressDto(
|
||||
TaskId: task.Id,
|
||||
CurrentValue: task.Progress.CurrentValue,
|
||||
TargetValue: task.Progress.TargetValue,
|
||||
PercentComplete: task.Progress.PercentComplete,
|
||||
Status: task.Status.Name,
|
||||
StartedAt: task.StartedAt,
|
||||
CompletedAt: task.CompletedAt,
|
||||
RewardClaimed: task.RewardClaimed));
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// EN: Map completed missions / VI: Map các mission đã hoàn thành
|
||||
var completedMissionSummaries = missions
|
||||
.Where(m => completedMissionIds.Contains(m.Id))
|
||||
.Select(m =>
|
||||
{
|
||||
var task = completedTasks.First(t => t.MissionId == m.Id);
|
||||
return new MissionSummaryDto(
|
||||
Id: m.Id,
|
||||
Code: m.Code,
|
||||
Title: m.TitleEn,
|
||||
Description: m.DescriptionEn,
|
||||
Type: m.Type.Name,
|
||||
Category: m.Category.Name,
|
||||
RewardPoints: m.Reward.Points,
|
||||
Frequency: m.Frequency.Name,
|
||||
MaxCompletions: m.MaxCompletions,
|
||||
IsAvailable: m.IsAvailable(),
|
||||
UserProgress: new UserTaskProgressDto(
|
||||
TaskId: task.Id,
|
||||
CurrentValue: task.Progress.CurrentValue,
|
||||
TargetValue: task.Progress.TargetValue,
|
||||
PercentComplete: task.Progress.PercentComplete,
|
||||
Status: task.Status.Name,
|
||||
StartedAt: task.StartedAt,
|
||||
CompletedAt: task.CompletedAt,
|
||||
RewardClaimed: task.RewardClaimed));
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new UserMissionProgressResult(
|
||||
TotalMissions: missions.Count,
|
||||
CompletedMissionsCount: completedMissionIds.Count,
|
||||
InProgressMissions: activeTasks.Count,
|
||||
TotalPointsEarned: totalPointsEarned,
|
||||
ActiveMissions: activeMissionSummaries,
|
||||
CompletedMissions: completedMissionSummaries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using FluentValidation;
|
||||
using MissionService.API.Application.Commands;
|
||||
|
||||
namespace MissionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ClaimTaskRewardCommand.
|
||||
/// VI: Validator cho ClaimTaskRewardCommand.
|
||||
/// </summary>
|
||||
public class ClaimTaskRewardCommandValidator : AbstractValidator<ClaimTaskRewardCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using MissionService.API.Application.Commands;
|
||||
|
||||
namespace MissionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for PerformCheckInCommand.
|
||||
/// VI: Validator cho PerformCheckInCommand.
|
||||
/// </summary>
|
||||
public class PerformCheckInCommandValidator : AbstractValidator<PerformCheckInCommand>
|
||||
{
|
||||
public PerformCheckInCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.NotEmpty().WithMessage("User ID is required / ID người dùng là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using FluentValidation;
|
||||
using MissionService.API.Application.Commands;
|
||||
|
||||
namespace MissionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for StartMissionTaskCommand.
|
||||
/// VI: Validator cho StartMissionTaskCommand.
|
||||
/// </summary>
|
||||
public class StartMissionTaskCommandValidator : AbstractValidator<StartMissionTaskCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using FluentValidation;
|
||||
using MissionService.API.Application.Commands;
|
||||
|
||||
namespace MissionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UpdateTaskProgressCommand.
|
||||
/// VI: Validator cho UpdateTaskProgressCommand.
|
||||
/// </summary>
|
||||
public class UpdateTaskProgressCommandValidator : AbstractValidator<UpdateTaskProgressCommand>
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetConversationsQuery - returns paginated conversations list.
|
||||
/// VI: Handler cho GetConversationsQuery - trả về danh sách conversations phân trang.
|
||||
/// </summary>
|
||||
public class GetConversationsQueryHandler : IRequestHandler<GetConversationsQuery, GetConversationsQueryResult>
|
||||
{
|
||||
private readonly FacebookServiceContext _context;
|
||||
|
||||
public GetConversationsQueryHandler(FacebookServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<GetConversationsQueryResult> 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<string>(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<DateTime?>(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetConversationByIdQuery.
|
||||
/// VI: Handler cho GetConversationByIdQuery.
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetCustomersQuery - returns paginated customers list.
|
||||
/// VI: Handler cho GetCustomersQuery - trả về danh sách customers phân trang.
|
||||
/// </summary>
|
||||
public class GetCustomersQueryHandler : IRequestHandler<GetCustomersQuery, GetCustomersQueryResult>
|
||||
{
|
||||
private readonly FacebookServiceContext _context;
|
||||
|
||||
public GetCustomersQueryHandler(FacebookServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<GetCustomersQueryResult> 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<string?>(c, "_name") != null &&
|
||||
EF.Property<string>(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<DateTime>(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetCustomerByIdQuery.
|
||||
/// VI: Handler cho GetCustomerByIdQuery.
|
||||
|
||||
@@ -9,6 +9,163 @@ using PromotionService.Domain.Exceptions;
|
||||
|
||||
namespace PromotionService.API.Application.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for ExchangeVoucherCommand (exchange points for voucher).
|
||||
/// VI: Handler cho ExchangeVoucherCommand (đổi điểm lấy voucher).
|
||||
/// </summary>
|
||||
public class ExchangeVoucherCommandHandler : IRequestHandler<ExchangeVoucherCommand, VoucherDto>
|
||||
{
|
||||
private readonly ICampaignRepository _campaignRepository;
|
||||
private readonly IWalletServiceClient _walletService;
|
||||
private readonly ILogger<ExchangeVoucherCommandHandler> _logger;
|
||||
|
||||
public ExchangeVoucherCommandHandler(
|
||||
ICampaignRepository campaignRepository,
|
||||
IWalletServiceClient walletService,
|
||||
ILogger<ExchangeVoucherCommandHandler> logger)
|
||||
{
|
||||
_campaignRepository = campaignRepository;
|
||||
_walletService = walletService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<VoucherDto> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for PurchaseVoucherCommand (buy voucher with currency).
|
||||
/// VI: Handler cho PurchaseVoucherCommand (mua voucher bằng tiền).
|
||||
/// </summary>
|
||||
public class PurchaseVoucherCommandHandler : IRequestHandler<PurchaseVoucherCommand, VoucherDto>
|
||||
{
|
||||
private readonly ICampaignRepository _campaignRepository;
|
||||
private readonly IWalletServiceClient _walletService;
|
||||
private readonly ILogger<PurchaseVoucherCommandHandler> _logger;
|
||||
|
||||
public PurchaseVoucherCommandHandler(
|
||||
ICampaignRepository campaignRepository,
|
||||
IWalletServiceClient walletService,
|
||||
ILogger<PurchaseVoucherCommandHandler> logger)
|
||||
{
|
||||
_campaignRepository = campaignRepository;
|
||||
_walletService = walletService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<VoucherDto> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for ClaimVoucherCommand (free vouchers).
|
||||
/// VI: Handler cho ClaimVoucherCommand (voucher miễn phí).
|
||||
|
||||
@@ -258,6 +258,126 @@ public class GetAllRedemptionsQueryHandler : IRequestHandler<GetAllRedemptionsQu
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for SearchVouchersQuery - search vouchers by code pattern.
|
||||
/// VI: Handler cho SearchVouchersQuery - tìm kiếm vouchers theo mẫu mã.
|
||||
/// </summary>
|
||||
public class SearchVouchersQueryHandler : IRequestHandler<SearchVouchersQuery, IEnumerable<AdminVoucherListDto>>
|
||||
{
|
||||
private readonly PromotionServiceContext _context;
|
||||
|
||||
public SearchVouchersQueryHandler(PromotionServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AdminVoucherListDto>> 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<AdminVoucherListDto>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class GetCampaignVouchersQueryHandler : IRequestHandler<GetCampaignVouchersQuery, PaginatedResponse<AdminVoucherListDto>>
|
||||
{
|
||||
private readonly PromotionServiceContext _context;
|
||||
|
||||
public GetCampaignVouchersQueryHandler(PromotionServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<PaginatedResponse<AdminVoucherListDto>> 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<AdminVoucherListDto>(items, totalCount, request.PageNumber, request.PageSize, totalPages);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetRedemptionStatisticsQuery.
|
||||
/// VI: Handler cho GetRedemptionStatisticsQuery.
|
||||
|
||||
@@ -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<GetUserVouchersQuery,
|
||||
v.ExpiresAt));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public class GetCampaignStatisticsQueryHandler : IRequestHandler<GetCampaignStatisticsQuery, CampaignStatisticsDto?>
|
||||
{
|
||||
private readonly ICampaignRepository _campaignRepository;
|
||||
private readonly IRedemptionRepository _redemptionRepository;
|
||||
|
||||
public GetCampaignStatisticsQueryHandler(
|
||||
ICampaignRepository campaignRepository,
|
||||
IRedemptionRepository redemptionRepository)
|
||||
{
|
||||
_campaignRepository = campaignRepository;
|
||||
_redemptionRepository = redemptionRepository;
|
||||
}
|
||||
|
||||
public async Task<CampaignStatisticsDto?> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ActivateCampaignCommand.
|
||||
/// VI: Validator cho ActivateCampaignCommand.
|
||||
/// </summary>
|
||||
public class ActivateCampaignCommandValidator : AbstractValidator<ActivateCampaignCommand>
|
||||
{
|
||||
public ActivateCampaignCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CampaignId)
|
||||
.NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CancelCampaignCommand.
|
||||
/// VI: Validator cho CancelCampaignCommand.
|
||||
/// </summary>
|
||||
public class CancelCampaignCommandValidator : AbstractValidator<CancelCampaignCommand>
|
||||
{
|
||||
public CancelCampaignCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CampaignId)
|
||||
.NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ClaimVoucherCommand.
|
||||
/// VI: Validator cho ClaimVoucherCommand.
|
||||
/// </summary>
|
||||
public class ClaimVoucherCommandValidator : AbstractValidator<ClaimVoucherCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CompleteCampaignCommand.
|
||||
/// VI: Validator cho CompleteCampaignCommand.
|
||||
/// </summary>
|
||||
public class CompleteCampaignCommandValidator : AbstractValidator<CompleteCampaignCommand>
|
||||
{
|
||||
public CompleteCampaignCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CampaignId)
|
||||
.NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for CreateCampaignCommand.
|
||||
/// VI: Validator cho CreateCampaignCommand.
|
||||
/// </summary>
|
||||
public class CreateCampaignCommandValidator : AbstractValidator<CreateCampaignCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for DeleteCampaignCommand.
|
||||
/// VI: Validator cho DeleteCampaignCommand.
|
||||
/// </summary>
|
||||
public class DeleteCampaignCommandValidator : AbstractValidator<DeleteCampaignCommand>
|
||||
{
|
||||
public DeleteCampaignCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CampaignId)
|
||||
.NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ExchangeVoucherCommand.
|
||||
/// VI: Validator cho ExchangeVoucherCommand.
|
||||
/// </summary>
|
||||
public class ExchangeVoucherCommandValidator : AbstractValidator<ExchangeVoucherCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for ExtendVoucherExpiryCommand.
|
||||
/// VI: Validator cho ExtendVoucherExpiryCommand.
|
||||
/// </summary>
|
||||
public class ExtendVoucherExpiryCommandValidator : AbstractValidator<ExtendVoucherExpiryCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for PauseCampaignCommand.
|
||||
/// VI: Validator cho PauseCampaignCommand.
|
||||
/// </summary>
|
||||
public class PauseCampaignCommandValidator : AbstractValidator<PauseCampaignCommand>
|
||||
{
|
||||
public PauseCampaignCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.CampaignId)
|
||||
.NotEmpty().WithMessage("Campaign ID is required / ID chiến dịch là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for PurchaseVoucherCommand.
|
||||
/// VI: Validator cho PurchaseVoucherCommand.
|
||||
/// </summary>
|
||||
public class PurchaseVoucherCommandValidator : AbstractValidator<PurchaseVoucherCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for RedeemVoucherCommand.
|
||||
/// VI: Validator cho RedeemVoucherCommand.
|
||||
/// </summary>
|
||||
public class RedeemVoucherCommandValidator : AbstractValidator<RedeemVoucherCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for RevokeVoucherCommand.
|
||||
/// VI: Validator cho RevokeVoucherCommand.
|
||||
/// </summary>
|
||||
public class RevokeVoucherCommandValidator : AbstractValidator<RevokeVoucherCommand>
|
||||
{
|
||||
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ự");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using FluentValidation;
|
||||
using PromotionService.API.Application.Commands;
|
||||
|
||||
namespace PromotionService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UpdateCampaignCommand.
|
||||
/// VI: Validator cho UpdateCampaignCommand.
|
||||
/// </summary>
|
||||
public class UpdateCampaignCommandValidator : AbstractValidator<UpdateCampaignCommand>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using SocialService.API.Application.Commands;
|
||||
|
||||
namespace SocialService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for AdminDeleteBlockCommand.
|
||||
/// VI: Validator cho AdminDeleteBlockCommand.
|
||||
/// </summary>
|
||||
public class AdminDeleteBlockCommandValidator : AbstractValidator<AdminDeleteBlockCommand>
|
||||
{
|
||||
public AdminDeleteBlockCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.BlockId)
|
||||
.NotEmpty().WithMessage("Block ID is required / ID block là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using SocialService.API.Application.Commands;
|
||||
|
||||
namespace SocialService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for AdminDeleteRelationshipCommand.
|
||||
/// VI: Validator cho AdminDeleteRelationshipCommand.
|
||||
/// </summary>
|
||||
public class AdminDeleteRelationshipCommandValidator : AbstractValidator<AdminDeleteRelationshipCommand>
|
||||
{
|
||||
public AdminDeleteRelationshipCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.RelationshipId)
|
||||
.NotEmpty().WithMessage("Relationship ID is required / ID quan hệ là bắt buộc");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using FluentValidation;
|
||||
using SocialService.API.Application.Commands;
|
||||
|
||||
namespace SocialService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for BlockUserCommand.
|
||||
/// VI: Validator cho BlockUserCommand.
|
||||
/// </summary>
|
||||
public class BlockUserCommandValidator : AbstractValidator<BlockUserCommand>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using SocialService.API.Application.Commands;
|
||||
|
||||
namespace SocialService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for FollowUserCommand.
|
||||
/// VI: Validator cho FollowUserCommand.
|
||||
/// </summary>
|
||||
public class FollowUserCommandValidator : AbstractValidator<FollowUserCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using FluentValidation;
|
||||
using SocialService.API.Application.Commands;
|
||||
|
||||
namespace SocialService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for RespondToFriendRequestCommand.
|
||||
/// VI: Validator cho RespondToFriendRequestCommand.
|
||||
/// </summary>
|
||||
public class RespondToFriendRequestCommandValidator : AbstractValidator<RespondToFriendRequestCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using SocialService.API.Application.Commands;
|
||||
|
||||
namespace SocialService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for SendFriendRequestCommand.
|
||||
/// VI: Validator cho SendFriendRequestCommand.
|
||||
/// </summary>
|
||||
public class SendFriendRequestCommandValidator : AbstractValidator<SendFriendRequestCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using SocialService.API.Application.Commands;
|
||||
|
||||
namespace SocialService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UnblockUserCommand.
|
||||
/// VI: Validator cho UnblockUserCommand.
|
||||
/// </summary>
|
||||
public class UnblockUserCommandValidator : AbstractValidator<UnblockUserCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
using SocialService.API.Application.Commands;
|
||||
|
||||
namespace SocialService.API.Application.Validations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Validator for UnfollowUserCommand.
|
||||
/// VI: Validator cho UnfollowUserCommand.
|
||||
/// </summary>
|
||||
public class UnfollowUserCommandValidator : AbstractValidator<UnfollowUserCommand>
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user