feat: Remove sample aggregates, introduce BillingAccount aggregate, and refactor CatalogServiceContext to CatalogContext.
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
using AdsBillingService.Domain.SeedWork;
|
||||
|
||||
namespace AdsBillingService.Domain.AggregatesModel.BillingAccountAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Billing account aggregate root - manages advertiser billing and payments.
|
||||
/// VI: Billing account aggregate root - quản lý thanh toán và billing của advertiser.
|
||||
/// </summary>
|
||||
public class BillingAccount : Entity, IAggregateRoot
|
||||
{
|
||||
private Guid _advertiserId;
|
||||
private Guid? _walletId;
|
||||
private PaymentMethodType _paymentMethod;
|
||||
private BillingThreshold? _threshold;
|
||||
private AccountStatus _status;
|
||||
private decimal _balance;
|
||||
private decimal _creditLimit;
|
||||
|
||||
public Guid AdvertiserId => _advertiserId;
|
||||
public Guid? WalletId => _walletId;
|
||||
public PaymentMethodType PaymentMethod => _paymentMethod;
|
||||
public BillingThreshold? Threshold => _threshold;
|
||||
public AccountStatus Status => _status;
|
||||
public decimal Balance => _balance;
|
||||
public decimal CreditLimit => _creditLimit;
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
public DateTime? UpdatedAt { get; private set; }
|
||||
|
||||
protected BillingAccount() { }
|
||||
|
||||
public BillingAccount(Guid advertiserId, Guid? walletId, PaymentMethodType paymentMethod)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
_advertiserId = advertiserId;
|
||||
_walletId = walletId;
|
||||
_paymentMethod = paymentMethod;
|
||||
_status = AccountStatus.Active;
|
||||
_balance = 0;
|
||||
_creditLimit = 0;
|
||||
CreatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Set auto-charge threshold for prepaid accounts.
|
||||
/// VI: Thiết lập ngưỡng tự động nạp tiền cho tài khoản trả trước.
|
||||
/// </summary>
|
||||
public void SetThreshold(decimal amount, bool autoCharge)
|
||||
{
|
||||
_threshold = new BillingThreshold(amount, autoCharge);
|
||||
_UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Deduct amount from balance (for prepaid).
|
||||
/// VI: Trừ tiền từ số dư (cho trả trước).
|
||||
/// </summary>
|
||||
public void DeductBalance(decimal amount)
|
||||
{
|
||||
if (_paymentMethod != PaymentMethodType.Prepaid)
|
||||
throw new AdsBillingDomainException("Can only deduct from prepaid accounts");
|
||||
|
||||
if (_balance < amount)
|
||||
throw new AdsBillingDomainException("Insufficient balance");
|
||||
|
||||
_balance -= amount;
|
||||
_UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Add balance (from wallet top-up).
|
||||
/// VI: Thêm số dư (từ nạp ví).
|
||||
/// </summary>
|
||||
public void AddBalance(decimal amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
throw new AdsBillingDomainException("Amount must be positive");
|
||||
|
||||
_balance += amount;
|
||||
_UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Suspend account (e.g., due to payment issues).
|
||||
/// VI: Tạm ngưng tài khoản (ví dụ do vấn đề thanh toán).
|
||||
/// </summary>
|
||||
public void Suspend()
|
||||
{
|
||||
_status = AccountStatus.Suspended;
|
||||
_UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private DateTime? _UpdatedAt
|
||||
{
|
||||
get => UpdatedAt;
|
||||
set => UpdatedAt = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Payment method type enumeration.
|
||||
/// VI: Enum loại phương thức thanh toán.
|
||||
/// </summary>
|
||||
public enum PaymentMethodType
|
||||
{
|
||||
Prepaid = 1, // Pay upfront via Wallet
|
||||
Postpaid = 2, // Monthly invoice
|
||||
CreditCard = 3 // Auto-charge credit card
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Account status enumeration.
|
||||
/// VI: Enum trạng thái tài khoản.
|
||||
/// </summary>
|
||||
public enum AccountStatus
|
||||
{
|
||||
Active = 1,
|
||||
Suspended = 2,
|
||||
Closed = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Billing threshold value object.
|
||||
/// VI: Value object ngưỡng billing.
|
||||
/// </summary>
|
||||
public class BillingThreshold : ValueObject
|
||||
{
|
||||
public decimal Amount { get; private set; }
|
||||
public bool AutoCharge { get; private set; }
|
||||
|
||||
protected BillingThreshold() { }
|
||||
|
||||
public BillingThreshold(decimal amount, bool autoCharge)
|
||||
{
|
||||
if (amount <= 0)
|
||||
throw new AdsBillingDomainException("Threshold amount must be positive");
|
||||
|
||||
Amount = amount;
|
||||
AutoCharge = autoCharge;
|
||||
}
|
||||
|
||||
protected override IEnumerable<object> GetEqualityComponents()
|
||||
{
|
||||
yield return Amount;
|
||||
yield return AutoCharge;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using AdsBillingService.Domain.SeedWork;
|
||||
|
||||
namespace AdsBillingService.Domain.AggregatesModel.SampleAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Repository interface for Sample aggregate.
|
||||
/// VI: Interface repository cho Sample aggregate.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public interface ISampleRepository : IRepository<Sample>
|
||||
{
|
||||
/// <summary>
|
||||
/// EN: Get a sample by its ID.
|
||||
/// VI: Lấy một sample theo ID.
|
||||
/// </summary>
|
||||
/// <param name="sampleId">EN: The sample ID / VI: ID của sample</param>
|
||||
/// <returns>EN: The sample or null if not found / VI: Sample hoặc null nếu không tìm thấy</returns>
|
||||
Task<Sample?> GetAsync(Guid sampleId);
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all samples.
|
||||
/// VI: Lấy tất cả samples.
|
||||
/// </summary>
|
||||
/// <returns>EN: List of samples / VI: Danh sách samples</returns>
|
||||
Task<IEnumerable<Sample>> GetAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// EN: Add a new sample.
|
||||
/// VI: Thêm một sample mới.
|
||||
/// </summary>
|
||||
/// <param name="sample">EN: The sample to add / VI: Sample cần thêm</param>
|
||||
/// <returns>EN: The added sample / VI: Sample đã thêm</returns>
|
||||
Sample Add(Sample sample);
|
||||
|
||||
/// <summary>
|
||||
/// EN: Update an existing sample.
|
||||
/// VI: Cập nhật một sample đã tồn tại.
|
||||
/// </summary>
|
||||
/// <param name="sample">EN: The sample to update / VI: Sample cần cập nhật</param>
|
||||
void Update(Sample sample);
|
||||
|
||||
/// <summary>
|
||||
/// EN: Delete a sample.
|
||||
/// VI: Xóa một sample.
|
||||
/// </summary>
|
||||
/// <param name="sample">EN: The sample to delete / VI: Sample cần xóa</param>
|
||||
void Delete(Sample sample);
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get samples by status.
|
||||
/// VI: Lấy samples theo trạng thái.
|
||||
/// </summary>
|
||||
/// <param name="statusId">EN: The status ID / VI: ID trạng thái</param>
|
||||
/// <returns>EN: List of samples with given status / VI: Danh sách samples với trạng thái cho trước</returns>
|
||||
Task<IEnumerable<Sample>> GetByStatusAsync(int statusId);
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
using AdsBillingService.Domain.Events;
|
||||
using AdsBillingService.Domain.Exceptions;
|
||||
using AdsBillingService.Domain.SeedWork;
|
||||
|
||||
namespace AdsBillingService.Domain.AggregatesModel.SampleAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Sample aggregate root demonstrating DDD patterns.
|
||||
/// VI: Sample aggregate root minh họa các pattern DDD.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Sample name (required).
|
||||
/// VI: Tên sample (bắt buộc).
|
||||
/// </summary>
|
||||
public string Name => _name;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Optional description.
|
||||
/// VI: Mô tả tùy chọn.
|
||||
/// </summary>
|
||||
public string? Description => _description;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Current status.
|
||||
/// VI: Trạng thái hiện tại.
|
||||
/// </summary>
|
||||
public SampleStatus Status => _status;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Status ID for EF Core mapping.
|
||||
/// VI: ID trạng thái cho EF Core mapping.
|
||||
/// </summary>
|
||||
public int StatusId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Creation timestamp.
|
||||
/// VI: Thời gian tạo.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt => _createdAt;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Last update timestamp.
|
||||
/// VI: Thời gian cập nhật cuối.
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt => _updatedAt;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Private constructor for EF Core.
|
||||
/// VI: Constructor private cho EF Core.
|
||||
/// </summary>
|
||||
protected Sample()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="name">EN: Sample name / VI: Tên sample</param>
|
||||
/// <param name="description">EN: Optional description / VI: Mô tả tùy chọn</param>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Update sample information.
|
||||
/// VI: Cập nhật thông tin sample.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Activate the sample.
|
||||
/// VI: Kích hoạt sample.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Complete the sample.
|
||||
/// VI: Hoàn thành sample.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Cancel the sample.
|
||||
/// VI: Hủy sample.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using AdsBillingService.Domain.SeedWork;
|
||||
|
||||
namespace AdsBillingService.Domain.AggregatesModel.SampleAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Sample status enumeration following type-safe enum pattern.
|
||||
/// VI: Enumeration trạng thái Sample theo pattern enum an toàn kiểu.
|
||||
/// </summary>
|
||||
public class SampleStatus : Enumeration
|
||||
{
|
||||
/// <summary>
|
||||
/// EN: Draft status - initial state
|
||||
/// VI: Trạng thái nháp - trạng thái ban đầu
|
||||
/// </summary>
|
||||
public static SampleStatus Draft = new(1, nameof(Draft));
|
||||
|
||||
/// <summary>
|
||||
/// EN: Active status - ready for use
|
||||
/// VI: Trạng thái hoạt động - sẵn sàng sử dụng
|
||||
/// </summary>
|
||||
public static SampleStatus Active = new(2, nameof(Active));
|
||||
|
||||
/// <summary>
|
||||
/// EN: Completed status - finished processing
|
||||
/// VI: Trạng thái hoàn thành - đã xử lý xong
|
||||
/// </summary>
|
||||
public static SampleStatus Completed = new(3, nameof(Completed));
|
||||
|
||||
/// <summary>
|
||||
/// EN: Cancelled status - cancelled by user
|
||||
/// VI: Trạng thái đã hủy - bị hủy bởi người dùng
|
||||
/// </summary>
|
||||
public static SampleStatus Cancelled = new(4, nameof(Cancelled));
|
||||
|
||||
public SampleStatus(int id, string name) : base(id, name)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all available statuses.
|
||||
/// VI: Lấy tất cả các trạng thái có sẵn.
|
||||
/// </summary>
|
||||
public static IEnumerable<SampleStatus> List() => GetAll<SampleStatus>();
|
||||
|
||||
/// <summary>
|
||||
/// EN: Parse status from name.
|
||||
/// VI: Parse trạng thái từ tên.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Parse status from ID.
|
||||
/// VI: Parse trạng thái từ ID.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ public class AdsController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Serve an ad based on user context (< 100ms target).
|
||||
/// VI: Serve quảng cáo dựa trên ngữ cảnh người dùng (mục tiêu < 100ms).
|
||||
/// EN: Serve an ad based on user context (less than 100ms target).
|
||||
/// VI: Serve quảng cáo dựa trên ngữ cảnh người dùng (mục tiêu dưới 100ms).
|
||||
/// </summary>
|
||||
[HttpPost("serve")]
|
||||
[ProducesResponseType(typeof(ServedAdDto), StatusCodes.Status200OK)]
|
||||
|
||||
@@ -13,11 +13,11 @@ namespace CatalogService.API.Application.Behaviors;
|
||||
public class TransactionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : IRequest<TResponse>
|
||||
{
|
||||
private readonly CatalogServiceContext _dbContext;
|
||||
private readonly CatalogContext _dbContext;
|
||||
private readonly ILogger<TransactionBehavior<TRequest, TResponse>> _logger;
|
||||
|
||||
public TransactionBehavior(
|
||||
CatalogServiceContext dbContext,
|
||||
CatalogContext dbContext,
|
||||
ILogger<TransactionBehavior<TRequest, TResponse>> logger)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
<!-- EN: FluentValidation for request validation / VI: FluentValidation cho validation request -->
|
||||
<PackageReference Include="FluentValidation" Version="11.11.0" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.11.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<!-- EN: Swagger/OpenAPI / VI: Swagger/OpenAPI -->
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
|
||||
|
||||
@@ -40,11 +40,6 @@ public class CatalogContext : DbContext, IUnitOfWork
|
||||
/// </summary>
|
||||
public bool HasActiveTransaction => _currentTransaction != null;
|
||||
|
||||
public CatalogContext(DbContextOptions<CatalogContext> options) : base(options)
|
||||
{
|
||||
_mediator = null!;
|
||||
}
|
||||
|
||||
public CatalogContext(DbContextOptions<CatalogContext> options, IMediator mediator) : base(options)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using CatalogService.Domain.AggregatesModel.SampleAggregate;
|
||||
using CatalogService.Domain.AggregatesModel.ProductAggregate;
|
||||
using CatalogService.Infrastructure.Idempotency;
|
||||
using CatalogService.Infrastructure.Repositories;
|
||||
|
||||
@@ -22,7 +22,7 @@ public static class DependencyInjection
|
||||
IConfiguration configuration)
|
||||
{
|
||||
// EN: Add DbContext with PostgreSQL / VI: Thêm DbContext với PostgreSQL
|
||||
services.AddDbContext<CatalogServiceContext>(options =>
|
||||
services.AddDbContext<CatalogContext>(options =>
|
||||
{
|
||||
var connectionString = configuration.GetConnectionString("DefaultConnection")
|
||||
?? configuration["DATABASE_URL"]
|
||||
@@ -30,7 +30,7 @@ public static class DependencyInjection
|
||||
|
||||
options.UseNpgsql(connectionString, npgsqlOptions =>
|
||||
{
|
||||
npgsqlOptions.MigrationsAssembly(typeof(CatalogServiceContext).Assembly.FullName);
|
||||
npgsqlOptions.MigrationsAssembly(typeof(CatalogContext).Assembly.FullName);
|
||||
npgsqlOptions.EnableRetryOnFailure(
|
||||
maxRetryCount: 5,
|
||||
maxRetryDelay: TimeSpan.FromSeconds(30),
|
||||
@@ -47,7 +47,7 @@ public static class DependencyInjection
|
||||
});
|
||||
|
||||
// EN: Register repositories / VI: Đăng ký repositories
|
||||
services.AddScoped<ISampleRepository, SampleRepository>();
|
||||
services.AddScoped<IProductRepository, ProductRepository>();
|
||||
|
||||
// EN: Register idempotency services / VI: Đăng ký idempotency services
|
||||
services.AddScoped<IRequestManager, RequestManager>();
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using CatalogService.Domain.AggregatesModel.SampleAggregate;
|
||||
|
||||
namespace CatalogService.Infrastructure.EntityConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: EF Core configuration for Sample entity.
|
||||
/// VI: Cấu hình EF Core cho entity Sample.
|
||||
/// </summary>
|
||||
public class SampleEntityTypeConfiguration : IEntityTypeConfiguration<Sample>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Sample> 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<string>("_name")
|
||||
.HasColumnName("name")
|
||||
.HasMaxLength(200)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property<string?>("_description")
|
||||
.HasColumnName("description")
|
||||
.HasMaxLength(1000);
|
||||
|
||||
builder.Property<DateTime>("_createdAt")
|
||||
.HasColumnName("created_at")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property<DateTime?>("_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");
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using CatalogService.Domain.AggregatesModel.SampleAggregate;
|
||||
|
||||
namespace CatalogService.Infrastructure.EntityConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// EN: EF Core configuration for SampleStatus enumeration.
|
||||
/// VI: Cấu hình EF Core cho enumeration SampleStatus.
|
||||
/// </summary>
|
||||
public class SampleStatusEntityTypeConfiguration : IEntityTypeConfiguration<SampleStatus>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<SampleStatus> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,9 @@ namespace CatalogService.Infrastructure.Idempotency;
|
||||
/// </summary>
|
||||
public class RequestManager : IRequestManager
|
||||
{
|
||||
private readonly CatalogServiceContext _context;
|
||||
private readonly CatalogContext _context;
|
||||
|
||||
public RequestManager(CatalogServiceContext context)
|
||||
public RequestManager(CatalogContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using CatalogService.Domain.AggregatesModel.SampleAggregate;
|
||||
using CatalogService.Domain.SeedWork;
|
||||
|
||||
namespace CatalogService.Infrastructure.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Repository implementation for Sample aggregate.
|
||||
/// VI: Triển khai repository cho Sample aggregate.
|
||||
/// </summary>
|
||||
public class SampleRepository : ISampleRepository
|
||||
{
|
||||
private readonly CatalogServiceContext _context;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Unit of work for transaction management.
|
||||
/// VI: Unit of work cho quản lý transaction.
|
||||
/// </summary>
|
||||
public IUnitOfWork UnitOfWork => _context;
|
||||
|
||||
public SampleRepository(CatalogServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Sample?> GetAsync(Guid sampleId)
|
||||
{
|
||||
var sample = await _context.Samples
|
||||
.Include(s => s.Status)
|
||||
.FirstOrDefaultAsync(s => s.Id == sampleId);
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IEnumerable<Sample>> GetAllAsync()
|
||||
{
|
||||
return await _context.Samples
|
||||
.Include(s => s.Status)
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Sample Add(Sample sample)
|
||||
{
|
||||
return _context.Samples.Add(sample).Entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Update(Sample sample)
|
||||
{
|
||||
_context.Entry(sample).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Delete(Sample sample)
|
||||
{
|
||||
_context.Samples.Remove(sample);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IEnumerable<Sample>> GetByStatusAsync(int statusId)
|
||||
{
|
||||
return await _context.Samples
|
||||
.Include(s => s.Status)
|
||||
.Where(s => s.StatusId == statusId)
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||
// EN: Remove the existing DbContext registration
|
||||
// VI: Xóa đăng ký DbContext hiện tại
|
||||
var descriptor = services.SingleOrDefault(
|
||||
d => d.ServiceType == typeof(DbContextOptions<CatalogServiceContext>));
|
||||
d => d.ServiceType == typeof(DbContextOptions<CatalogContext>));
|
||||
|
||||
if (descriptor != null)
|
||||
{
|
||||
@@ -31,7 +31,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||
// EN: Remove DbContext service
|
||||
// VI: Xóa DbContext service
|
||||
var dbContextDescriptor = services.SingleOrDefault(
|
||||
d => d.ServiceType == typeof(CatalogServiceContext));
|
||||
d => d.ServiceType == typeof(CatalogContext));
|
||||
|
||||
if (dbContextDescriptor != null)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||
|
||||
// EN: Add in-memory database for testing
|
||||
// VI: Thêm in-memory database để test
|
||||
services.AddDbContext<CatalogServiceContext>(options =>
|
||||
services.AddDbContext<CatalogContext>(options =>
|
||||
{
|
||||
options.UseInMemoryDatabase("TestDatabase_" + Guid.NewGuid().ToString());
|
||||
});
|
||||
@@ -49,7 +49,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||
// VI: Đảm bảo database được tạo với seed data
|
||||
var sp = services.BuildServiceProvider();
|
||||
using var scope = sp.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<CatalogServiceContext>();
|
||||
var db = scope.ServiceProvider.GetRequiredService<CatalogContext>();
|
||||
db.Database.EnsureCreated();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user