diff --git a/OrderTrackingApp.Api/Controllers/OrdersController.cs b/OrderTrackingApp.Api/Controllers/OrdersController.cs index 4778258..978a006 100644 --- a/OrderTrackingApp.Api/Controllers/OrdersController.cs +++ b/OrderTrackingApp.Api/Controllers/OrdersController.cs @@ -3,7 +3,8 @@ using Microsoft.AspNetCore.Mvc; using OrderTrackingApp.Api.Examples; using OrderTrackingApp.Api.Models; -using OrderTrackingApp.Application.Commands.Orders; +using OrderTrackingApp.Application.Orders.Commands; +using OrderTrackingApp.Application.Orders.Queries; using Swashbuckle.AspNetCore.Filters; namespace OrderTrackingApp.Api.Controllers @@ -23,5 +24,21 @@ public async Task CreateOrder([FromBody] CreateOrderRequest reque return CreatedAtAction(nameof(CreateOrder), new { id = orderId }, new { OrderId = orderId }); } + + [HttpGet("{id}")] + public async Task GetOrderById(Guid id) + { + var order = await mediator.Send(new GetOrderByIdQuery(id)); + return Ok(order); + } + + [HttpGet] + public async Task GetOrders([FromQuery] GetOrdersRequest request) + { + var query = mapper.Map(request); + + var orders = await mediator.Send(query); + return Ok(orders); + } } } diff --git a/OrderTrackingApp.Api/Dockerfile b/OrderTrackingApp.Api/Dockerfile index b77026f..f4fe51f 100644 --- a/OrderTrackingApp.Api/Dockerfile +++ b/OrderTrackingApp.Api/Dockerfile @@ -7,6 +7,7 @@ WORKDIR /src COPY ["OrderTrackingApp.Api/OrderTrackingApp.Api.csproj", "OrderTrackingApp.Api/"] COPY ["OrderTrackingApp.Application/OrderTrackingApp.Application.csproj", "OrderTrackingApp.Application/"] +COPY ["OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj", "OrderTrackingApp.Application.Contracts/"] COPY ["OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj", "OrderTrackingApp.Domain/"] COPY ["OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj", "OrderTrackingApp.Infrastructure/"] COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] diff --git a/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs b/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs index aaa2245..b2198cb 100644 --- a/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs +++ b/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs @@ -26,7 +26,7 @@ public static IServiceCollection AddApiServices(this IServiceCollection services }); services.AddValidatorsFromAssemblyContaining(); - services.AddAutoMapper(config => { }, typeof(OrderMappingProfile).Assembly); + services.AddAutoMapper(config => { }, typeof(OrderProfile).Assembly); return services; } diff --git a/OrderTrackingApp.Api/MappingProfiles/OrderMappingProfile.cs b/OrderTrackingApp.Api/MappingProfiles/OrderProfile.cs similarity index 54% rename from OrderTrackingApp.Api/MappingProfiles/OrderMappingProfile.cs rename to OrderTrackingApp.Api/MappingProfiles/OrderProfile.cs index 72900a1..6e97398 100644 --- a/OrderTrackingApp.Api/MappingProfiles/OrderMappingProfile.cs +++ b/OrderTrackingApp.Api/MappingProfiles/OrderProfile.cs @@ -1,15 +1,18 @@ using AutoMapper; using OrderTrackingApp.Api.Models; -using OrderTrackingApp.Application.Commands.Orders; +using OrderTrackingApp.Application.Orders.Commands; +using OrderTrackingApp.Application.Orders.Queries; namespace OrderTrackingApp.Api.MappingProfiles { - public class OrderMappingProfile : Profile + public class OrderProfile : Profile { - public OrderMappingProfile() + public OrderProfile() { CreateMap(); CreateMap(); + + CreateMap(); } } } diff --git a/OrderTrackingApp.Api/Models/GetOrdersRequest.cs b/OrderTrackingApp.Api/Models/GetOrdersRequest.cs new file mode 100644 index 0000000..39f30a2 --- /dev/null +++ b/OrderTrackingApp.Api/Models/GetOrdersRequest.cs @@ -0,0 +1,15 @@ +namespace OrderTrackingApp.Api.Models +{ + public class GetOrdersRequest + { + public int Page { get; set; } = 1; + + public int PageSize { get; set; } = 50; + + public string? Status { get; set; } + + public DateTime? FromDate { get; set; } + + public DateTime? ToDate { get; set; } + } +} diff --git a/OrderTrackingApp.Api/appsettings.json b/OrderTrackingApp.Api/appsettings.json index 917aef7..7013322 100644 --- a/OrderTrackingApp.Api/appsettings.json +++ b/OrderTrackingApp.Api/appsettings.json @@ -8,7 +8,7 @@ "AllowedHosts": "*", "ConnectionStrings": { "SqlConnection": "Server=localhost,1433;Database=OrderDb;User=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True", - "MongoDb": "mongodb://mongoadmin:secret123@localhost:27017", + "MongoDb": "mongodb://admin:secret@localhost:27017", "RabbitMq": "amqp://guest:guest@localhost:5672/" } } diff --git a/OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj b/OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj new file mode 100644 index 0000000..85ef9ce --- /dev/null +++ b/OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/OrderTrackingApp.Application.Contracts/Orders/IOrderReadRepository.cs b/OrderTrackingApp.Application.Contracts/Orders/IOrderReadRepository.cs new file mode 100644 index 0000000..cb387b9 --- /dev/null +++ b/OrderTrackingApp.Application.Contracts/Orders/IOrderReadRepository.cs @@ -0,0 +1,16 @@ + +namespace OrderTrackingApp.Application.Contracts.Orders +{ + public interface IOrderReadRepository + { + Task GetByIdAsync(Guid id); + + Task> GetAllAsync(); + + Task InsertAsync(OrderDto order); + + Task> GetPaginatedOrdersAsync(int page, int pageSize, string? status, + DateTime? fromDate, DateTime? toDate, + CancellationToken cancellationToken); + } +} diff --git a/OrderTrackingApp.Application.Contracts/Orders/IOrderWriteRepository.cs b/OrderTrackingApp.Application.Contracts/Orders/IOrderWriteRepository.cs new file mode 100644 index 0000000..0bb50bc --- /dev/null +++ b/OrderTrackingApp.Application.Contracts/Orders/IOrderWriteRepository.cs @@ -0,0 +1,13 @@ +namespace OrderTrackingApp.Application.Contracts.Orders +{ + public interface IOrderWriteRepository + { + Task AddAsync(OrderDto order); + + Task UpdateAsync(OrderDto order); + + Task DeleteAsync(Guid id); + + Task GetByIdAsync(Guid id); + } +} diff --git a/OrderTrackingApp.Application.Contracts/Orders/OrderDto.cs b/OrderTrackingApp.Application.Contracts/Orders/OrderDto.cs new file mode 100644 index 0000000..33ba483 --- /dev/null +++ b/OrderTrackingApp.Application.Contracts/Orders/OrderDto.cs @@ -0,0 +1,28 @@ +using OrderTrackingApp.Domain.Entities; + +namespace OrderTrackingApp.Application.Contracts.Orders +{ + public class OrderDto + { + public Guid Id { get; set; } + + public string OrderNumber { get; set; } = string.Empty; + + public OrderStatus Status { get; set; } + + public DateTime CreatedAt { get; set; } + + public Guid CustomerId { get; set; } + + public List Items { get; set; } = []; + } + + public class OrderItemDto + { + public Guid ProductId { get; set; } + + public int Quantity { get; set; } + + public decimal UnitPrice { get; set; } + } +} diff --git a/OrderTrackingApp.Application.Contracts/PaginatedResult.cs b/OrderTrackingApp.Application.Contracts/PaginatedResult.cs new file mode 100644 index 0000000..773201d --- /dev/null +++ b/OrderTrackingApp.Application.Contracts/PaginatedResult.cs @@ -0,0 +1,13 @@ +namespace OrderTrackingApp.Application.Contracts +{ + public class PaginatedResult(List items, long totalCount, int page, int pageSize) + { + public List Items { get; } = items; + + public long TotalCount { get; } = totalCount; + + public int Page { get; } = page; + + public int PageSize { get; } = pageSize; + } +} diff --git a/OrderTrackingApp.Application/DTOs/OrderDto.cs b/OrderTrackingApp.Application/DTOs/OrderDto.cs deleted file mode 100644 index 558dd16..0000000 --- a/OrderTrackingApp.Application/DTOs/OrderDto.cs +++ /dev/null @@ -1,15 +0,0 @@ -using OrderTrackingApp.Domain.Entities; - -namespace OrderTrackingApp.Application.DTOs -{ - public class OrderDto - { - public Guid Id { get; set; } - - public string CustomerName { get; set; } = string.Empty; - - public OrderStatus Status { get; set; } - - public DateTime CreatedAt { get; set; } - } -} diff --git a/OrderTrackingApp.Application/Extensions/ApplicationServiceRegistrations.cs b/OrderTrackingApp.Application/Extensions/ApplicationServiceRegistrations.cs index 92f2ea3..ca42207 100644 --- a/OrderTrackingApp.Application/Extensions/ApplicationServiceRegistrations.cs +++ b/OrderTrackingApp.Application/Extensions/ApplicationServiceRegistrations.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; -using OrderTrackingApp.Application.Commands.Orders; +using OrderTrackingApp.Application.MappingProfiles; +using OrderTrackingApp.Application.Orders.Commands; namespace OrderTrackingApp.Application.Extensions { @@ -8,6 +9,7 @@ public static class ApplicationServiceRegistrations public static IServiceCollection AddApplicationServices(this IServiceCollection services) { services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateOrderCommandHandler).Assembly)); + services.AddAutoMapper(config => { }, typeof(OrderProfile).Assembly); return services; } } diff --git a/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs b/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs deleted file mode 100644 index 183b1fb..0000000 --- a/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using OrderTrackingApp.Domain.Entities; - -namespace OrderTrackingApp.Application.Interfaces -{ - public interface IOrderWriteRepository - { - Task AddAsync(Order order); - - Task UpdateAsync(Order order); - - Task DeleteAsync(Guid id); - - Task GetByIdAsync(Guid id); - } -} diff --git a/OrderTrackingApp.Application/MappingProfiles/OrderProfile.cs b/OrderTrackingApp.Application/MappingProfiles/OrderProfile.cs new file mode 100644 index 0000000..611290f --- /dev/null +++ b/OrderTrackingApp.Application/MappingProfiles/OrderProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using OrderTrackingApp.Application.Contracts.Orders; +using OrderTrackingApp.Domain.Entities; + +namespace OrderTrackingApp.Application.MappingProfiles +{ + public class OrderProfile : Profile + { + public OrderProfile() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } + } +} diff --git a/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj b/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj index 49e1b55..eab0a09 100644 --- a/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj +++ b/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj @@ -7,10 +7,12 @@ + + diff --git a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs b/OrderTrackingApp.Application/Orders/Commands/CreateOrderCommand.cs similarity index 85% rename from OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs rename to OrderTrackingApp.Application/Orders/Commands/CreateOrderCommand.cs index 3ec6b5c..491fdc3 100644 --- a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs +++ b/OrderTrackingApp.Application/Orders/Commands/CreateOrderCommand.cs @@ -1,6 +1,6 @@ using MediatR; -namespace OrderTrackingApp.Application.Commands.Orders +namespace OrderTrackingApp.Application.Orders.Commands { public class CreateOrderCommand : IRequest { diff --git a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs b/OrderTrackingApp.Application/Orders/Commands/CreateOrderCommandHandler.cs similarity index 83% rename from OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs rename to OrderTrackingApp.Application/Orders/Commands/CreateOrderCommandHandler.cs index 9f77403..3c161b5 100644 --- a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs +++ b/OrderTrackingApp.Application/Orders/Commands/CreateOrderCommandHandler.cs @@ -1,17 +1,17 @@ using MediatR; -using OrderTrackingApp.Application.Interfaces; +using OrderTrackingApp.Application.Contracts.Orders; using OrderTrackingApp.Domain.Entities; using OrderTrackingApp.Domain.Events; using OrderTrackingApp.Domain.Interfaces; -namespace OrderTrackingApp.Application.Commands.Orders +namespace OrderTrackingApp.Application.Orders.Commands { - public class CreateOrderCommandHandler(IOrderWriteRepository orderRepository, IProductRepository productRepository, IMediator mediator) + public class CreateOrderCommandHandler(IOrderWriteRepository orderRepository, IProductRepository productRepository, IMediator mediator) : IRequestHandler { public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) { - var orderItems = new List(); + var orderItems = new List(); var updatedProducts = new List(); var productIds = request.Items.Select(item => item.ProductId).ToList(); @@ -30,7 +30,7 @@ public async Task Handle(CreateOrderCommand request, CancellationToken can throw new InvalidOperationException($"Insufficient stock for product {product.Name}. Available: {product.StockQuantity}, Requested: {item.Quantity}."); } - orderItems.Add(new OrderItem { ProductId = item.ProductId, Quantity = item.Quantity, UnitPrice = product.Price }); + orderItems.Add(new OrderItemDto { ProductId = item.ProductId, Quantity = item.Quantity, UnitPrice = product.Price }); product.StockQuantity -= item.Quantity; @@ -39,12 +39,12 @@ public async Task Handle(CreateOrderCommand request, CancellationToken can await productRepository.UpdateProducts(updatedProducts); - var order = new Order + var order = new OrderDto { Id = Guid.NewGuid(), OrderNumber = $"ORD-{DateTime.UtcNow.Ticks}", CustomerId = request.CustomerId, - OrderDate = DateTime.UtcNow, + CreatedAt = DateTime.UtcNow, Status = OrderStatus.Pending, Items = orderItems }; diff --git a/OrderTrackingApp.Application/Orders/Queries/GetOrderByIdQuery.cs b/OrderTrackingApp.Application/Orders/Queries/GetOrderByIdQuery.cs new file mode 100644 index 0000000..d1c39ca --- /dev/null +++ b/OrderTrackingApp.Application/Orders/Queries/GetOrderByIdQuery.cs @@ -0,0 +1,7 @@ +using MediatR; +using OrderTrackingApp.Application.Contracts.Orders; + +namespace OrderTrackingApp.Application.Orders.Queries +{ + public record GetOrderByIdQuery(Guid OrderId) : IRequest; +} diff --git a/OrderTrackingApp.Application/Orders/Queries/GetOrderByIdQueryHandler.cs b/OrderTrackingApp.Application/Orders/Queries/GetOrderByIdQueryHandler.cs new file mode 100644 index 0000000..5be44d3 --- /dev/null +++ b/OrderTrackingApp.Application/Orders/Queries/GetOrderByIdQueryHandler.cs @@ -0,0 +1,14 @@ +using MediatR; +using OrderTrackingApp.Application.Contracts.Orders; + +namespace OrderTrackingApp.Application.Orders.Queries +{ + public class GetOrderByIdQueryHandler(IOrderReadRepository orderReadRepository) : IRequestHandler + { + public async Task Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) + { + var order = await orderReadRepository.GetByIdAsync(request.OrderId); + return order ?? throw new Exception($"Order not found for ID {request.OrderId}"); + } + } +} diff --git a/OrderTrackingApp.Application/Orders/Queries/GetOrdersQuery.cs b/OrderTrackingApp.Application/Orders/Queries/GetOrdersQuery.cs new file mode 100644 index 0000000..729822a --- /dev/null +++ b/OrderTrackingApp.Application/Orders/Queries/GetOrdersQuery.cs @@ -0,0 +1,19 @@ +using MediatR; +using OrderTrackingApp.Application.Contracts; +using OrderTrackingApp.Application.Contracts.Orders; + +namespace OrderTrackingApp.Application.Orders.Queries +{ + public record GetOrdersQuery : IRequest> + { + public int Page { get; set; } = 1; + + public int PageSize { get; set; } = 50; + + public string? Status { get; set; } + + public DateTime? FromDate { get; set; } + + public DateTime? ToDate { get; set; } + } +} diff --git a/OrderTrackingApp.Application/Orders/Queries/GetOrdersQueryHandler.cs b/OrderTrackingApp.Application/Orders/Queries/GetOrdersQueryHandler.cs new file mode 100644 index 0000000..7756a4e --- /dev/null +++ b/OrderTrackingApp.Application/Orders/Queries/GetOrdersQueryHandler.cs @@ -0,0 +1,20 @@ +using MediatR; +using OrderTrackingApp.Application.Contracts; +using OrderTrackingApp.Application.Contracts.Orders; + +namespace OrderTrackingApp.Application.Orders.Queries +{ + public class GetOrdersQueryHandler(IOrderReadRepository orderReadRepository) : IRequestHandler> + { + public async Task> Handle(GetOrdersQuery request, CancellationToken cancellationToken) + { + return await orderReadRepository.GetPaginatedOrdersAsync( + page: request.Page, + pageSize: request.PageSize, + status: request.Status, + fromDate: request.FromDate, + toDate: request.ToDate, + cancellationToken: cancellationToken); + } + } +} diff --git a/OrderTrackingApp.Consumer/Dockerfile b/OrderTrackingApp.Consumer/Dockerfile index 5cd2a65..b4aa87a 100644 --- a/OrderTrackingApp.Consumer/Dockerfile +++ b/OrderTrackingApp.Consumer/Dockerfile @@ -5,6 +5,7 @@ WORKDIR /src COPY ["OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj", "OrderTrackingApp.Consumer/"] COPY ["OrderTrackingApp.Application/OrderTrackingApp.Application.csproj", "OrderTrackingApp.Application/"] +COPY ["OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj", "OrderTrackingApp.Application.Contracts/"] COPY ["OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj", "OrderTrackingApp.Domain/"] COPY ["OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj", "OrderTrackingApp.Infrastructure/"] COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] diff --git a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj index 48f1333..4140bee 100644 --- a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj +++ b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj @@ -13,6 +13,7 @@ + diff --git a/OrderTrackingApp.Consumer/Program.cs b/OrderTrackingApp.Consumer/Program.cs index 3cb619d..2e4ec64 100644 --- a/OrderTrackingApp.Consumer/Program.cs +++ b/OrderTrackingApp.Consumer/Program.cs @@ -1,12 +1,14 @@ using OrderTrackingApp.Consumer; using OrderTrackingApp.Persistence.Extensions; using OrderTrackingApp.ReadPersistence.Extensions; +using OrderTrackingApp.Application.Extensions; var builder = Host.CreateApplicationBuilder(args); builder.Services.AddHostedService(); builder.Services.AddPersistenceServices(builder.Configuration); builder.Services.AddReadPersistence(); +builder.Services.AddApplicationServices(); var host = builder.Build(); host.Run(); diff --git a/OrderTrackingApp.Consumer/Worker.cs b/OrderTrackingApp.Consumer/Worker.cs index 683e1f2..3fc0b34 100644 --- a/OrderTrackingApp.Consumer/Worker.cs +++ b/OrderTrackingApp.Consumer/Worker.cs @@ -1,7 +1,5 @@ -using OrderTrackingApp.Application.Interfaces; +using OrderTrackingApp.Application.Contracts.Orders; using OrderTrackingApp.Domain.Events; -using OrderTrackingApp.ReadPersistence.Interfaces; -using OrderTrackingApp.ReadPersistence.Models; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System.Text; @@ -9,19 +7,11 @@ namespace OrderTrackingApp.Consumer; -public class Worker : BackgroundService +public class Worker(ILogger logger, IServiceProvider serviceProvider) : BackgroundService { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; private IConnection? _connection; private IChannel? _channel; - public Worker(ILogger logger, IServiceProvider serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await InitializeRabbitMqListener(); @@ -32,14 +22,14 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { - using var scope = _serviceProvider.CreateScope(); + using var scope = serviceProvider.CreateScope(); var orderWriteRepository = scope.ServiceProvider.GetRequiredService(); var orderReadRepository = scope.ServiceProvider.GetRequiredService(); var body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); - _logger.LogInformation("Received message: {message}", message); + logger.LogInformation("Received message: {message}", message); var orderEvent = JsonSerializer.Deserialize(message); @@ -48,30 +38,16 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) var order = await orderWriteRepository.GetByIdAsync(orderEvent.OrderId) ?? throw new Exception($"Order with ID {orderEvent.OrderId} not found."); - var readModel = new OrderReadModel - { - Id = order.Id, - CustomerId = order.CustomerId, - Status = order.Status, - CreatedAt = order.OrderDate, - Items = order.Items.Select(i => new OrderItemReadModel - { - ProductId = i.ProductId, - Quantity = i.Quantity, - UnitPrice = i.UnitPrice - }).ToList() - }; - - await orderReadRepository.InsertAsync(readModel); + await orderReadRepository.InsertAsync(order); - _logger.LogInformation("Synced OrderId {orderId} to MongoDB", order.Id); + logger.LogInformation("Synced OrderId {orderId} to MongoDB", order.Id); await _channel!.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false); } } catch (Exception ex) { - _logger.LogError(ex, "Failed to process RabbitMQ message."); + logger.LogError(ex, "Failed to process RabbitMQ message."); await _channel!.BasicNackAsync(deliveryTag: ea.DeliveryTag, multiple: false, requeue: true); } diff --git a/OrderTrackingApp.MigrationRunner/Dockerfile b/OrderTrackingApp.MigrationRunner/Dockerfile index 2e7e226..1186c8e 100644 --- a/OrderTrackingApp.MigrationRunner/Dockerfile +++ b/OrderTrackingApp.MigrationRunner/Dockerfile @@ -7,6 +7,7 @@ WORKDIR /src COPY ["OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj", "OrderTrackingApp.MigrationRunner/"] COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] COPY ["OrderTrackingApp.Application/OrderTrackingApp.Application.csproj", "OrderTrackingApp.Application/"] +COPY ["OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj", "OrderTrackingApp.Application.Contracts/"] COPY ["OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj", "OrderTrackingApp.Domain/"] # Copy your root certificate into the container diff --git a/OrderTrackingApp.MigrationRunner/ProductSeeder.cs b/OrderTrackingApp.MigrationRunner/ProductSeeder.cs index 15c49bf..0940046 100644 --- a/OrderTrackingApp.MigrationRunner/ProductSeeder.cs +++ b/OrderTrackingApp.MigrationRunner/ProductSeeder.cs @@ -22,7 +22,7 @@ public static async Task SeedAsync(AppDbContext context, CancellationToken cance Sku = "MSE-001", Description = "Ergonomic wireless mouse", Price = 29.99m, - StockQuantity = 100 + StockQuantity = 1000 }, new() { @@ -31,7 +31,7 @@ public static async Task SeedAsync(AppDbContext context, CancellationToken cance Sku = "KEY-101", Description = "RGB mechanical keyboard", Price = 79.99m, - StockQuantity = 50 + StockQuantity = 500 } }; diff --git a/OrderTrackingApp.Persistence.Tests/UnitTest1.cs b/OrderTrackingApp.Persistence.Tests/UnitTest1.cs deleted file mode 100644 index 9376a2a..0000000 --- a/OrderTrackingApp.Persistence.Tests/UnitTest1.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using OrderTrackingApp.Persistence.Repositories; - -namespace OrderTrackingApp.Persistence.Tests -{ - public class UnitTest1 - { - [Fact] - public async Task Test1() - { - var dbContext = CreateDbContext(); - - var repo= new OrderWriteRepository(dbContext); - - var id = Guid.NewGuid(); - await repo.AddAsync(new Domain.Entities.Order - { - Id = id, - CustomerId = Guid.NewGuid(), - OrderDate = DateTime.UtcNow, - Status = Domain.Entities.OrderStatus.Pending, - Items = new List - { - new Domain.Entities.OrderItem - { - ProductId = Guid.NewGuid(), - Quantity = 2, - UnitPrice = 10.0m - } - } - }); - - var order = await repo.GetByIdAsync(id); - } - - private AppDbContext CreateDbContext() - { - var options = new DbContextOptionsBuilder() - .UseSqlServer("Server=localhost,1433;Database=OrderDb;User=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True") - .Options; - return new AppDbContext(options); - } - } -} \ No newline at end of file diff --git a/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistrations.cs b/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistrations.cs index aa8ac99..200ab96 100644 --- a/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistrations.cs +++ b/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistrations.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using OrderTrackingApp.Application.Interfaces; +using OrderTrackingApp.Application.Contracts.Orders; using OrderTrackingApp.Domain.Interfaces; using OrderTrackingApp.Persistence.Repositories; diff --git a/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj b/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj index ba93773..edce8ab 100644 --- a/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj +++ b/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj @@ -17,6 +17,7 @@ + diff --git a/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs b/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs index c660a71..da9d827 100644 --- a/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs +++ b/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs @@ -1,13 +1,16 @@ -using Microsoft.EntityFrameworkCore; -using OrderTrackingApp.Application.Interfaces; +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using OrderTrackingApp.Application.Contracts.Orders; using OrderTrackingApp.Domain.Entities; namespace OrderTrackingApp.Persistence.Repositories { - public class OrderWriteRepository(AppDbContext dbContext) : IOrderWriteRepository + public class OrderWriteRepository(AppDbContext dbContext, IMapper mapper) : IOrderWriteRepository { - public async Task AddAsync(Order order) + public async Task AddAsync(OrderDto orderDto) { + var order = mapper.Map(orderDto); + dbContext.Orders.Add(order); await dbContext.SaveChangesAsync(); } @@ -23,15 +26,21 @@ public async Task DeleteAsync(Guid id) } } - public async Task UpdateAsync(Order order) + public async Task UpdateAsync(OrderDto orderDto) { + var order = mapper.Map(orderDto); + dbContext.Orders.Update(order); await dbContext.SaveChangesAsync(); } - public async Task GetByIdAsync(Guid id) + public async Task GetByIdAsync(Guid id) { - return await dbContext.Orders.FirstOrDefaultAsync(x => x.Id == id); + var order = await dbContext.Orders + .Include(o => o.Items) + .FirstOrDefaultAsync(x => x.Id == id); + + return mapper.Map(order); } } } diff --git a/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs b/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs index 02d71ac..85c86ee 100644 --- a/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs +++ b/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs @@ -1,5 +1,5 @@ using Microsoft.Extensions.DependencyInjection; -using OrderTrackingApp.ReadPersistence.Interfaces; +using OrderTrackingApp.Application.Contracts.Orders; using OrderTrackingApp.ReadPersistence.Repositories; namespace OrderTrackingApp.ReadPersistence.Extensions @@ -10,6 +10,8 @@ public static IServiceCollection AddReadPersistence(this IServiceCollection serv { services.AddSingleton(); services.AddScoped(); + + services.AddAutoMapper(config => { }, typeof(MappingProfiles.OrderProfile).Assembly); return services; } } diff --git a/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs b/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs deleted file mode 100644 index 93a1b2f..0000000 --- a/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using OrderTrackingApp.ReadPersistence.Models; - -namespace OrderTrackingApp.ReadPersistence.Interfaces -{ - public interface IOrderReadRepository - { - Task GetByIdAsync(Guid id); - - Task> GetAllAsync(); - - Task InsertAsync(OrderReadModel order); - } -} diff --git a/OrderTrackingApp.ReadPersistence/MappingProfiles/OrderProfile.cs b/OrderTrackingApp.ReadPersistence/MappingProfiles/OrderProfile.cs new file mode 100644 index 0000000..aa1c95b --- /dev/null +++ b/OrderTrackingApp.ReadPersistence/MappingProfiles/OrderProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using OrderTrackingApp.Application.Contracts.Orders; +using OrderTrackingApp.ReadPersistence.Models; + +namespace OrderTrackingApp.ReadPersistence.MappingProfiles +{ + public class OrderProfile : Profile + { + public OrderProfile() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } + } +} diff --git a/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj b/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj index 70bed8b..527abbf 100644 --- a/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj +++ b/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj @@ -12,6 +12,7 @@ + diff --git a/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs b/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs index de4a620..96c23ca 100644 --- a/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs +++ b/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs @@ -1,25 +1,71 @@ -using MongoDB.Driver; -using OrderTrackingApp.ReadPersistence.Interfaces; +using AutoMapper; +using MongoDB.Driver; +using OrderTrackingApp.Application.Contracts; +using OrderTrackingApp.Application.Contracts.Orders; +using OrderTrackingApp.Domain.Entities; using OrderTrackingApp.ReadPersistence.Models; namespace OrderTrackingApp.ReadPersistence.Repositories { - public class OrderReadRepository(MongoDbContext context) : IOrderReadRepository + public class OrderReadRepository(MongoDbContext context, IMapper mapper) : IOrderReadRepository { private readonly IMongoCollection _collection = context.Database.GetCollection("Orders"); - public async Task> GetAllAsync() + public async Task> GetAllAsync() { - return await _collection.Find(_ => true).ToListAsync(); + var orders = await _collection.Find(_ => true).ToListAsync(); + + return mapper.Map>(orders); } - public async Task GetByIdAsync(Guid id) + public async Task GetByIdAsync(Guid id) { - return await _collection.Find(o => o.Id == id).FirstOrDefaultAsync(); + var order = await _collection.Find(o => o.Id == id).FirstOrDefaultAsync(); + + return mapper.Map(order); } - public async Task InsertAsync(OrderReadModel order) + public async Task> GetPaginatedOrdersAsync(int page, int pageSize, string? status, + DateTime? fromDate, DateTime? toDate, + CancellationToken cancellationToken) { + var filterBuilder = Builders.Filter; + var filters = new List>(); + + if (!string.IsNullOrWhiteSpace(status) && Enum.TryParse(status, out var parsedStatus)) + { + filters.Add(filterBuilder.Eq(o => o.Status, parsedStatus)); + } + + if (fromDate.HasValue) + { + filters.Add(filterBuilder.Gte(o => o.CreatedAt, fromDate.Value)); + } + + if (toDate.HasValue) + { + filters.Add(filterBuilder.Lte(o => o.CreatedAt, toDate.Value)); + } + + var filter = filters.Any() ? filterBuilder.And(filters) : FilterDefinition.Empty; + + var totalCount = await _collection.CountDocumentsAsync(filter, cancellationToken: cancellationToken); + + var orders = await _collection.Find(filter) + .SortByDescending(o => o.CreatedAt) + .Skip((page - 1) * pageSize) + .Limit(pageSize) + .ToListAsync(cancellationToken); + + var dtos = mapper.Map>(orders); + + return new PaginatedResult(dtos, totalCount, page, pageSize); + } + + public async Task InsertAsync(OrderDto orderDto) + { + var order = mapper.Map(orderDto); + await _collection.InsertOneAsync(order); } } diff --git a/OrderTrackingApp.sln b/OrderTrackingApp.sln index 008bed0..1afc192 100644 --- a/OrderTrackingApp.sln +++ b/OrderTrackingApp.sln @@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Persistenc EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.MigrationRunner", "OrderTrackingApp.MigrationRunner\OrderTrackingApp.MigrationRunner.csproj", "{C9DE7296-2132-490F-97DE-96FF65C7A5BB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Application.Contracts", "OrderTrackingApp.Application.Contracts\OrderTrackingApp.Application.Contracts.csproj", "{E9BB177D-D125-4D7F-A599-126DADEDCDD1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,6 +86,10 @@ Global {C9DE7296-2132-490F-97DE-96FF65C7A5BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9DE7296-2132-490F-97DE-96FF65C7A5BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {C9DE7296-2132-490F-97DE-96FF65C7A5BB}.Release|Any CPU.Build.0 = Release|Any CPU + {E9BB177D-D125-4D7F-A599-126DADEDCDD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9BB177D-D125-4D7F-A599-126DADEDCDD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9BB177D-D125-4D7F-A599-126DADEDCDD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9BB177D-D125-4D7F-A599-126DADEDCDD1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -100,6 +106,7 @@ Global {43A85AAD-5188-4103-BEF6-F88971B638F2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {77A97D9B-3156-47EE-91A1-8D6F0D9BABE3} = {D901624D-8396-4393-B3AE-7D8405C89D69} {C9DE7296-2132-490F-97DE-96FF65C7A5BB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {E9BB177D-D125-4D7F-A599-126DADEDCDD1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECFA56EC-C322-4A64-8936-E930A2EEBE28}