diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/ChangeSampleStatusCommand.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/ChangeSampleStatusCommand.cs deleted file mode 100644 index fbb0ff2b..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/ChangeSampleStatusCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MediatR; - -namespace AdsAnalyticsService.API.Application.Commands; - -/// -/// EN: Command to change status of a Sample. -/// VI: Command để thay đổi trạng thái của Sample. -/// -/// EN: Sample ID / VI: ID sample -/// EN: New status (activate, complete, cancel) / VI: Trạng thái mới (activate, complete, cancel) -public record ChangeSampleStatusCommand( - Guid SampleId, - string NewStatus -) : IRequest; diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs deleted file mode 100644 index f08db449..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MediatR; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.API.Application.Commands; - -/// -/// EN: Handler for ChangeSampleStatusCommand. -/// VI: Handler cho ChangeSampleStatusCommand. -/// -public class ChangeSampleStatusCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public ChangeSampleStatusCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - ChangeSampleStatusCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Changing status of sample {SampleId} to {NewStatus} / Thay đổi trạng thái sample {SampleId} thành {NewStatus}", - request.SampleId, request.NewStatus); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Change status based on action / VI: Thay đổi trạng thái dựa trên action - switch (request.NewStatus.ToLowerInvariant()) - { - case "activate": - sample.Activate(); - break; - case "complete": - sample.Complete(); - break; - case "cancel": - sample.Cancel(); - break; - default: - _logger.LogWarning( - "Invalid status action: {NewStatus} / Action trạng thái không hợp lệ: {NewStatus}", - request.NewStatus); - return false; - } - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} status changed to {NewStatus} / Trạng thái sample {SampleId} đã đổi thành {NewStatus}", - request.SampleId, request.NewStatus); - - return true; - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/CreateSampleCommand.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/CreateSampleCommand.cs deleted file mode 100644 index 72adc28d..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/CreateSampleCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MediatR; - -namespace AdsAnalyticsService.API.Application.Commands; - -/// -/// EN: Command to create a new Sample. -/// VI: Command để tạo một Sample mới. -/// -/// EN: Sample name / VI: Tên sample -/// EN: Optional description / VI: Mô tả tùy chọn -public record CreateSampleCommand( - string Name, - string? Description -) : IRequest; - -/// -/// EN: Result of CreateSampleCommand. -/// VI: Kết quả của CreateSampleCommand. -/// -/// EN: Created sample ID / VI: ID sample đã tạo -public record CreateSampleCommandResult(Guid Id); diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/CreateSampleCommandHandler.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/CreateSampleCommandHandler.cs deleted file mode 100644 index ef8ac79b..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/CreateSampleCommandHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediatR; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.API.Application.Commands; - -/// -/// EN: Handler for CreateSampleCommand. -/// VI: Handler cho CreateSampleCommand. -/// -public class CreateSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public CreateSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - CreateSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Creating new sample with name: {Name} / Tạo sample mới với tên: {Name}", - request.Name); - - // EN: Create domain entity / VI: Tạo domain entity - var sample = new Sample(request.Name, request.Description); - - // EN: Add to repository / VI: Thêm vào repository - _sampleRepository.Add(sample); - - // EN: Save changes (dispatches domain events) / VI: Lưu thay đổi (dispatch domain events) - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample created successfully with ID: {SampleId} / Sample đã tạo thành công với ID: {SampleId}", - sample.Id); - - return new CreateSampleCommandResult(sample.Id); - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/DeleteSampleCommand.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/DeleteSampleCommand.cs deleted file mode 100644 index b0af54cd..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/DeleteSampleCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MediatR; - -namespace AdsAnalyticsService.API.Application.Commands; - -/// -/// EN: Command to delete a Sample. -/// VI: Command để xóa một Sample. -/// -/// EN: Sample ID to delete / VI: ID sample cần xóa -public record DeleteSampleCommand(Guid SampleId) : IRequest; diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/DeleteSampleCommandHandler.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/DeleteSampleCommandHandler.cs deleted file mode 100644 index 0a98020a..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/DeleteSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.API.Application.Commands; - -/// -/// EN: Handler for DeleteSampleCommand. -/// VI: Handler cho DeleteSampleCommand. -/// -public class DeleteSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public DeleteSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - DeleteSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Deleting sample {SampleId} / Xóa sample {SampleId}", - request.SampleId); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Delete sample / VI: Xóa sample - _sampleRepository.Delete(sample); - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} deleted successfully / Sample {SampleId} đã xóa thành công", - request.SampleId); - - return true; - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/UpdateSampleCommand.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/UpdateSampleCommand.cs deleted file mode 100644 index 65e2ed4f..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/UpdateSampleCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; - -namespace AdsAnalyticsService.API.Application.Commands; - -/// -/// EN: Command to update an existing Sample. -/// VI: Command để cập nhật một Sample đã tồn tại. -/// -/// EN: Sample ID to update / VI: ID sample cần cập nhật -/// EN: New name / VI: Tên mới -/// EN: New description / VI: Mô tả mới -public record UpdateSampleCommand( - Guid SampleId, - string Name, - string? Description -) : IRequest; diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/UpdateSampleCommandHandler.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/UpdateSampleCommandHandler.cs deleted file mode 100644 index be602020..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Commands/UpdateSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.API.Application.Commands; - -/// -/// EN: Handler for UpdateSampleCommand. -/// VI: Handler cho UpdateSampleCommand. -/// -public class UpdateSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public UpdateSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - UpdateSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Updating sample {SampleId} / Cập nhật sample {SampleId}", - request.SampleId); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Update sample using domain method / VI: Cập nhật sample sử dụng domain method - sample.Update(request.Name, request.Description); - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} updated successfully / Sample {SampleId} đã cập nhật thành công", - request.SampleId); - - return true; - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSampleQuery.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSampleQuery.cs deleted file mode 100644 index 646a4389..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSampleQuery.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediatR; - -namespace AdsAnalyticsService.API.Application.Queries; - -/// -/// EN: Query to get a Sample by ID. -/// VI: Query để lấy một Sample theo ID. -/// -/// EN: Sample ID / VI: ID sample -public record GetSampleQuery(Guid SampleId) : IRequest; - -/// -/// EN: Sample view model for API responses. -/// VI: Sample view model cho API responses. -/// -public record SampleViewModel( - Guid Id, - string Name, - string? Description, - string Status, - DateTime CreatedAt, - DateTime? UpdatedAt -); diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSampleQueryHandler.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSampleQueryHandler.cs deleted file mode 100644 index 5555c419..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSampleQueryHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.API.Application.Queries; - -/// -/// EN: Handler for GetSampleQuery. -/// VI: Handler cho GetSampleQuery. -/// -public class GetSampleQueryHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - - public GetSampleQueryHandler(ISampleRepository sampleRepository) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - } - - public async Task Handle( - GetSampleQuery request, - CancellationToken cancellationToken) - { - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - return null; - } - - return new SampleViewModel( - sample.Id, - sample.Name, - sample.Description, - sample.Status.Name, - sample.CreatedAt, - sample.UpdatedAt - ); - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSamplesQuery.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSamplesQuery.cs deleted file mode 100644 index 4ee4e5d5..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSamplesQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MediatR; - -namespace AdsAnalyticsService.API.Application.Queries; - -/// -/// EN: Query to get all Samples. -/// VI: Query để lấy tất cả Samples. -/// -public record GetSamplesQuery : IRequest>; diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSamplesQueryHandler.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSamplesQueryHandler.cs deleted file mode 100644 index 970dfa85..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Queries/GetSamplesQueryHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MediatR; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.API.Application.Queries; - -/// -/// EN: Handler for GetSamplesQuery. -/// VI: Handler cho GetSamplesQuery. -/// -public class GetSamplesQueryHandler : IRequestHandler> -{ - private readonly ISampleRepository _sampleRepository; - - public GetSamplesQueryHandler(ISampleRepository sampleRepository) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - } - - public async Task> Handle( - GetSamplesQuery request, - CancellationToken cancellationToken) - { - var samples = await _sampleRepository.GetAllAsync(); - - return samples.Select(sample => new SampleViewModel( - sample.Id, - sample.Name, - sample.Description, - sample.Status.Name, - sample.CreatedAt, - sample.UpdatedAt - )); - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/CreateSampleCommandValidator.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/CreateSampleCommandValidator.cs deleted file mode 100644 index ede6c3ff..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/CreateSampleCommandValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FluentValidation; -using AdsAnalyticsService.API.Application.Commands; - -namespace AdsAnalyticsService.API.Application.Validations; - -/// -/// EN: Validator for CreateSampleCommand. -/// VI: Validator cho CreateSampleCommand. -/// -public class CreateSampleCommandValidator : AbstractValidator -{ - public CreateSampleCommandValidator() - { - RuleFor(x => x.Name) - .NotEmpty() - .WithMessage("Name is required / Tên là bắt buộc") - .MaximumLength(200) - .WithMessage("Name must be less than 200 characters / Tên phải ít hơn 200 ký tự"); - - RuleFor(x => x.Description) - .MaximumLength(1000) - .WithMessage("Description must be less than 1000 characters / Mô tả phải ít hơn 1000 ký tự") - .When(x => x.Description != null); - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/UpdateSampleCommandValidator.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/UpdateSampleCommandValidator.cs deleted file mode 100644 index 7a65c46e..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Application/Validations/UpdateSampleCommandValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using AdsAnalyticsService.API.Application.Commands; - -namespace AdsAnalyticsService.API.Application.Validations; - -/// -/// EN: Validator for UpdateSampleCommand. -/// VI: Validator cho UpdateSampleCommand. -/// -public class UpdateSampleCommandValidator : AbstractValidator -{ - public UpdateSampleCommandValidator() - { - RuleFor(x => x.SampleId) - .NotEmpty() - .WithMessage("Sample ID is required / ID sample là bắt buộc"); - - RuleFor(x => x.Name) - .NotEmpty() - .WithMessage("Name is required / Tên là bắt buộc") - .MaximumLength(200) - .WithMessage("Name must be less than 200 characters / Tên phải ít hơn 200 ký tự"); - - RuleFor(x => x.Description) - .MaximumLength(1000) - .WithMessage("Description must be less than 1000 characters / Mô tả phải ít hơn 1000 ký tự") - .When(x => x.Description != null); - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Controllers/SamplesController.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Controllers/SamplesController.cs deleted file mode 100644 index eec58e4b..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.API/Controllers/SamplesController.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Asp.Versioning; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using AdsAnalyticsService.API.Application.Commands; -using AdsAnalyticsService.API.Application.Queries; - -namespace AdsAnalyticsService.API.Controllers; - -/// -/// EN: Controller for Sample CRUD operations using CQRS pattern. -/// VI: Controller cho các thao tác CRUD Sample sử dụng pattern CQRS. -/// -[ApiController] -[ApiVersion("1.0")] -[Route("api/v{version:apiVersion}/[controller]")] -[Produces("application/json")] -public class SamplesController : ControllerBase -{ - private readonly IMediator _mediator; - private readonly ILogger _logger; - - public SamplesController(IMediator mediator, ILogger logger) - { - _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - /// EN: Get all samples. - /// VI: Lấy tất cả samples. - /// - /// EN: List of samples / VI: Danh sách samples - [HttpGet] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public async Task GetSamples() - { - var samples = await _mediator.Send(new GetSamplesQuery()); - return Ok(new { success = true, data = samples }); - } - - /// - /// EN: Get a sample by ID. - /// VI: Lấy một sample theo ID. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Sample details / VI: Chi tiết sample - [HttpGet("{id:guid}")] - [ProducesResponseType(typeof(SampleViewModel), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetSample(Guid id) - { - var sample = await _mediator.Send(new GetSampleQuery(id)); - - if (sample is null) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return Ok(new { success = true, data = sample }); - } - - /// - /// EN: Create a new sample. - /// VI: Tạo một sample mới. - /// - /// EN: Create request / VI: Request tạo - /// EN: Created sample ID / VI: ID sample đã tạo - [HttpPost] - [ProducesResponseType(typeof(CreateSampleCommandResult), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task CreateSample([FromBody] CreateSampleRequest request) - { - var command = new CreateSampleCommand(request.Name, request.Description); - var result = await _mediator.Send(command); - - return CreatedAtAction( - nameof(GetSample), - new { id = result.Id }, - new { success = true, data = result }); - } - - /// - /// EN: Update an existing sample. - /// VI: Cập nhật một sample đã tồn tại. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Update request / VI: Request cập nhật - /// EN: Success status / VI: Trạng thái thành công - [HttpPut("{id:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task UpdateSample(Guid id, [FromBody] UpdateSampleRequest request) - { - var command = new UpdateSampleCommand(id, request.Name, request.Description); - var result = await _mediator.Send(command); - - if (!result) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return Ok(new { success = true, message = "Sample updated successfully / Sample đã cập nhật thành công" }); - } - - /// - /// EN: Delete a sample. - /// VI: Xóa một sample. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Success status / VI: Trạng thái thành công - [HttpDelete("{id:guid}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteSample(Guid id) - { - var command = new DeleteSampleCommand(id); - var result = await _mediator.Send(command); - - if (!result) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return NoContent(); - } - - /// - /// EN: Change sample status. - /// VI: Thay đổi trạng thái sample. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Status change request / VI: Request thay đổi trạng thái - /// EN: Success status / VI: Trạng thái thành công - [HttpPatch("{id:guid}/status")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task ChangeSampleStatus(Guid id, [FromBody] ChangeStatusRequest request) - { - var command = new ChangeSampleStatusCommand(id, request.Status); - var result = await _mediator.Send(command); - - if (!result) - { - return BadRequest(new - { - success = false, - error = new - { - code = "STATUS_CHANGE_FAILED", - message = "Failed to change sample status / Thay đổi trạng thái sample thất bại" - } - }); - } - - return Ok(new { success = true, message = "Sample status changed successfully / Trạng thái sample đã thay đổi thành công" }); - } -} - -/// -/// EN: Request model for creating a sample. -/// VI: Model request để tạo sample. -/// -public record CreateSampleRequest(string Name, string? Description); - -/// -/// EN: Request model for updating a sample. -/// VI: Model request để cập nhật sample. -/// -public record UpdateSampleRequest(string Name, string? Description); - -/// -/// EN: Request model for changing sample status. -/// VI: Model request để thay đổi trạng thái sample. -/// -public record ChangeStatusRequest(string Status); diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/MetricsAggregate/CampaignMetrics.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/MetricsAggregate/CampaignMetrics.cs new file mode 100644 index 00000000..6436e3f9 --- /dev/null +++ b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/MetricsAggregate/CampaignMetrics.cs @@ -0,0 +1,47 @@ +using AdsAnalyticsService.Domain.SeedWork; + +namespace AdsAnalyticsService.Domain.AggregatesModel.MetricsAggregate; + +/// +/// EN: Campaign metrics aggregate root - performance metrics per campaign. +/// VI: Campaign metrics aggregate root - chỉ số hiệu suất theo campaign. +/// +public class CampaignMetrics : Entity, IAggregateRoot +{ + private Guid _campaignId; + private DateTime _date; + private long _impressions; + private long _clicks; + private long _conversions; + private decimal _spend; + private decimal _revenue; + + public Guid CampaignId => _campaignId; + public DateTime Date => _date; + public long Impressions => _impressions; + public long Clicks => _clicks; + public long Conversions => _conversions; + public decimal Spend => _spend; + public decimal Revenue => _revenue; + + // Calculated metrics + public decimal CTR => _impressions > 0 ? (decimal)_clicks / _impressions * 100 : 0; + public decimal CPC => _clicks > 0 ? _spend / _clicks : 0; + public decimal CPA => _conversions > 0 ? _spend / _conversions : 0; + public decimal ROAS => _spend > 0 ? _revenue / _spend : 0; + + protected CampaignMetrics() { } + + public CampaignMetrics(Guid campaignId, DateTime date) + { + Id = Guid.NewGuid(); + _campaignId = campaignId; + _date = date.Date; // Normalize to start of day + } + + public void RecordImpression() => _impressions++; + public void RecordClick() => _clicks++; + public void RecordConversion() => _conversions++; + public void AddSpend(decimal amount) => _spend += amount; + public void AddRevenue(decimal amount) => _revenue += amount; +} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/ReportAggregate/Report.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/ReportAggregate/Report.cs new file mode 100644 index 00000000..bbcaf77c --- /dev/null +++ b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/ReportAggregate/Report.cs @@ -0,0 +1,65 @@ +using AdsAnalyticsService.Domain.SeedWork; + +namespace AdsAnalyticsService.Domain.AggregatesModel.ReportAggregate; + +/// +/// EN: Report aggregate root - scheduled or custom reports. +/// VI: Report aggregate root - báo cáo theo lịch hoặc tùy chỉnh. +/// +public class Report : Entity, IAggregateRoot +{ + private Guid _advertiserId; + private string _name = null!; + private ReportType _reportType; + private DateTime _startDate; + private DateTime _endDate; + private ReportStatus _status; + private string? _dataJson; + + public Guid AdvertiserId => _advertiserId; + public string Name => _name; + public ReportType ReportType => _reportType; + public DateTime StartDate => _startDate; + public DateTime EndDate => _endDate; + public ReportStatus Status => _status; + public string? DataJson => _dataJson; + public DateTime CreatedAt { get; private set; } + + protected Report() { } + + public Report(Guid advertiserId, string name, ReportType reportType, DateTime startDate, DateTime endDate) + { + Id = Guid.NewGuid(); + _advertiserId = advertiserId; + _name = name; + _reportType = reportType; + _startDate = startDate; + _endDate = endDate; + _status = ReportStatus.Pending; + CreatedAt = DateTime.UtcNow; + } + + public void MarkAsProcessing() => _status = ReportStatus.Processing; + public void Complete(string dataJson) + { + _dataJson = dataJson; + _status = ReportStatus.Completed; + } + public void Fail() => _status = ReportStatus.Failed; +} + +public enum ReportType +{ + Campaign = 1, + AdSet = 2, + Ad = 3, + Audience = 4 +} + +public enum ReportStatus +{ + Pending = 1, + Processing = 2, + Completed = 3, + Failed = 4 +} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs deleted file mode 100644 index 6d19f663..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs +++ /dev/null @@ -1,61 +0,0 @@ -using AdsAnalyticsService.Domain.SeedWork; - -namespace AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Repository interface for Sample aggregate. -/// VI: Interface repository cho Sample aggregate. -/// -/// -/// EN: Following repository pattern, this interface defines the contract -/// for data access operations on Sample aggregate. -/// VI: Theo pattern repository, interface này định nghĩa contract -/// cho các thao tác truy cập dữ liệu trên Sample aggregate. -/// -public interface ISampleRepository : IRepository -{ - /// - /// EN: Get a sample by its ID. - /// VI: Lấy một sample theo ID. - /// - /// EN: The sample ID / VI: ID của sample - /// EN: The sample or null if not found / VI: Sample hoặc null nếu không tìm thấy - Task GetAsync(Guid sampleId); - - /// - /// EN: Get all samples. - /// VI: Lấy tất cả samples. - /// - /// EN: List of samples / VI: Danh sách samples - Task> GetAllAsync(); - - /// - /// EN: Add a new sample. - /// VI: Thêm một sample mới. - /// - /// EN: The sample to add / VI: Sample cần thêm - /// EN: The added sample / VI: Sample đã thêm - Sample Add(Sample sample); - - /// - /// EN: Update an existing sample. - /// VI: Cập nhật một sample đã tồn tại. - /// - /// EN: The sample to update / VI: Sample cần cập nhật - void Update(Sample sample); - - /// - /// EN: Delete a sample. - /// VI: Xóa một sample. - /// - /// EN: The sample to delete / VI: Sample cần xóa - void Delete(Sample sample); - - /// - /// EN: Get samples by status. - /// VI: Lấy samples theo trạng thái. - /// - /// EN: The status ID / VI: ID trạng thái - /// EN: List of samples with given status / VI: Danh sách samples với trạng thái cho trước - Task> GetByStatusAsync(int statusId); -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/Sample.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/Sample.cs deleted file mode 100644 index e2713c2b..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/Sample.cs +++ /dev/null @@ -1,158 +0,0 @@ -using AdsAnalyticsService.Domain.Events; -using AdsAnalyticsService.Domain.Exceptions; -using AdsAnalyticsService.Domain.SeedWork; - -namespace AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Sample aggregate root demonstrating DDD patterns. -/// VI: Sample aggregate root minh họa các pattern DDD. -/// -public class Sample : Entity, IAggregateRoot -{ - // EN: Private fields for encapsulation - // VI: Fields private để đóng gói - private string _name = null!; - private string? _description; - private SampleStatus _status = null!; - private DateTime _createdAt; - private DateTime? _updatedAt; - - /// - /// EN: Sample name (required). - /// VI: Tên sample (bắt buộc). - /// - public string Name => _name; - - /// - /// EN: Optional description. - /// VI: Mô tả tùy chọn. - /// - public string? Description => _description; - - /// - /// EN: Current status. - /// VI: Trạng thái hiện tại. - /// - public SampleStatus Status => _status; - - /// - /// EN: Status ID for EF Core mapping. - /// VI: ID trạng thái cho EF Core mapping. - /// - public int StatusId { get; private set; } - - /// - /// EN: Creation timestamp. - /// VI: Thời gian tạo. - /// - public DateTime CreatedAt => _createdAt; - - /// - /// EN: Last update timestamp. - /// VI: Thời gian cập nhật cuối. - /// - public DateTime? UpdatedAt => _updatedAt; - - /// - /// EN: Private constructor for EF Core. - /// VI: Constructor private cho EF Core. - /// - protected Sample() - { - } - - /// - /// EN: Create a new Sample with required information. - /// VI: Tạo một Sample mới với thông tin bắt buộc. - /// - /// EN: Sample name / VI: Tên sample - /// EN: Optional description / VI: Mô tả tùy chọn - public Sample(string name, string? description = null) : this() - { - if (string.IsNullOrWhiteSpace(name)) - throw new SampleDomainException("Sample name cannot be empty"); - - Id = Guid.NewGuid(); - _name = name; - _description = description; - _status = SampleStatus.Draft; - StatusId = SampleStatus.Draft.Id; - _createdAt = DateTime.UtcNow; - - // EN: Add domain event for creation - // VI: Thêm domain event cho việc tạo - AddDomainEvent(new SampleCreatedDomainEvent(this)); - } - - /// - /// EN: Update sample information. - /// VI: Cập nhật thông tin sample. - /// - public void Update(string name, string? description) - { - if (string.IsNullOrWhiteSpace(name)) - throw new SampleDomainException("Sample name cannot be empty"); - - if (_status == SampleStatus.Cancelled) - throw new SampleDomainException("Cannot update a cancelled sample"); - - _name = name; - _description = description; - _updatedAt = DateTime.UtcNow; - } - - /// - /// EN: Activate the sample. - /// VI: Kích hoạt sample. - /// - public void Activate() - { - if (_status != SampleStatus.Draft) - throw new SampleDomainException("Only draft samples can be activated"); - - var previousStatus = _status; - _status = SampleStatus.Active; - StatusId = SampleStatus.Active.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } - - /// - /// EN: Complete the sample. - /// VI: Hoàn thành sample. - /// - public void Complete() - { - if (_status != SampleStatus.Active) - throw new SampleDomainException("Only active samples can be completed"); - - var previousStatus = _status; - _status = SampleStatus.Completed; - StatusId = SampleStatus.Completed.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } - - /// - /// EN: Cancel the sample. - /// VI: Hủy sample. - /// - public void Cancel() - { - if (_status == SampleStatus.Completed) - throw new SampleDomainException("Cannot cancel a completed sample"); - - if (_status == SampleStatus.Cancelled) - throw new SampleDomainException("Sample is already cancelled"); - - var previousStatus = _status; - _status = SampleStatus.Cancelled; - StatusId = SampleStatus.Cancelled.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs deleted file mode 100644 index defbccd0..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs +++ /dev/null @@ -1,77 +0,0 @@ -using AdsAnalyticsService.Domain.SeedWork; - -namespace AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Sample status enumeration following type-safe enum pattern. -/// VI: Enumeration trạng thái Sample theo pattern enum an toàn kiểu. -/// -public class SampleStatus : Enumeration -{ - /// - /// EN: Draft status - initial state - /// VI: Trạng thái nháp - trạng thái ban đầu - /// - public static SampleStatus Draft = new(1, nameof(Draft)); - - /// - /// EN: Active status - ready for use - /// VI: Trạng thái hoạt động - sẵn sàng sử dụng - /// - public static SampleStatus Active = new(2, nameof(Active)); - - /// - /// EN: Completed status - finished processing - /// VI: Trạng thái hoàn thành - đã xử lý xong - /// - public static SampleStatus Completed = new(3, nameof(Completed)); - - /// - /// EN: Cancelled status - cancelled by user - /// VI: Trạng thái đã hủy - bị hủy bởi người dùng - /// - public static SampleStatus Cancelled = new(4, nameof(Cancelled)); - - public SampleStatus(int id, string name) : base(id, name) - { - } - - /// - /// EN: Get all available statuses. - /// VI: Lấy tất cả các trạng thái có sẵn. - /// - public static IEnumerable List() => GetAll(); - - /// - /// EN: Parse status from name. - /// VI: Parse trạng thái từ tên. - /// - public static SampleStatus FromName(string name) - { - var status = List().SingleOrDefault(s => - string.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase)); - - if (status is null) - { - throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}"); - } - - return status; - } - - /// - /// EN: Parse status from ID. - /// VI: Parse trạng thái từ ID. - /// - public static SampleStatus From(int id) - { - var status = List().SingleOrDefault(s => s.Id == id); - - if (status is null) - { - throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}"); - } - - return status; - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Events/SampleCreatedDomainEvent.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Events/SampleCreatedDomainEvent.cs deleted file mode 100644 index e543be73..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Events/SampleCreatedDomainEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MediatR; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.Domain.Events; - -/// -/// EN: Domain event raised when a new Sample is created. -/// VI: Domain event được phát ra khi một Sample mới được tạo. -/// -public class SampleCreatedDomainEvent : INotification -{ - /// - /// EN: The newly created sample. - /// VI: Sample mới được tạo. - /// - public Sample Sample { get; } - - public SampleCreatedDomainEvent(Sample sample) - { - Sample = sample; - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Events/SampleStatusChangedDomainEvent.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Events/SampleStatusChangedDomainEvent.cs deleted file mode 100644 index 70f70464..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Events/SampleStatusChangedDomainEvent.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.Domain.Events; - -/// -/// EN: Domain event raised when Sample status changes. -/// VI: Domain event được phát ra khi trạng thái Sample thay đổi. -/// -public class SampleStatusChangedDomainEvent : INotification -{ - /// - /// EN: The sample ID. - /// VI: ID của sample. - /// - public Guid SampleId { get; } - - /// - /// EN: Previous status before the change. - /// VI: Trạng thái trước khi thay đổi. - /// - public SampleStatus PreviousStatus { get; } - - /// - /// EN: New status after the change. - /// VI: Trạng thái mới sau khi thay đổi. - /// - public SampleStatus NewStatus { get; } - - public SampleStatusChangedDomainEvent( - Guid sampleId, - SampleStatus previousStatus, - SampleStatus newStatus) - { - SampleId = sampleId; - PreviousStatus = previousStatus; - NewStatus = newStatus; - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Exceptions/AdsAnalyticsDomainException.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Exceptions/AdsAnalyticsDomainException.cs new file mode 100644 index 00000000..fceb01df --- /dev/null +++ b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Exceptions/AdsAnalyticsDomainException.cs @@ -0,0 +1,8 @@ +namespace AdsAnalyticsService.Domain.Exceptions; + +public class AdsAnalyticsDomainException : Exception +{ + public AdsAnalyticsDomainException() { } + public AdsAnalyticsDomainException(string message) : base(message) { } + public AdsAnalyticsDomainException(string message, Exception innerException) : base(message, innerException) { } +} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Exceptions/SampleDomainException.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Exceptions/SampleDomainException.cs deleted file mode 100644 index 7536168b..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Domain/Exceptions/SampleDomainException.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace AdsAnalyticsService.Domain.Exceptions; - -/// -/// EN: Exception for Sample aggregate domain errors. -/// VI: Exception cho các lỗi domain của Sample aggregate. -/// -public class SampleDomainException : DomainException -{ - public SampleDomainException() - { - } - - public SampleDomainException(string message) : base(message) - { - } - - public SampleDomainException(string message, Exception innerException) - : base(message, innerException) - { - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/MyServiceContext.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/AdsAnalyticsServiceContext.cs similarity index 100% rename from services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/MyServiceContext.cs rename to services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/AdsAnalyticsServiceContext.cs diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/DependencyInjection.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/DependencyInjection.cs index 3d6f69e9..669242df 100644 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/DependencyInjection.cs +++ b/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/DependencyInjection.cs @@ -1,27 +1,16 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; using AdsAnalyticsService.Infrastructure.Idempotency; -using AdsAnalyticsService.Infrastructure.Repositories; namespace AdsAnalyticsService.Infrastructure; -/// -/// EN: Dependency injection extensions for Infrastructure layer. -/// VI: Extensions dependency injection cho lớp Infrastructure. -/// public static class DependencyInjection { - /// - /// EN: Add infrastructure services to the DI container. - /// VI: Thêm các services infrastructure vào DI container. - /// public static IServiceCollection AddInfrastructure( this IServiceCollection services, IConfiguration configuration) { - // EN: Add DbContext with PostgreSQL / VI: Thêm DbContext với PostgreSQL services.AddDbContext(options => { var connectionString = configuration.GetConnectionString("DefaultConnection") @@ -31,14 +20,9 @@ public static class DependencyInjection options.UseNpgsql(connectionString, npgsqlOptions => { npgsqlOptions.MigrationsAssembly(typeof(AdsAnalyticsServiceContext).Assembly.FullName); - npgsqlOptions.EnableRetryOnFailure( - maxRetryCount: 5, - maxRetryDelay: TimeSpan.FromSeconds(30), - errorCodesToAdd: null); + npgsqlOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null); }); - // EN: Enable sensitive data logging in development only - // VI: Chỉ bật sensitive data logging trong development if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") { options.EnableSensitiveDataLogging(); @@ -46,12 +30,7 @@ public static class DependencyInjection } }); - // EN: Register repositories / VI: Đăng ký repositories - services.AddScoped(); - - // EN: Register idempotency services / VI: Đăng ký idempotency services services.AddScoped(); - return services; } } diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs deleted file mode 100644 index 2cb0457e..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.Infrastructure.EntityConfigurations; - -/// -/// EN: EF Core configuration for Sample entity. -/// VI: Cấu hình EF Core cho entity Sample. -/// -public class SampleEntityTypeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - // EN: Table name / VI: Tên bảng - builder.ToTable("samples"); - - // EN: Primary key / VI: Khóa chính - builder.HasKey(s => s.Id); - - // EN: Ignore domain events (not persisted) - // VI: Bỏ qua domain events (không lưu) - builder.Ignore(s => s.DomainEvents); - - // EN: Properties / VI: Các thuộc tính - builder.Property(s => s.Id) - .HasColumnName("id") - .IsRequired(); - - builder.Property("_name") - .HasColumnName("name") - .HasMaxLength(200) - .IsRequired(); - - builder.Property("_description") - .HasColumnName("description") - .HasMaxLength(1000); - - builder.Property("_createdAt") - .HasColumnName("created_at") - .IsRequired(); - - builder.Property("_updatedAt") - .HasColumnName("updated_at"); - - // EN: Status relationship / VI: Quan hệ với Status - builder.Property(s => s.StatusId) - .HasColumnName("status_id") - .IsRequired(); - - builder.HasOne(s => s.Status) - .WithMany() - .HasForeignKey(s => s.StatusId) - .OnDelete(DeleteBehavior.Restrict); - - // EN: Indexes / VI: Các index - builder.HasIndex("_name"); - builder.HasIndex(s => s.StatusId); - builder.HasIndex("_createdAt"); - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs deleted file mode 100644 index 7d3654b2..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsAnalyticsService.Infrastructure.EntityConfigurations; - -/// -/// EN: EF Core configuration for SampleStatus enumeration. -/// VI: Cấu hình EF Core cho enumeration SampleStatus. -/// -public class SampleStatusEntityTypeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - // EN: Table name / VI: Tên bảng - builder.ToTable("sample_statuses"); - - // EN: Primary key / VI: Khóa chính - builder.HasKey(s => s.Id); - - builder.Property(s => s.Id) - .HasColumnName("id") - .ValueGeneratedNever() - .IsRequired(); - - builder.Property(s => s.Name) - .HasColumnName("name") - .HasMaxLength(50) - .IsRequired(); - - // EN: Seed initial data / VI: Seed dữ liệu ban đầu - builder.HasData( - SampleStatus.Draft, - SampleStatus.Active, - SampleStatus.Completed, - SampleStatus.Cancelled - ); - } -} diff --git a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/Repositories/SampleRepository.cs b/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/Repositories/SampleRepository.cs deleted file mode 100644 index 8b3dfc29..00000000 --- a/services/ads-analytics-service-net/src/AdsAnalyticsService.Infrastructure/Repositories/SampleRepository.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; -using AdsAnalyticsService.Domain.SeedWork; - -namespace AdsAnalyticsService.Infrastructure.Repositories; - -/// -/// EN: Repository implementation for Sample aggregate. -/// VI: Triển khai repository cho Sample aggregate. -/// -public class SampleRepository : ISampleRepository -{ - private readonly AdsAnalyticsServiceContext _context; - - /// - /// EN: Unit of work for transaction management. - /// VI: Unit of work cho quản lý transaction. - /// - public IUnitOfWork UnitOfWork => _context; - - public SampleRepository(AdsAnalyticsServiceContext context) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - } - - /// - public async Task GetAsync(Guid sampleId) - { - var sample = await _context.Samples - .Include(s => s.Status) - .FirstOrDefaultAsync(s => s.Id == sampleId); - - return sample; - } - - /// - public async Task> GetAllAsync() - { - return await _context.Samples - .Include(s => s.Status) - .OrderByDescending(s => s.CreatedAt) - .ToListAsync(); - } - - /// - public Sample Add(Sample sample) - { - return _context.Samples.Add(sample).Entity; - } - - /// - public void Update(Sample sample) - { - _context.Entry(sample).State = EntityState.Modified; - } - - /// - public void Delete(Sample sample) - { - _context.Samples.Remove(sample); - } - - /// - public async Task> GetByStatusAsync(int statusId) - { - return await _context.Samples - .Include(s => s.Status) - .Where(s => s.StatusId == statusId) - .OrderByDescending(s => s.CreatedAt) - .ToListAsync(); - } -} diff --git a/services/ads-analytics-service-net/tests/AdsAnalyticsService.UnitTests/Application/CreateSampleCommandHandlerTests.cs b/services/ads-analytics-service-net/tests/AdsAnalyticsService.UnitTests/Application/CreateSampleCommandHandlerTests.cs deleted file mode 100644 index 1d9b304c..00000000 --- a/services/ads-analytics-service-net/tests/AdsAnalyticsService.UnitTests/Application/CreateSampleCommandHandlerTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging; -using Moq; -using AdsAnalyticsService.API.Application.Commands; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; -using AdsAnalyticsService.Domain.SeedWork; -using Xunit; - -namespace AdsAnalyticsService.UnitTests.Application; - -/// -/// EN: Unit tests for CreateSampleCommandHandler. -/// VI: Unit tests cho CreateSampleCommandHandler. -/// -public class CreateSampleCommandHandlerTests -{ - private readonly Mock _mockRepository; - private readonly Mock> _mockLogger; - private readonly CreateSampleCommandHandler _handler; - - public CreateSampleCommandHandlerTests() - { - _mockRepository = new Mock(); - _mockLogger = new Mock>(); - - var mockUnitOfWork = new Mock(); - mockUnitOfWork.Setup(u => u.SaveEntitiesAsync(It.IsAny())) - .ReturnsAsync(true); - - _mockRepository.SetupGet(r => r.UnitOfWork).Returns(mockUnitOfWork.Object); - - _handler = new CreateSampleCommandHandler(_mockRepository.Object, _mockLogger.Object); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCreateSampleAndReturnId() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", "Test Description"); - - _mockRepository.Setup(r => r.Add(It.IsAny())) - .Returns((Sample s) => s); - - // Act - var result = await _handler.Handle(command, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result.Id.Should().NotBeEmpty(); - _mockRepository.Verify(r => r.Add(It.IsAny()), Times.Once); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCallSaveEntities() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", null); - - // Act - await _handler.Handle(command, CancellationToken.None); - - // Assert - _mockRepository.Verify(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny()), Times.Once); - } -} diff --git a/services/ads-analytics-service-net/tests/AdsAnalyticsService.UnitTests/Domain/SampleAggregateTests.cs b/services/ads-analytics-service-net/tests/AdsAnalyticsService.UnitTests/Domain/SampleAggregateTests.cs deleted file mode 100644 index 7c5508d8..00000000 --- a/services/ads-analytics-service-net/tests/AdsAnalyticsService.UnitTests/Domain/SampleAggregateTests.cs +++ /dev/null @@ -1,151 +0,0 @@ -using FluentAssertions; -using AdsAnalyticsService.Domain.AggregatesModel.SampleAggregate; -using AdsAnalyticsService.Domain.Exceptions; -using Xunit; - -namespace AdsAnalyticsService.UnitTests.Domain; - -/// -/// EN: Unit tests for Sample aggregate. -/// VI: Unit tests cho Sample aggregate. -/// -public class SampleAggregateTests -{ - [Fact] - public void CreateSample_WithValidName_ShouldCreateWithDraftStatus() - { - // Arrange - var name = "Test Sample"; - var description = "Test Description"; - - // Act - var sample = new Sample(name, description); - - // Assert - sample.Name.Should().Be(name); - sample.Description.Should().Be(description); - sample.Status.Should().Be(SampleStatus.Draft); - sample.Id.Should().NotBeEmpty(); - sample.DomainEvents.Should().ContainSingle(); // SampleCreatedDomainEvent - } - - [Fact] - public void CreateSample_WithEmptyName_ShouldThrowException() - { - // Arrange - var name = ""; - - // Act - var act = () => new Sample(name); - - // Assert - act.Should().Throw() - .WithMessage("Sample name cannot be empty"); - } - - [Fact] - public void Activate_WhenDraft_ShouldChangeToActive() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.ClearDomainEvents(); - - // Act - sample.Activate(); - - // Assert - sample.Status.Should().Be(SampleStatus.Active); - sample.DomainEvents.Should().ContainSingle(); // SampleStatusChangedDomainEvent - } - - [Fact] - public void Activate_WhenNotDraft_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - - // Act - var act = () => sample.Activate(); - - // Assert - act.Should().Throw() - .WithMessage("Only draft samples can be activated"); - } - - [Fact] - public void Complete_WhenActive_ShouldChangeToCompleted() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - sample.ClearDomainEvents(); - - // Act - sample.Complete(); - - // Assert - sample.Status.Should().Be(SampleStatus.Completed); - } - - [Fact] - public void Cancel_WhenDraftOrActive_ShouldChangeToCancelled() - { - // Arrange - var sample = new Sample("Test Sample"); - - // Act - sample.Cancel(); - - // Assert - sample.Status.Should().Be(SampleStatus.Cancelled); - } - - [Fact] - public void Cancel_WhenCompleted_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - sample.Complete(); - - // Act - var act = () => sample.Cancel(); - - // Assert - act.Should().Throw() - .WithMessage("Cannot cancel a completed sample"); - } - - [Fact] - public void Update_WhenNotCancelled_ShouldUpdateNameAndDescription() - { - // Arrange - var sample = new Sample("Original Name", "Original Description"); - var newName = "Updated Name"; - var newDescription = "Updated Description"; - - // Act - sample.Update(newName, newDescription); - - // Assert - sample.Name.Should().Be(newName); - sample.Description.Should().Be(newDescription); - sample.UpdatedAt.Should().NotBeNull(); - } - - [Fact] - public void Update_WhenCancelled_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Cancel(); - - // Act - var act = () => sample.Update("New Name", null); - - // Assert - act.Should().Throw() - .WithMessage("Cannot update a cancelled sample"); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/ChangeSampleStatusCommand.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/ChangeSampleStatusCommand.cs deleted file mode 100644 index fbc0375c..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/ChangeSampleStatusCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MediatR; - -namespace AdsTrackingService.API.Application.Commands; - -/// -/// EN: Command to change status of a Sample. -/// VI: Command để thay đổi trạng thái của Sample. -/// -/// EN: Sample ID / VI: ID sample -/// EN: New status (activate, complete, cancel) / VI: Trạng thái mới (activate, complete, cancel) -public record ChangeSampleStatusCommand( - Guid SampleId, - string NewStatus -) : IRequest; diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs deleted file mode 100644 index 84578ef5..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MediatR; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.API.Application.Commands; - -/// -/// EN: Handler for ChangeSampleStatusCommand. -/// VI: Handler cho ChangeSampleStatusCommand. -/// -public class ChangeSampleStatusCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public ChangeSampleStatusCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - ChangeSampleStatusCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Changing status of sample {SampleId} to {NewStatus} / Thay đổi trạng thái sample {SampleId} thành {NewStatus}", - request.SampleId, request.NewStatus); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Change status based on action / VI: Thay đổi trạng thái dựa trên action - switch (request.NewStatus.ToLowerInvariant()) - { - case "activate": - sample.Activate(); - break; - case "complete": - sample.Complete(); - break; - case "cancel": - sample.Cancel(); - break; - default: - _logger.LogWarning( - "Invalid status action: {NewStatus} / Action trạng thái không hợp lệ: {NewStatus}", - request.NewStatus); - return false; - } - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} status changed to {NewStatus} / Trạng thái sample {SampleId} đã đổi thành {NewStatus}", - request.SampleId, request.NewStatus); - - return true; - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/CreateSampleCommand.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/CreateSampleCommand.cs deleted file mode 100644 index 1826eedd..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/CreateSampleCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MediatR; - -namespace AdsTrackingService.API.Application.Commands; - -/// -/// EN: Command to create a new Sample. -/// VI: Command để tạo một Sample mới. -/// -/// EN: Sample name / VI: Tên sample -/// EN: Optional description / VI: Mô tả tùy chọn -public record CreateSampleCommand( - string Name, - string? Description -) : IRequest; - -/// -/// EN: Result of CreateSampleCommand. -/// VI: Kết quả của CreateSampleCommand. -/// -/// EN: Created sample ID / VI: ID sample đã tạo -public record CreateSampleCommandResult(Guid Id); diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/CreateSampleCommandHandler.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/CreateSampleCommandHandler.cs deleted file mode 100644 index 2638f4c9..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/CreateSampleCommandHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediatR; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.API.Application.Commands; - -/// -/// EN: Handler for CreateSampleCommand. -/// VI: Handler cho CreateSampleCommand. -/// -public class CreateSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public CreateSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - CreateSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Creating new sample with name: {Name} / Tạo sample mới với tên: {Name}", - request.Name); - - // EN: Create domain entity / VI: Tạo domain entity - var sample = new Sample(request.Name, request.Description); - - // EN: Add to repository / VI: Thêm vào repository - _sampleRepository.Add(sample); - - // EN: Save changes (dispatches domain events) / VI: Lưu thay đổi (dispatch domain events) - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample created successfully with ID: {SampleId} / Sample đã tạo thành công với ID: {SampleId}", - sample.Id); - - return new CreateSampleCommandResult(sample.Id); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/DeleteSampleCommand.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/DeleteSampleCommand.cs deleted file mode 100644 index 604b8915..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/DeleteSampleCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MediatR; - -namespace AdsTrackingService.API.Application.Commands; - -/// -/// EN: Command to delete a Sample. -/// VI: Command để xóa một Sample. -/// -/// EN: Sample ID to delete / VI: ID sample cần xóa -public record DeleteSampleCommand(Guid SampleId) : IRequest; diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/DeleteSampleCommandHandler.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/DeleteSampleCommandHandler.cs deleted file mode 100644 index 5ecc8b40..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/DeleteSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.API.Application.Commands; - -/// -/// EN: Handler for DeleteSampleCommand. -/// VI: Handler cho DeleteSampleCommand. -/// -public class DeleteSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public DeleteSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - DeleteSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Deleting sample {SampleId} / Xóa sample {SampleId}", - request.SampleId); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Delete sample / VI: Xóa sample - _sampleRepository.Delete(sample); - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} deleted successfully / Sample {SampleId} đã xóa thành công", - request.SampleId); - - return true; - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/UpdateSampleCommand.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/UpdateSampleCommand.cs deleted file mode 100644 index 02268ec4..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/UpdateSampleCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; - -namespace AdsTrackingService.API.Application.Commands; - -/// -/// EN: Command to update an existing Sample. -/// VI: Command để cập nhật một Sample đã tồn tại. -/// -/// EN: Sample ID to update / VI: ID sample cần cập nhật -/// EN: New name / VI: Tên mới -/// EN: New description / VI: Mô tả mới -public record UpdateSampleCommand( - Guid SampleId, - string Name, - string? Description -) : IRequest; diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/UpdateSampleCommandHandler.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/UpdateSampleCommandHandler.cs deleted file mode 100644 index a58f5b98..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Commands/UpdateSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.API.Application.Commands; - -/// -/// EN: Handler for UpdateSampleCommand. -/// VI: Handler cho UpdateSampleCommand. -/// -public class UpdateSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public UpdateSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - UpdateSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Updating sample {SampleId} / Cập nhật sample {SampleId}", - request.SampleId); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Update sample using domain method / VI: Cập nhật sample sử dụng domain method - sample.Update(request.Name, request.Description); - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} updated successfully / Sample {SampleId} đã cập nhật thành công", - request.SampleId); - - return true; - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSampleQuery.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSampleQuery.cs deleted file mode 100644 index 192600d1..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSampleQuery.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediatR; - -namespace AdsTrackingService.API.Application.Queries; - -/// -/// EN: Query to get a Sample by ID. -/// VI: Query để lấy một Sample theo ID. -/// -/// EN: Sample ID / VI: ID sample -public record GetSampleQuery(Guid SampleId) : IRequest; - -/// -/// EN: Sample view model for API responses. -/// VI: Sample view model cho API responses. -/// -public record SampleViewModel( - Guid Id, - string Name, - string? Description, - string Status, - DateTime CreatedAt, - DateTime? UpdatedAt -); diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSampleQueryHandler.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSampleQueryHandler.cs deleted file mode 100644 index 68a5f0e3..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSampleQueryHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.API.Application.Queries; - -/// -/// EN: Handler for GetSampleQuery. -/// VI: Handler cho GetSampleQuery. -/// -public class GetSampleQueryHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - - public GetSampleQueryHandler(ISampleRepository sampleRepository) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - } - - public async Task Handle( - GetSampleQuery request, - CancellationToken cancellationToken) - { - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - return null; - } - - return new SampleViewModel( - sample.Id, - sample.Name, - sample.Description, - sample.Status.Name, - sample.CreatedAt, - sample.UpdatedAt - ); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSamplesQuery.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSamplesQuery.cs deleted file mode 100644 index e5fc8e8b..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSamplesQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MediatR; - -namespace AdsTrackingService.API.Application.Queries; - -/// -/// EN: Query to get all Samples. -/// VI: Query để lấy tất cả Samples. -/// -public record GetSamplesQuery : IRequest>; diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSamplesQueryHandler.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSamplesQueryHandler.cs deleted file mode 100644 index 9113dc44..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Queries/GetSamplesQueryHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MediatR; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.API.Application.Queries; - -/// -/// EN: Handler for GetSamplesQuery. -/// VI: Handler cho GetSamplesQuery. -/// -public class GetSamplesQueryHandler : IRequestHandler> -{ - private readonly ISampleRepository _sampleRepository; - - public GetSamplesQueryHandler(ISampleRepository sampleRepository) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - } - - public async Task> Handle( - GetSamplesQuery request, - CancellationToken cancellationToken) - { - var samples = await _sampleRepository.GetAllAsync(); - - return samples.Select(sample => new SampleViewModel( - sample.Id, - sample.Name, - sample.Description, - sample.Status.Name, - sample.CreatedAt, - sample.UpdatedAt - )); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/CreateSampleCommandValidator.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/CreateSampleCommandValidator.cs deleted file mode 100644 index a5d89a5d..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/CreateSampleCommandValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FluentValidation; -using AdsTrackingService.API.Application.Commands; - -namespace AdsTrackingService.API.Application.Validations; - -/// -/// EN: Validator for CreateSampleCommand. -/// VI: Validator cho CreateSampleCommand. -/// -public class CreateSampleCommandValidator : AbstractValidator -{ - public CreateSampleCommandValidator() - { - RuleFor(x => x.Name) - .NotEmpty() - .WithMessage("Name is required / Tên là bắt buộc") - .MaximumLength(200) - .WithMessage("Name must be less than 200 characters / Tên phải ít hơn 200 ký tự"); - - RuleFor(x => x.Description) - .MaximumLength(1000) - .WithMessage("Description must be less than 1000 characters / Mô tả phải ít hơn 1000 ký tự") - .When(x => x.Description != null); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/UpdateSampleCommandValidator.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/UpdateSampleCommandValidator.cs deleted file mode 100644 index c6fc4380..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Application/Validations/UpdateSampleCommandValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using AdsTrackingService.API.Application.Commands; - -namespace AdsTrackingService.API.Application.Validations; - -/// -/// EN: Validator for UpdateSampleCommand. -/// VI: Validator cho UpdateSampleCommand. -/// -public class UpdateSampleCommandValidator : AbstractValidator -{ - public UpdateSampleCommandValidator() - { - RuleFor(x => x.SampleId) - .NotEmpty() - .WithMessage("Sample ID is required / ID sample là bắt buộc"); - - RuleFor(x => x.Name) - .NotEmpty() - .WithMessage("Name is required / Tên là bắt buộc") - .MaximumLength(200) - .WithMessage("Name must be less than 200 characters / Tên phải ít hơn 200 ký tự"); - - RuleFor(x => x.Description) - .MaximumLength(1000) - .WithMessage("Description must be less than 1000 characters / Mô tả phải ít hơn 1000 ký tự") - .When(x => x.Description != null); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/SamplesController.cs b/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/SamplesController.cs deleted file mode 100644 index 032734df..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.API/Controllers/SamplesController.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Asp.Versioning; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using AdsTrackingService.API.Application.Commands; -using AdsTrackingService.API.Application.Queries; - -namespace AdsTrackingService.API.Controllers; - -/// -/// EN: Controller for Sample CRUD operations using CQRS pattern. -/// VI: Controller cho các thao tác CRUD Sample sử dụng pattern CQRS. -/// -[ApiController] -[ApiVersion("1.0")] -[Route("api/v{version:apiVersion}/[controller]")] -[Produces("application/json")] -public class SamplesController : ControllerBase -{ - private readonly IMediator _mediator; - private readonly ILogger _logger; - - public SamplesController(IMediator mediator, ILogger logger) - { - _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - /// EN: Get all samples. - /// VI: Lấy tất cả samples. - /// - /// EN: List of samples / VI: Danh sách samples - [HttpGet] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public async Task GetSamples() - { - var samples = await _mediator.Send(new GetSamplesQuery()); - return Ok(new { success = true, data = samples }); - } - - /// - /// EN: Get a sample by ID. - /// VI: Lấy một sample theo ID. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Sample details / VI: Chi tiết sample - [HttpGet("{id:guid}")] - [ProducesResponseType(typeof(SampleViewModel), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetSample(Guid id) - { - var sample = await _mediator.Send(new GetSampleQuery(id)); - - if (sample is null) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return Ok(new { success = true, data = sample }); - } - - /// - /// EN: Create a new sample. - /// VI: Tạo một sample mới. - /// - /// EN: Create request / VI: Request tạo - /// EN: Created sample ID / VI: ID sample đã tạo - [HttpPost] - [ProducesResponseType(typeof(CreateSampleCommandResult), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task CreateSample([FromBody] CreateSampleRequest request) - { - var command = new CreateSampleCommand(request.Name, request.Description); - var result = await _mediator.Send(command); - - return CreatedAtAction( - nameof(GetSample), - new { id = result.Id }, - new { success = true, data = result }); - } - - /// - /// EN: Update an existing sample. - /// VI: Cập nhật một sample đã tồn tại. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Update request / VI: Request cập nhật - /// EN: Success status / VI: Trạng thái thành công - [HttpPut("{id:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task UpdateSample(Guid id, [FromBody] UpdateSampleRequest request) - { - var command = new UpdateSampleCommand(id, request.Name, request.Description); - var result = await _mediator.Send(command); - - if (!result) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return Ok(new { success = true, message = "Sample updated successfully / Sample đã cập nhật thành công" }); - } - - /// - /// EN: Delete a sample. - /// VI: Xóa một sample. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Success status / VI: Trạng thái thành công - [HttpDelete("{id:guid}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteSample(Guid id) - { - var command = new DeleteSampleCommand(id); - var result = await _mediator.Send(command); - - if (!result) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return NoContent(); - } - - /// - /// EN: Change sample status. - /// VI: Thay đổi trạng thái sample. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Status change request / VI: Request thay đổi trạng thái - /// EN: Success status / VI: Trạng thái thành công - [HttpPatch("{id:guid}/status")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task ChangeSampleStatus(Guid id, [FromBody] ChangeStatusRequest request) - { - var command = new ChangeSampleStatusCommand(id, request.Status); - var result = await _mediator.Send(command); - - if (!result) - { - return BadRequest(new - { - success = false, - error = new - { - code = "STATUS_CHANGE_FAILED", - message = "Failed to change sample status / Thay đổi trạng thái sample thất bại" - } - }); - } - - return Ok(new { success = true, message = "Sample status changed successfully / Trạng thái sample đã thay đổi thành công" }); - } -} - -/// -/// EN: Request model for creating a sample. -/// VI: Model request để tạo sample. -/// -public record CreateSampleRequest(string Name, string? Description); - -/// -/// EN: Request model for updating a sample. -/// VI: Model request để cập nhật sample. -/// -public record UpdateSampleRequest(string Name, string? Description); - -/// -/// EN: Request model for changing sample status. -/// VI: Model request để thay đổi trạng thái sample. -/// -public record ChangeStatusRequest(string Status); diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/AttributionAggregate/Attribution.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/AttributionAggregate/Attribution.cs new file mode 100644 index 00000000..46247538 --- /dev/null +++ b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/AttributionAggregate/Attribution.cs @@ -0,0 +1,49 @@ +using AdsTrackingService.Domain.SeedWork; + +namespace AdsTrackingService.Domain.AggregatesModel.AttributionAggregate; + +/// +/// EN: Attribution aggregate root - links conversions to ads. +/// VI: Attribution aggregate root - liên kết conversion với quảng cáo. +/// +public class Attribution : Entity, IAggregateRoot +{ + private Guid _conversionId; + private Guid _adId; + private Guid _campaignId; + private AttributionModel _model; + private decimal _attributedValue; + private DateTime _attributedAt; + + public Guid ConversionId => _conversionId; + public Guid AdId => _adId; + public Guid CampaignId => _campaignId; + public AttributionModel Model => _model; + public decimal AttributedValue => _attributedValue; + public DateTime AttributedAt => _attributedAt; + + protected Attribution() { } + + public Attribution(Guid conversionId, Guid adId, Guid campaignId, AttributionModel model, decimal attributedValue) + { + Id = Guid.NewGuid(); + _conversionId = conversionId; + _adId = adId; + _campaignId = campaignId; + _model = model; + _attributedValue = attributedValue; + _attributedAt = DateTime.UtcNow; + } +} + +/// +/// EN: Attribution model enumeration. +/// VI: Enum mô hình attribution. +/// +public enum AttributionModel +{ + LastClick = 1, + FirstClick = 2, + Linear = 3, + TimeDecay = 4 +} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/ConversionAggregate/Conversion.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/ConversionAggregate/Conversion.cs new file mode 100644 index 00000000..ece2308b --- /dev/null +++ b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/ConversionAggregate/Conversion.cs @@ -0,0 +1,49 @@ +using AdsTrackingService.Domain.SeedWork; + +namespace AdsTrackingService.Domain.AggregatesModel.ConversionAggregate; + +/// +/// EN: Conversion aggregate root - tracks conversion events. +/// VI: Conversion aggregate root - theo dõi sự kiện conversion. +/// +public class Conversion : Entity, IAggregateRoot +{ + private Guid _advertiserId; + private Guid _campaignId; + private Guid _userId; + private string _conversionType = null!; + private decimal _conversionValue; + private string _currency; + private DateTime _conversionTime; + + public Guid AdvertiserId => _advertiserId; + public Guid CampaignId => _campaignId; + public Guid UserId => _userId; + public string ConversionType => _conversionType; + public decimal ConversionValue => _conversionValue; + public string Currency => _currency; + public DateTime ConversionTime => _conversionTime; + + protected Conversion() + { + _currency = "VND"; + } + + public Conversion(Guid advertiserId, Guid campaignId, Guid userId, string conversionType, decimal conversionValue, string currency = "VND") + { + Id = Guid.NewGuid(); + _advertiserId = advertiserId; + _campaignId = campaignId; + _userId = userId; + _conversionType = conversionType; + _conversionValue = conversionValue; + _currency = currency; + _conversionTime = DateTime.UtcNow; + } + + public static Conversion Purchase(Guid advertiserId, Guid campaignId, Guid userId, decimal amount) => + new(advertiserId, campaignId, userId, "purchase", amount); + + public static Conversion Lead(Guid advertiserId, Guid campaignId, Guid userId) => + new(advertiserId, campaignId, userId, "lead", 0); +} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs deleted file mode 100644 index 106654bb..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs +++ /dev/null @@ -1,61 +0,0 @@ -using AdsTrackingService.Domain.SeedWork; - -namespace AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Repository interface for Sample aggregate. -/// VI: Interface repository cho Sample aggregate. -/// -/// -/// EN: Following repository pattern, this interface defines the contract -/// for data access operations on Sample aggregate. -/// VI: Theo pattern repository, interface này định nghĩa contract -/// cho các thao tác truy cập dữ liệu trên Sample aggregate. -/// -public interface ISampleRepository : IRepository -{ - /// - /// EN: Get a sample by its ID. - /// VI: Lấy một sample theo ID. - /// - /// EN: The sample ID / VI: ID của sample - /// EN: The sample or null if not found / VI: Sample hoặc null nếu không tìm thấy - Task GetAsync(Guid sampleId); - - /// - /// EN: Get all samples. - /// VI: Lấy tất cả samples. - /// - /// EN: List of samples / VI: Danh sách samples - Task> GetAllAsync(); - - /// - /// EN: Add a new sample. - /// VI: Thêm một sample mới. - /// - /// EN: The sample to add / VI: Sample cần thêm - /// EN: The added sample / VI: Sample đã thêm - Sample Add(Sample sample); - - /// - /// EN: Update an existing sample. - /// VI: Cập nhật một sample đã tồn tại. - /// - /// EN: The sample to update / VI: Sample cần cập nhật - void Update(Sample sample); - - /// - /// EN: Delete a sample. - /// VI: Xóa một sample. - /// - /// EN: The sample to delete / VI: Sample cần xóa - void Delete(Sample sample); - - /// - /// EN: Get samples by status. - /// VI: Lấy samples theo trạng thái. - /// - /// EN: The status ID / VI: ID trạng thái - /// EN: List of samples with given status / VI: Danh sách samples với trạng thái cho trước - Task> GetByStatusAsync(int statusId); -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/Sample.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/Sample.cs deleted file mode 100644 index fe8efbf8..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/Sample.cs +++ /dev/null @@ -1,158 +0,0 @@ -using AdsTrackingService.Domain.Events; -using AdsTrackingService.Domain.Exceptions; -using AdsTrackingService.Domain.SeedWork; - -namespace AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Sample aggregate root demonstrating DDD patterns. -/// VI: Sample aggregate root minh họa các pattern DDD. -/// -public class Sample : Entity, IAggregateRoot -{ - // EN: Private fields for encapsulation - // VI: Fields private để đóng gói - private string _name = null!; - private string? _description; - private SampleStatus _status = null!; - private DateTime _createdAt; - private DateTime? _updatedAt; - - /// - /// EN: Sample name (required). - /// VI: Tên sample (bắt buộc). - /// - public string Name => _name; - - /// - /// EN: Optional description. - /// VI: Mô tả tùy chọn. - /// - public string? Description => _description; - - /// - /// EN: Current status. - /// VI: Trạng thái hiện tại. - /// - public SampleStatus Status => _status; - - /// - /// EN: Status ID for EF Core mapping. - /// VI: ID trạng thái cho EF Core mapping. - /// - public int StatusId { get; private set; } - - /// - /// EN: Creation timestamp. - /// VI: Thời gian tạo. - /// - public DateTime CreatedAt => _createdAt; - - /// - /// EN: Last update timestamp. - /// VI: Thời gian cập nhật cuối. - /// - public DateTime? UpdatedAt => _updatedAt; - - /// - /// EN: Private constructor for EF Core. - /// VI: Constructor private cho EF Core. - /// - protected Sample() - { - } - - /// - /// EN: Create a new Sample with required information. - /// VI: Tạo một Sample mới với thông tin bắt buộc. - /// - /// EN: Sample name / VI: Tên sample - /// EN: Optional description / VI: Mô tả tùy chọn - public Sample(string name, string? description = null) : this() - { - if (string.IsNullOrWhiteSpace(name)) - throw new SampleDomainException("Sample name cannot be empty"); - - Id = Guid.NewGuid(); - _name = name; - _description = description; - _status = SampleStatus.Draft; - StatusId = SampleStatus.Draft.Id; - _createdAt = DateTime.UtcNow; - - // EN: Add domain event for creation - // VI: Thêm domain event cho việc tạo - AddDomainEvent(new SampleCreatedDomainEvent(this)); - } - - /// - /// EN: Update sample information. - /// VI: Cập nhật thông tin sample. - /// - public void Update(string name, string? description) - { - if (string.IsNullOrWhiteSpace(name)) - throw new SampleDomainException("Sample name cannot be empty"); - - if (_status == SampleStatus.Cancelled) - throw new SampleDomainException("Cannot update a cancelled sample"); - - _name = name; - _description = description; - _updatedAt = DateTime.UtcNow; - } - - /// - /// EN: Activate the sample. - /// VI: Kích hoạt sample. - /// - public void Activate() - { - if (_status != SampleStatus.Draft) - throw new SampleDomainException("Only draft samples can be activated"); - - var previousStatus = _status; - _status = SampleStatus.Active; - StatusId = SampleStatus.Active.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } - - /// - /// EN: Complete the sample. - /// VI: Hoàn thành sample. - /// - public void Complete() - { - if (_status != SampleStatus.Active) - throw new SampleDomainException("Only active samples can be completed"); - - var previousStatus = _status; - _status = SampleStatus.Completed; - StatusId = SampleStatus.Completed.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } - - /// - /// EN: Cancel the sample. - /// VI: Hủy sample. - /// - public void Cancel() - { - if (_status == SampleStatus.Completed) - throw new SampleDomainException("Cannot cancel a completed sample"); - - if (_status == SampleStatus.Cancelled) - throw new SampleDomainException("Sample is already cancelled"); - - var previousStatus = _status; - _status = SampleStatus.Cancelled; - StatusId = SampleStatus.Cancelled.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs deleted file mode 100644 index b2aea566..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs +++ /dev/null @@ -1,77 +0,0 @@ -using AdsTrackingService.Domain.SeedWork; - -namespace AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Sample status enumeration following type-safe enum pattern. -/// VI: Enumeration trạng thái Sample theo pattern enum an toàn kiểu. -/// -public class SampleStatus : Enumeration -{ - /// - /// EN: Draft status - initial state - /// VI: Trạng thái nháp - trạng thái ban đầu - /// - public static SampleStatus Draft = new(1, nameof(Draft)); - - /// - /// EN: Active status - ready for use - /// VI: Trạng thái hoạt động - sẵn sàng sử dụng - /// - public static SampleStatus Active = new(2, nameof(Active)); - - /// - /// EN: Completed status - finished processing - /// VI: Trạng thái hoàn thành - đã xử lý xong - /// - public static SampleStatus Completed = new(3, nameof(Completed)); - - /// - /// EN: Cancelled status - cancelled by user - /// VI: Trạng thái đã hủy - bị hủy bởi người dùng - /// - public static SampleStatus Cancelled = new(4, nameof(Cancelled)); - - public SampleStatus(int id, string name) : base(id, name) - { - } - - /// - /// EN: Get all available statuses. - /// VI: Lấy tất cả các trạng thái có sẵn. - /// - public static IEnumerable List() => GetAll(); - - /// - /// EN: Parse status from name. - /// VI: Parse trạng thái từ tên. - /// - public static SampleStatus FromName(string name) - { - var status = List().SingleOrDefault(s => - string.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase)); - - if (status is null) - { - throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}"); - } - - return status; - } - - /// - /// EN: Parse status from ID. - /// VI: Parse trạng thái từ ID. - /// - public static SampleStatus From(int id) - { - var status = List().SingleOrDefault(s => s.Id == id); - - if (status is null) - { - throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}"); - } - - return status; - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/TrackingPixelAggregate/TrackingPixel.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/TrackingPixelAggregate/TrackingPixel.cs new file mode 100644 index 00000000..d4063582 --- /dev/null +++ b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/AggregatesModel/TrackingPixelAggregate/TrackingPixel.cs @@ -0,0 +1,79 @@ +using AdsTrackingService.Domain.SeedWork; + +namespace AdsTrackingService.Domain.AggregatesModel.TrackingPixelAggregate; + +/// +/// EN: Tracking pixel aggregate root - tracks ad impressions and clicks. +/// VI: Tracking pixel aggregate root - theo dõi impression và click quảng cáo. +/// +public class TrackingPixel : Entity, IAggregateRoot +{ + private Guid _advertiserId; + private string _pixelCode = null!; + private bool _isActive; + + public Guid AdvertiserId => _advertiserId; + public string PixelCode => _pixelCode; + public bool IsActive => _isActive; + public DateTime CreatedAt { get; private set; } + + protected TrackingPixel() { } + + public TrackingPixel(Guid advertiserId) + { + Id = Guid.NewGuid(); + _advertiserId = advertiserId; + _pixelCode = GeneratePixelCode(); + _isActive = true; + CreatedAt = DateTime.UtcNow; + } + + public void Deactivate() => _isActive = false; + public void Activate() => _isActive = true; + + private static string GeneratePixelCode() => Guid.NewGuid().ToString("N")[..16].ToUpper(); +} + +/// +/// EN: Pixel event - individual tracking event (impression, click, pageview). +/// VI: Pixel event - sự kiện tracking riêng lẻ. +/// +public class PixelEvent : Entity +{ + private Guid _pixelId; + private Guid _adId; + private Guid _userId; + private PixelEventType _eventType; + private string? _userAgent; + private string? _ipAddress; + private DateTime _timestamp; + + public Guid PixelId => _pixelId; + public Guid AdId => _adId; + public Guid UserId => _userId; + public PixelEventType EventType => _eventType; + public string? UserAgent => _userAgent; + public string? IpAddress => _ipAddress; + public DateTime Timestamp => _timestamp; + + protected PixelEvent() { } + + public PixelEvent(Guid pixelId, Guid adId, Guid userId, PixelEventType eventType, string? userAgent = null, string? ipAddress = null) + { + Id = Guid.NewGuid(); + _pixelId = pixelId; + _adId = adId; + _userId = userId; + _eventType = eventType; + _userAgent = userAgent; + _ipAddress = ipAddress; + _timestamp = DateTime.UtcNow; + } +} + +public enum PixelEventType +{ + Impression = 1, + Click = 2, + PageView = 3 +} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Events/SampleCreatedDomainEvent.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Events/SampleCreatedDomainEvent.cs deleted file mode 100644 index 31bb2849..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Events/SampleCreatedDomainEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MediatR; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.Domain.Events; - -/// -/// EN: Domain event raised when a new Sample is created. -/// VI: Domain event được phát ra khi một Sample mới được tạo. -/// -public class SampleCreatedDomainEvent : INotification -{ - /// - /// EN: The newly created sample. - /// VI: Sample mới được tạo. - /// - public Sample Sample { get; } - - public SampleCreatedDomainEvent(Sample sample) - { - Sample = sample; - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Events/SampleStatusChangedDomainEvent.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Events/SampleStatusChangedDomainEvent.cs deleted file mode 100644 index e6b69660..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Events/SampleStatusChangedDomainEvent.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.Domain.Events; - -/// -/// EN: Domain event raised when Sample status changes. -/// VI: Domain event được phát ra khi trạng thái Sample thay đổi. -/// -public class SampleStatusChangedDomainEvent : INotification -{ - /// - /// EN: The sample ID. - /// VI: ID của sample. - /// - public Guid SampleId { get; } - - /// - /// EN: Previous status before the change. - /// VI: Trạng thái trước khi thay đổi. - /// - public SampleStatus PreviousStatus { get; } - - /// - /// EN: New status after the change. - /// VI: Trạng thái mới sau khi thay đổi. - /// - public SampleStatus NewStatus { get; } - - public SampleStatusChangedDomainEvent( - Guid sampleId, - SampleStatus previousStatus, - SampleStatus newStatus) - { - SampleId = sampleId; - PreviousStatus = previousStatus; - NewStatus = newStatus; - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Exceptions/AdsTrackingDomainException.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Exceptions/AdsTrackingDomainException.cs new file mode 100644 index 00000000..47d53515 --- /dev/null +++ b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Exceptions/AdsTrackingDomainException.cs @@ -0,0 +1,8 @@ +namespace AdsTrackingService.Domain.Exceptions; + +public class AdsTrackingDomainException : Exception +{ + public AdsTrackingDomainException() { } + public AdsTrackingDomainException(string message) : base(message) { } + public AdsTrackingDomainException(string message, Exception innerException) : base(message, innerException) { } +} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Exceptions/SampleDomainException.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Exceptions/SampleDomainException.cs deleted file mode 100644 index cee2b970..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Domain/Exceptions/SampleDomainException.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace AdsTrackingService.Domain.Exceptions; - -/// -/// EN: Exception for Sample aggregate domain errors. -/// VI: Exception cho các lỗi domain của Sample aggregate. -/// -public class SampleDomainException : DomainException -{ - public SampleDomainException() - { - } - - public SampleDomainException(string message) : base(message) - { - } - - public SampleDomainException(string message, Exception innerException) - : base(message, innerException) - { - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/MyServiceContext.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/AdsTrackingServiceContext.cs similarity index 100% rename from services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/MyServiceContext.cs rename to services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/AdsTrackingServiceContext.cs diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/DependencyInjection.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/DependencyInjection.cs index 6d54f5d2..268f0780 100644 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/DependencyInjection.cs +++ b/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/DependencyInjection.cs @@ -1,27 +1,16 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; using AdsTrackingService.Infrastructure.Idempotency; -using AdsTrackingService.Infrastructure.Repositories; namespace AdsTrackingService.Infrastructure; -/// -/// EN: Dependency injection extensions for Infrastructure layer. -/// VI: Extensions dependency injection cho lớp Infrastructure. -/// public static class DependencyInjection { - /// - /// EN: Add infrastructure services to the DI container. - /// VI: Thêm các services infrastructure vào DI container. - /// public static IServiceCollection AddInfrastructure( this IServiceCollection services, IConfiguration configuration) { - // EN: Add DbContext with PostgreSQL / VI: Thêm DbContext với PostgreSQL services.AddDbContext(options => { var connectionString = configuration.GetConnectionString("DefaultConnection") @@ -31,14 +20,9 @@ public static class DependencyInjection options.UseNpgsql(connectionString, npgsqlOptions => { npgsqlOptions.MigrationsAssembly(typeof(AdsTrackingServiceContext).Assembly.FullName); - npgsqlOptions.EnableRetryOnFailure( - maxRetryCount: 5, - maxRetryDelay: TimeSpan.FromSeconds(30), - errorCodesToAdd: null); + npgsqlOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null); }); - // EN: Enable sensitive data logging in development only - // VI: Chỉ bật sensitive data logging trong development if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") { options.EnableSensitiveDataLogging(); @@ -46,12 +30,7 @@ public static class DependencyInjection } }); - // EN: Register repositories / VI: Đăng ký repositories - services.AddScoped(); - - // EN: Register idempotency services / VI: Đăng ký idempotency services services.AddScoped(); - return services; } } diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs deleted file mode 100644 index 88ca2fdd..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.Infrastructure.EntityConfigurations; - -/// -/// EN: EF Core configuration for Sample entity. -/// VI: Cấu hình EF Core cho entity Sample. -/// -public class SampleEntityTypeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - // EN: Table name / VI: Tên bảng - builder.ToTable("samples"); - - // EN: Primary key / VI: Khóa chính - builder.HasKey(s => s.Id); - - // EN: Ignore domain events (not persisted) - // VI: Bỏ qua domain events (không lưu) - builder.Ignore(s => s.DomainEvents); - - // EN: Properties / VI: Các thuộc tính - builder.Property(s => s.Id) - .HasColumnName("id") - .IsRequired(); - - builder.Property("_name") - .HasColumnName("name") - .HasMaxLength(200) - .IsRequired(); - - builder.Property("_description") - .HasColumnName("description") - .HasMaxLength(1000); - - builder.Property("_createdAt") - .HasColumnName("created_at") - .IsRequired(); - - builder.Property("_updatedAt") - .HasColumnName("updated_at"); - - // EN: Status relationship / VI: Quan hệ với Status - builder.Property(s => s.StatusId) - .HasColumnName("status_id") - .IsRequired(); - - builder.HasOne(s => s.Status) - .WithMany() - .HasForeignKey(s => s.StatusId) - .OnDelete(DeleteBehavior.Restrict); - - // EN: Indexes / VI: Các index - builder.HasIndex("_name"); - builder.HasIndex(s => s.StatusId); - builder.HasIndex("_createdAt"); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs deleted file mode 100644 index 3d0450db..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsTrackingService.Infrastructure.EntityConfigurations; - -/// -/// EN: EF Core configuration for SampleStatus enumeration. -/// VI: Cấu hình EF Core cho enumeration SampleStatus. -/// -public class SampleStatusEntityTypeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - // EN: Table name / VI: Tên bảng - builder.ToTable("sample_statuses"); - - // EN: Primary key / VI: Khóa chính - builder.HasKey(s => s.Id); - - builder.Property(s => s.Id) - .HasColumnName("id") - .ValueGeneratedNever() - .IsRequired(); - - builder.Property(s => s.Name) - .HasColumnName("name") - .HasMaxLength(50) - .IsRequired(); - - // EN: Seed initial data / VI: Seed dữ liệu ban đầu - builder.HasData( - SampleStatus.Draft, - SampleStatus.Active, - SampleStatus.Completed, - SampleStatus.Cancelled - ); - } -} diff --git a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/Repositories/SampleRepository.cs b/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/Repositories/SampleRepository.cs deleted file mode 100644 index a92f74d3..00000000 --- a/services/ads-tracking-service-net/src/AdsTrackingService.Infrastructure/Repositories/SampleRepository.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; -using AdsTrackingService.Domain.SeedWork; - -namespace AdsTrackingService.Infrastructure.Repositories; - -/// -/// EN: Repository implementation for Sample aggregate. -/// VI: Triển khai repository cho Sample aggregate. -/// -public class SampleRepository : ISampleRepository -{ - private readonly AdsTrackingServiceContext _context; - - /// - /// EN: Unit of work for transaction management. - /// VI: Unit of work cho quản lý transaction. - /// - public IUnitOfWork UnitOfWork => _context; - - public SampleRepository(AdsTrackingServiceContext context) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - } - - /// - public async Task GetAsync(Guid sampleId) - { - var sample = await _context.Samples - .Include(s => s.Status) - .FirstOrDefaultAsync(s => s.Id == sampleId); - - return sample; - } - - /// - public async Task> GetAllAsync() - { - return await _context.Samples - .Include(s => s.Status) - .OrderByDescending(s => s.CreatedAt) - .ToListAsync(); - } - - /// - public Sample Add(Sample sample) - { - return _context.Samples.Add(sample).Entity; - } - - /// - public void Update(Sample sample) - { - _context.Entry(sample).State = EntityState.Modified; - } - - /// - public void Delete(Sample sample) - { - _context.Samples.Remove(sample); - } - - /// - public async Task> GetByStatusAsync(int statusId) - { - return await _context.Samples - .Include(s => s.Status) - .Where(s => s.StatusId == statusId) - .OrderByDescending(s => s.CreatedAt) - .ToListAsync(); - } -} diff --git a/services/ads-tracking-service-net/tests/AdsTrackingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs b/services/ads-tracking-service-net/tests/AdsTrackingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs deleted file mode 100644 index 6967b93b..00000000 --- a/services/ads-tracking-service-net/tests/AdsTrackingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging; -using Moq; -using AdsTrackingService.API.Application.Commands; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; -using AdsTrackingService.Domain.SeedWork; -using Xunit; - -namespace AdsTrackingService.UnitTests.Application; - -/// -/// EN: Unit tests for CreateSampleCommandHandler. -/// VI: Unit tests cho CreateSampleCommandHandler. -/// -public class CreateSampleCommandHandlerTests -{ - private readonly Mock _mockRepository; - private readonly Mock> _mockLogger; - private readonly CreateSampleCommandHandler _handler; - - public CreateSampleCommandHandlerTests() - { - _mockRepository = new Mock(); - _mockLogger = new Mock>(); - - var mockUnitOfWork = new Mock(); - mockUnitOfWork.Setup(u => u.SaveEntitiesAsync(It.IsAny())) - .ReturnsAsync(true); - - _mockRepository.SetupGet(r => r.UnitOfWork).Returns(mockUnitOfWork.Object); - - _handler = new CreateSampleCommandHandler(_mockRepository.Object, _mockLogger.Object); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCreateSampleAndReturnId() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", "Test Description"); - - _mockRepository.Setup(r => r.Add(It.IsAny())) - .Returns((Sample s) => s); - - // Act - var result = await _handler.Handle(command, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result.Id.Should().NotBeEmpty(); - _mockRepository.Verify(r => r.Add(It.IsAny()), Times.Once); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCallSaveEntities() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", null); - - // Act - await _handler.Handle(command, CancellationToken.None); - - // Assert - _mockRepository.Verify(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny()), Times.Once); - } -} diff --git a/services/ads-tracking-service-net/tests/AdsTrackingService.UnitTests/Domain/SampleAggregateTests.cs b/services/ads-tracking-service-net/tests/AdsTrackingService.UnitTests/Domain/SampleAggregateTests.cs deleted file mode 100644 index 27e36c1f..00000000 --- a/services/ads-tracking-service-net/tests/AdsTrackingService.UnitTests/Domain/SampleAggregateTests.cs +++ /dev/null @@ -1,151 +0,0 @@ -using FluentAssertions; -using AdsTrackingService.Domain.AggregatesModel.SampleAggregate; -using AdsTrackingService.Domain.Exceptions; -using Xunit; - -namespace AdsTrackingService.UnitTests.Domain; - -/// -/// EN: Unit tests for Sample aggregate. -/// VI: Unit tests cho Sample aggregate. -/// -public class SampleAggregateTests -{ - [Fact] - public void CreateSample_WithValidName_ShouldCreateWithDraftStatus() - { - // Arrange - var name = "Test Sample"; - var description = "Test Description"; - - // Act - var sample = new Sample(name, description); - - // Assert - sample.Name.Should().Be(name); - sample.Description.Should().Be(description); - sample.Status.Should().Be(SampleStatus.Draft); - sample.Id.Should().NotBeEmpty(); - sample.DomainEvents.Should().ContainSingle(); // SampleCreatedDomainEvent - } - - [Fact] - public void CreateSample_WithEmptyName_ShouldThrowException() - { - // Arrange - var name = ""; - - // Act - var act = () => new Sample(name); - - // Assert - act.Should().Throw() - .WithMessage("Sample name cannot be empty"); - } - - [Fact] - public void Activate_WhenDraft_ShouldChangeToActive() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.ClearDomainEvents(); - - // Act - sample.Activate(); - - // Assert - sample.Status.Should().Be(SampleStatus.Active); - sample.DomainEvents.Should().ContainSingle(); // SampleStatusChangedDomainEvent - } - - [Fact] - public void Activate_WhenNotDraft_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - - // Act - var act = () => sample.Activate(); - - // Assert - act.Should().Throw() - .WithMessage("Only draft samples can be activated"); - } - - [Fact] - public void Complete_WhenActive_ShouldChangeToCompleted() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - sample.ClearDomainEvents(); - - // Act - sample.Complete(); - - // Assert - sample.Status.Should().Be(SampleStatus.Completed); - } - - [Fact] - public void Cancel_WhenDraftOrActive_ShouldChangeToCancelled() - { - // Arrange - var sample = new Sample("Test Sample"); - - // Act - sample.Cancel(); - - // Assert - sample.Status.Should().Be(SampleStatus.Cancelled); - } - - [Fact] - public void Cancel_WhenCompleted_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - sample.Complete(); - - // Act - var act = () => sample.Cancel(); - - // Assert - act.Should().Throw() - .WithMessage("Cannot cancel a completed sample"); - } - - [Fact] - public void Update_WhenNotCancelled_ShouldUpdateNameAndDescription() - { - // Arrange - var sample = new Sample("Original Name", "Original Description"); - var newName = "Updated Name"; - var newDescription = "Updated Description"; - - // Act - sample.Update(newName, newDescription); - - // Assert - sample.Name.Should().Be(newName); - sample.Description.Should().Be(newDescription); - sample.UpdatedAt.Should().NotBeNull(); - } - - [Fact] - public void Update_WhenCancelled_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Cancel(); - - // Act - var act = () => sample.Update("New Name", null); - - // Assert - act.Should().Throw() - .WithMessage("Cannot update a cancelled sample"); - } -} diff --git a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs index 6beeadae..09fd04d2 100644 --- a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs +++ b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs @@ -52,54 +52,61 @@ public class OrderEntityTypeConfiguration : IEntityTypeConfiguration // EN: OrderItems collection // VI: Collection OrderItems - builder.OwnsMany("_orderItems", oi => + builder.OwnsMany(o => o.Items, orderItems => { - oi.ToTable("order_items"); + orderItems.ToTable("order_items"); - oi.WithOwner().HasForeignKey("OrderId"); + orderItems.WithOwner().HasForeignKey("OrderId"); - oi.Property("OrderId") + orderItems.Property("OrderId") .HasColumnName("order_id"); - oi.HasKey("Id"); + orderItems.HasKey(oi => oi.Id); - oi.Property(x => x.Id) + orderItems.Property(x => x.Id) .HasColumnName("id") .ValueGeneratedNever(); - oi.Property("_productId") + orderItems.Property("_productId") .HasColumnName("product_id") .IsRequired(); - oi.Property("_productName") + orderItems.Property("_productName") .HasColumnName("product_name") .HasMaxLength(255) .IsRequired(); - oi.Property(x => x.ProductTypeId) - .HasColumnName("product_type_id") + orderItems.Property("_productType") + .HasColumnName("product_type") + .HasMaxLength(50) .IsRequired(); - oi.Property("_quantity") + orderItems.Property("_quantity") .HasColumnName("quantity") .IsRequired(); - oi.Property("_unitPrice") + orderItems.Property("_unitPrice") .HasColumnName("unit_price") .HasColumnType("decimal(18,2)") .IsRequired(); - oi.Property("_totalPrice") - .HasColumnName("total_price") - .HasColumnType("decimal(18,2)") + orderItems.Property("_status") + .HasColumnName("status") + .HasMaxLength(50) .IsRequired(); - oi.Ignore(x => x.ProductId); - oi.Ignore(x => x.ProductName); - oi.Ignore(x => x.ProductType); - oi.Ignore(x => x.Quantity); - oi.Ignore(x => x.UnitPrice); - oi.Ignore(x => x.TotalPrice); + orderItems.Property("_metadata") + .HasColumnName("metadata") + .HasColumnType("jsonb"); + + orderItems.Ignore(x => x.ProductId); + orderItems.Ignore(x => x.ProductName); + orderItems.Ignore(x => x.ProductType); + orderItems.Ignore(x => x.Quantity); + orderItems.Ignore(x => x.UnitPrice); + orderItems.Ignore(x => x.TotalPrice); + orderItems.Ignore(x => x.Status); + orderItems.Ignore(x => x.Metadata); }); // EN: Indexes @@ -114,9 +121,7 @@ public class OrderEntityTypeConfiguration : IEntityTypeConfiguration builder.Ignore(o => o.ShopId); builder.Ignore(o => o.CustomerId); builder.Ignore(o => o.Status); - builder.Ignore(o => o.OrderItems); builder.Ignore(o => o.TotalAmount); - builder.Ignore(o => o.Notes); builder.Ignore(o => o.CreatedAt); builder.Ignore(o => o.UpdatedAt); } diff --git a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs deleted file mode 100644 index 062ed4f8..00000000 --- a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs +++ /dev/null @@ -1,26 +0,0 @@ -// EN: OrderItem entity configuration (owned type). -// VI: Cấu hình entity OrderItem (owned type). - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using OrderService.Domain.AggregatesModel.OrderAggregate; - -namespace OrderService.Infrastructure.EntityConfigurations; - -/// -/// EN: EF Core configuration for OrderItem entity (configured as owned type in OrderEntityTypeConfiguration). -/// VI: Cấu hình EF Core cho OrderItem entity (được cấu hình như owned type trong OrderEntityTypeConfiguration). -/// -/// -/// EN: OrderItem is configured as an owned type within Order aggregate. -/// VI: OrderItem được cấu hình như owned type trong Order aggregate. -/// -public class OrderItemEntityTypeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - // EN: OrderItem is configured as owned type in OrderEntityTypeConfiguration - // VI: OrderItem được cấu hình như owned type trong OrderEntityTypeConfiguration - builder.Ignore(o => o.Id); - } -} diff --git a/services/order-service-net/src/OrderService.Infrastructure/OrderContext.cs b/services/order-service-net/src/OrderService.Infrastructure/OrderContext.cs index b972ca90..e4613264 100644 --- a/services/order-service-net/src/OrderService.Infrastructure/OrderContext.cs +++ b/services/order-service-net/src/OrderService.Infrastructure/OrderContext.cs @@ -46,7 +46,6 @@ public class OrderContext : DbContext, IUnitOfWork // EN: Apply entity configurations // VI: Áp dụng các cấu hình entity modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration()); - modelBuilder.ApplyConfiguration(new OrderItemEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new OrderStatusEntityTypeConfiguration()); }