From f9c3a71552143684b94c14f9d4a2803db429748d Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Fri, 11 Jul 2025 12:32:49 +1000 Subject: [PATCH 01/17] First draft --- .../Controllers/WeatherForecastController.cs | 33 ++++++++++++++ .../OrderTrackingApp.Api.csproj | 21 +++++++++ .../OrderTrackingApp.Api.http | 6 +++ OrderTrackingApp.Api/Program.cs | 31 +++++++++++++ .../Properties/launchSettings.json | 41 ++++++++++++++++++ OrderTrackingApp.Api/WeatherForecast.cs | 13 ++++++ .../appsettings.Development.json | 0 OrderTrackingApp.Api/appsettings.json | 14 ++++++ .../Commands/Orders/CreateOrderCommand.cs | 15 +++++++ .../Orders/CreateOrderCommandHandler.cs | 41 ++++++++++++++++++ OrderTrackingApp.Application/DTOs/OrderDto.cs | 20 +++++++++ .../Interfaces/IOrderWriteRepository.cs | 18 ++++++++ .../OrderTrackingApp.Application.csproj | 17 ++++++++ .../OrderTrackingApp.Blazor.Client.csproj | 0 .../Pages/Counter.razor | 0 .../Program.cs | 0 .../_Imports.razor | 4 +- .../wwwroot}/appsettings.Development.json | 0 .../wwwroot/appsettings.json | 0 .../Components/App.razor | 0 .../Components/Layout/MainLayout.razor | 0 .../Components/Layout/MainLayout.razor.css | 0 .../Components/Layout/NavMenu.razor | 0 .../Components/Layout/NavMenu.razor.css | 0 .../Components/Pages/Error.razor | 0 .../Components/Pages/Home.razor | 0 .../Components/Pages/Weather.razor | 0 .../Components/Routes.razor | 0 .../Components}/_Imports.razor | 2 +- .../OrderTrackingApp.Blazor.Server.csproj | 5 ++- .../Program.cs | 5 +-- .../Properties/launchSettings.json | 0 .../appsettings.Development.json | 8 ++++ .../appsettings.json | 0 .../wwwroot/app.css | 0 .../wwwroot/bootstrap/bootstrap.min.css | 0 .../wwwroot/bootstrap/bootstrap.min.css.map | 0 .../wwwroot/favicon.png | Bin OrderTrackingApp.Consumer/Class1.cs | 7 +++ .../OrderTrackingApp.Consumer.csproj | 9 ++++ OrderTrackingApp.Domain/Entities/Order.cs | 19 ++++++++ .../Entities/OrderStatus.cs | 17 ++++++++ .../OrderTrackingApp.Domain.csproj | 9 ++++ OrderTrackingApp.Infrastructure/Class1.cs | 7 +++ .../OrderTrackingApp.Infrastructure.csproj | 9 ++++ OrderTrackingApp.Persistence/AppDbContext.cs | 28 ++++++++++++ .../PersistenceServiceRegistration.cs | 26 +++++++++++ .../OrderTrackingApp.Persistence.csproj | 20 +++++++++ .../Repositories/OrderWriteRepository.cs | 37 ++++++++++++++++ .../ReadPersistenceServiceRegistration.cs | 22 ++++++++++ .../Interfaces/IOrderReadRepository.cs | 16 +++++++ .../MongoDbContext.cs | 22 ++++++++++ .../OrderTrackingApp.ReadPersistence.csproj | 18 ++++++++ .../Repositories/OrderReadRepository.cs | 31 +++++++++++++ OrderTrackingApp.SignalR/Class1.cs | 7 +++ .../OrderTrackingApp.SignalR.csproj | 9 ++++ OrderTrackingApp.sln | 20 ++++----- 57 files changed, 609 insertions(+), 18 deletions(-) create mode 100644 OrderTrackingApp.Api/Controllers/WeatherForecastController.cs create mode 100644 OrderTrackingApp.Api/OrderTrackingApp.Api.csproj create mode 100644 OrderTrackingApp.Api/OrderTrackingApp.Api.http create mode 100644 OrderTrackingApp.Api/Program.cs create mode 100644 OrderTrackingApp.Api/Properties/launchSettings.json create mode 100644 OrderTrackingApp.Api/WeatherForecast.cs rename {OrderTrackingApp.Client/wwwroot => OrderTrackingApp.Api}/appsettings.Development.json (100%) create mode 100644 OrderTrackingApp.Api/appsettings.json create mode 100644 OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs create mode 100644 OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs create mode 100644 OrderTrackingApp.Application/DTOs/OrderDto.cs create mode 100644 OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs create mode 100644 OrderTrackingApp.Application/OrderTrackingApp.Application.csproj rename {OrderTrackingApp.Client => OrderTrackingApp.Blazor.Client}/OrderTrackingApp.Blazor.Client.csproj (100%) rename {OrderTrackingApp.Client => OrderTrackingApp.Blazor.Client}/Pages/Counter.razor (100%) rename {OrderTrackingApp.Client => OrderTrackingApp.Blazor.Client}/Program.cs (100%) rename {OrderTrackingApp/Components => OrderTrackingApp.Blazor.Client}/_Imports.razor (78%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Client/wwwroot}/appsettings.Development.json (100%) rename {OrderTrackingApp.Client => OrderTrackingApp.Blazor.Client}/wwwroot/appsettings.json (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/App.razor (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/Layout/MainLayout.razor (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/Layout/MainLayout.razor.css (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/Layout/NavMenu.razor (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/Layout/NavMenu.razor.css (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/Pages/Error.razor (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/Pages/Home.razor (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/Pages/Weather.razor (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Components/Routes.razor (100%) rename {OrderTrackingApp.Client => OrderTrackingApp.Blazor.Server/Components}/_Imports.razor (91%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/OrderTrackingApp.Blazor.Server.csproj (71%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Program.cs (83%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/Properties/launchSettings.json (100%) create mode 100644 OrderTrackingApp.Blazor.Server/appsettings.Development.json rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/appsettings.json (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/wwwroot/app.css (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/wwwroot/bootstrap/bootstrap.min.css (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/wwwroot/bootstrap/bootstrap.min.css.map (100%) rename {OrderTrackingApp => OrderTrackingApp.Blazor.Server}/wwwroot/favicon.png (100%) create mode 100644 OrderTrackingApp.Consumer/Class1.cs create mode 100644 OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj create mode 100644 OrderTrackingApp.Domain/Entities/Order.cs create mode 100644 OrderTrackingApp.Domain/Entities/OrderStatus.cs create mode 100644 OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj create mode 100644 OrderTrackingApp.Infrastructure/Class1.cs create mode 100644 OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj create mode 100644 OrderTrackingApp.Persistence/AppDbContext.cs create mode 100644 OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs create mode 100644 OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj create mode 100644 OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs create mode 100644 OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs create mode 100644 OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs create mode 100644 OrderTrackingApp.ReadPersistence/MongoDbContext.cs create mode 100644 OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj create mode 100644 OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs create mode 100644 OrderTrackingApp.SignalR/Class1.cs create mode 100644 OrderTrackingApp.SignalR/OrderTrackingApp.SignalR.csproj diff --git a/OrderTrackingApp.Api/Controllers/WeatherForecastController.cs b/OrderTrackingApp.Api/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..13b5931 --- /dev/null +++ b/OrderTrackingApp.Api/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace OrderTrackingApp.Api.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj b/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj new file mode 100644 index 0000000..21a8bb8 --- /dev/null +++ b/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + fd420179-d5b0-44df-b29b-018fbfbdb9f1 + Linux + ..\OrderTrackingApp + + + + + + + + + + + + diff --git a/OrderTrackingApp.Api/OrderTrackingApp.Api.http b/OrderTrackingApp.Api/OrderTrackingApp.Api.http new file mode 100644 index 0000000..994a93b --- /dev/null +++ b/OrderTrackingApp.Api/OrderTrackingApp.Api.http @@ -0,0 +1,6 @@ +@OrderTrackingApp.Api_HostAddress = http://localhost:5077 + +GET {{OrderTrackingApp.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/OrderTrackingApp.Api/Program.cs b/OrderTrackingApp.Api/Program.cs new file mode 100644 index 0000000..6950881 --- /dev/null +++ b/OrderTrackingApp.Api/Program.cs @@ -0,0 +1,31 @@ +using OrderTrackingApp.Persistence.Extensions; +using OrderTrackingApp.ReadPersistence.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddPersistenceServices(builder.Configuration); +builder.Services.AddReadPersistence(builder.Configuration); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/OrderTrackingApp.Api/Properties/launchSettings.json b/OrderTrackingApp.Api/Properties/launchSettings.json new file mode 100644 index 0000000..a5c6743 --- /dev/null +++ b/OrderTrackingApp.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:12188", + "sslPort": 44349 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5077", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7113;http://localhost:5077", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/OrderTrackingApp.Api/WeatherForecast.cs b/OrderTrackingApp.Api/WeatherForecast.cs new file mode 100644 index 0000000..f9643e9 --- /dev/null +++ b/OrderTrackingApp.Api/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace OrderTrackingApp.Api +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/OrderTrackingApp.Client/wwwroot/appsettings.Development.json b/OrderTrackingApp.Api/appsettings.Development.json similarity index 100% rename from OrderTrackingApp.Client/wwwroot/appsettings.Development.json rename to OrderTrackingApp.Api/appsettings.Development.json diff --git a/OrderTrackingApp.Api/appsettings.json b/OrderTrackingApp.Api/appsettings.json new file mode 100644 index 0000000..b558c5c --- /dev/null +++ b/OrderTrackingApp.Api/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "", + "MongoDb": "" + } + +} diff --git a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs new file mode 100644 index 0000000..7fbc789 --- /dev/null +++ b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs @@ -0,0 +1,15 @@ +using MediatR; +using OrderTrackingApp.Application.DTOs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.Application.Commands.Orders +{ + public class CreateOrderCommand : IRequest + { + public string CustomerName { get; set; } = string.Empty; + } +} diff --git a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs new file mode 100644 index 0000000..577d60e --- /dev/null +++ b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs @@ -0,0 +1,41 @@ +using MediatR; +using OrderTrackingApp.Application.DTOs; +using OrderTrackingApp.Application.Interfaces; +using OrderTrackingApp.Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.Application.Commands.Orders +{ + public class CreateOrderCommandHandler : IRequestHandler + { + private readonly IOrderWriteRepository _orderRepository; + + public CreateOrderCommandHandler(IOrderWriteRepository orderRepository) + { + _orderRepository = orderRepository; + } + + public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) + { + var order = new Order + { + CustomerName = request.CustomerName, + Status = OrderStatus.Pending, + }; + + await _orderRepository.AddAsync(order); + + return new OrderDto + { + Id = order.Id, + CustomerName = order.CustomerName, + Status = order.Status, + CreatedAt = order.CreatedAt + }; + } + } +} diff --git a/OrderTrackingApp.Application/DTOs/OrderDto.cs b/OrderTrackingApp.Application/DTOs/OrderDto.cs new file mode 100644 index 0000000..fbb553b --- /dev/null +++ b/OrderTrackingApp.Application/DTOs/OrderDto.cs @@ -0,0 +1,20 @@ +using OrderTrackingApp.Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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/Interfaces/IOrderWriteRepository.cs b/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs new file mode 100644 index 0000000..dd6d4f1 --- /dev/null +++ b/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs @@ -0,0 +1,18 @@ +using OrderTrackingApp.Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.Application.Interfaces +{ + public interface IOrderWriteRepository + { + Task AddAsync(Order order); + + Task UpdateAsync(Order order); + + Task DeleteAsync(Guid id); + } +} diff --git a/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj b/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj new file mode 100644 index 0000000..49e1b55 --- /dev/null +++ b/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/OrderTrackingApp.Client/OrderTrackingApp.Blazor.Client.csproj b/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj similarity index 100% rename from OrderTrackingApp.Client/OrderTrackingApp.Blazor.Client.csproj rename to OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj diff --git a/OrderTrackingApp.Client/Pages/Counter.razor b/OrderTrackingApp.Blazor.Client/Pages/Counter.razor similarity index 100% rename from OrderTrackingApp.Client/Pages/Counter.razor rename to OrderTrackingApp.Blazor.Client/Pages/Counter.razor diff --git a/OrderTrackingApp.Client/Program.cs b/OrderTrackingApp.Blazor.Client/Program.cs similarity index 100% rename from OrderTrackingApp.Client/Program.cs rename to OrderTrackingApp.Blazor.Client/Program.cs diff --git a/OrderTrackingApp/Components/_Imports.razor b/OrderTrackingApp.Blazor.Client/_Imports.razor similarity index 78% rename from OrderTrackingApp/Components/_Imports.razor rename to OrderTrackingApp.Blazor.Client/_Imports.razor index 6b191ef..0b4c4a9 100644 --- a/OrderTrackingApp/Components/_Imports.razor +++ b/OrderTrackingApp.Blazor.Client/_Imports.razor @@ -6,6 +6,4 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop -@using OrderTrackingApp -@using OrderTrackingApp.Client -@using OrderTrackingApp.Components +@using OrderTrackingApp.Blazor.Client diff --git a/OrderTrackingApp/appsettings.Development.json b/OrderTrackingApp.Blazor.Client/wwwroot/appsettings.Development.json similarity index 100% rename from OrderTrackingApp/appsettings.Development.json rename to OrderTrackingApp.Blazor.Client/wwwroot/appsettings.Development.json diff --git a/OrderTrackingApp.Client/wwwroot/appsettings.json b/OrderTrackingApp.Blazor.Client/wwwroot/appsettings.json similarity index 100% rename from OrderTrackingApp.Client/wwwroot/appsettings.json rename to OrderTrackingApp.Blazor.Client/wwwroot/appsettings.json diff --git a/OrderTrackingApp/Components/App.razor b/OrderTrackingApp.Blazor.Server/Components/App.razor similarity index 100% rename from OrderTrackingApp/Components/App.razor rename to OrderTrackingApp.Blazor.Server/Components/App.razor diff --git a/OrderTrackingApp/Components/Layout/MainLayout.razor b/OrderTrackingApp.Blazor.Server/Components/Layout/MainLayout.razor similarity index 100% rename from OrderTrackingApp/Components/Layout/MainLayout.razor rename to OrderTrackingApp.Blazor.Server/Components/Layout/MainLayout.razor diff --git a/OrderTrackingApp/Components/Layout/MainLayout.razor.css b/OrderTrackingApp.Blazor.Server/Components/Layout/MainLayout.razor.css similarity index 100% rename from OrderTrackingApp/Components/Layout/MainLayout.razor.css rename to OrderTrackingApp.Blazor.Server/Components/Layout/MainLayout.razor.css diff --git a/OrderTrackingApp/Components/Layout/NavMenu.razor b/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor similarity index 100% rename from OrderTrackingApp/Components/Layout/NavMenu.razor rename to OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor diff --git a/OrderTrackingApp/Components/Layout/NavMenu.razor.css b/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor.css similarity index 100% rename from OrderTrackingApp/Components/Layout/NavMenu.razor.css rename to OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor.css diff --git a/OrderTrackingApp/Components/Pages/Error.razor b/OrderTrackingApp.Blazor.Server/Components/Pages/Error.razor similarity index 100% rename from OrderTrackingApp/Components/Pages/Error.razor rename to OrderTrackingApp.Blazor.Server/Components/Pages/Error.razor diff --git a/OrderTrackingApp/Components/Pages/Home.razor b/OrderTrackingApp.Blazor.Server/Components/Pages/Home.razor similarity index 100% rename from OrderTrackingApp/Components/Pages/Home.razor rename to OrderTrackingApp.Blazor.Server/Components/Pages/Home.razor diff --git a/OrderTrackingApp/Components/Pages/Weather.razor b/OrderTrackingApp.Blazor.Server/Components/Pages/Weather.razor similarity index 100% rename from OrderTrackingApp/Components/Pages/Weather.razor rename to OrderTrackingApp.Blazor.Server/Components/Pages/Weather.razor diff --git a/OrderTrackingApp/Components/Routes.razor b/OrderTrackingApp.Blazor.Server/Components/Routes.razor similarity index 100% rename from OrderTrackingApp/Components/Routes.razor rename to OrderTrackingApp.Blazor.Server/Components/Routes.razor diff --git a/OrderTrackingApp.Client/_Imports.razor b/OrderTrackingApp.Blazor.Server/Components/_Imports.razor similarity index 91% rename from OrderTrackingApp.Client/_Imports.razor rename to OrderTrackingApp.Blazor.Server/Components/_Imports.razor index 425c382..903f847 100644 --- a/OrderTrackingApp.Client/_Imports.razor +++ b/OrderTrackingApp.Blazor.Server/Components/_Imports.razor @@ -6,4 +6,4 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop -@using OrderTrackingApp.Client +@using OrderTrackingApp diff --git a/OrderTrackingApp/OrderTrackingApp.Blazor.Server.csproj b/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj similarity index 71% rename from OrderTrackingApp/OrderTrackingApp.Blazor.Server.csproj rename to OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj index 8eac3cb..a91bf87 100644 --- a/OrderTrackingApp/OrderTrackingApp.Blazor.Server.csproj +++ b/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj @@ -7,8 +7,11 @@ - + + + + diff --git a/OrderTrackingApp/Program.cs b/OrderTrackingApp.Blazor.Server/Program.cs similarity index 83% rename from OrderTrackingApp/Program.cs rename to OrderTrackingApp.Blazor.Server/Program.cs index 075c206..d784bf9 100644 --- a/OrderTrackingApp/Program.cs +++ b/OrderTrackingApp.Blazor.Server/Program.cs @@ -1,5 +1,4 @@ -using OrderTrackingApp.Client.Pages; -using OrderTrackingApp.Components; +using OrderTrackingApp.Blazor.Server.Components; var builder = WebApplication.CreateBuilder(args); @@ -30,6 +29,6 @@ app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() - .AddAdditionalAssemblies(typeof(OrderTrackingApp.Client._Imports).Assembly); + .AddAdditionalAssemblies(typeof(OrderTrackingApp.Blazor.Client._Imports).Assembly); app.Run(); diff --git a/OrderTrackingApp/Properties/launchSettings.json b/OrderTrackingApp.Blazor.Server/Properties/launchSettings.json similarity index 100% rename from OrderTrackingApp/Properties/launchSettings.json rename to OrderTrackingApp.Blazor.Server/Properties/launchSettings.json diff --git a/OrderTrackingApp.Blazor.Server/appsettings.Development.json b/OrderTrackingApp.Blazor.Server/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/OrderTrackingApp.Blazor.Server/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/OrderTrackingApp/appsettings.json b/OrderTrackingApp.Blazor.Server/appsettings.json similarity index 100% rename from OrderTrackingApp/appsettings.json rename to OrderTrackingApp.Blazor.Server/appsettings.json diff --git a/OrderTrackingApp/wwwroot/app.css b/OrderTrackingApp.Blazor.Server/wwwroot/app.css similarity index 100% rename from OrderTrackingApp/wwwroot/app.css rename to OrderTrackingApp.Blazor.Server/wwwroot/app.css diff --git a/OrderTrackingApp/wwwroot/bootstrap/bootstrap.min.css b/OrderTrackingApp.Blazor.Server/wwwroot/bootstrap/bootstrap.min.css similarity index 100% rename from OrderTrackingApp/wwwroot/bootstrap/bootstrap.min.css rename to OrderTrackingApp.Blazor.Server/wwwroot/bootstrap/bootstrap.min.css diff --git a/OrderTrackingApp/wwwroot/bootstrap/bootstrap.min.css.map b/OrderTrackingApp.Blazor.Server/wwwroot/bootstrap/bootstrap.min.css.map similarity index 100% rename from OrderTrackingApp/wwwroot/bootstrap/bootstrap.min.css.map rename to OrderTrackingApp.Blazor.Server/wwwroot/bootstrap/bootstrap.min.css.map diff --git a/OrderTrackingApp/wwwroot/favicon.png b/OrderTrackingApp.Blazor.Server/wwwroot/favicon.png similarity index 100% rename from OrderTrackingApp/wwwroot/favicon.png rename to OrderTrackingApp.Blazor.Server/wwwroot/favicon.png diff --git a/OrderTrackingApp.Consumer/Class1.cs b/OrderTrackingApp.Consumer/Class1.cs new file mode 100644 index 0000000..b5a4e15 --- /dev/null +++ b/OrderTrackingApp.Consumer/Class1.cs @@ -0,0 +1,7 @@ +namespace OrderTrackingApp.Consumer +{ + public class Class1 + { + + } +} diff --git a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/OrderTrackingApp.Domain/Entities/Order.cs b/OrderTrackingApp.Domain/Entities/Order.cs new file mode 100644 index 0000000..c7861a4 --- /dev/null +++ b/OrderTrackingApp.Domain/Entities/Order.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.Domain.Entities +{ + public class Order + { + public Guid Id { get; set; } = Guid.NewGuid(); + + public string CustomerName { get; set; } = string.Empty; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public OrderStatus Status { get; set; } + } +} diff --git a/OrderTrackingApp.Domain/Entities/OrderStatus.cs b/OrderTrackingApp.Domain/Entities/OrderStatus.cs new file mode 100644 index 0000000..3ec3e56 --- /dev/null +++ b/OrderTrackingApp.Domain/Entities/OrderStatus.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.Domain.Entities +{ + public enum OrderStatus + { + Pending = 0, + Processing = 1, + Shipped = 2, + Delivered = 3, + Cancelled = 4 + } +} diff --git a/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj b/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/OrderTrackingApp.Infrastructure/Class1.cs b/OrderTrackingApp.Infrastructure/Class1.cs new file mode 100644 index 0000000..bd16946 --- /dev/null +++ b/OrderTrackingApp.Infrastructure/Class1.cs @@ -0,0 +1,7 @@ +namespace OrderTrackingApp.Infrastructure +{ + public class Class1 + { + + } +} diff --git a/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj b/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/OrderTrackingApp.Persistence/AppDbContext.cs b/OrderTrackingApp.Persistence/AppDbContext.cs new file mode 100644 index 0000000..83c03b1 --- /dev/null +++ b/OrderTrackingApp.Persistence/AppDbContext.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using OrderTrackingApp.Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.Persistence +{ + public class AppDbContext : DbContext + { + public AppDbContext(DbContextOptions options) : base(options) { } + + public DbSet Orders => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.Status) + .HasConversion(); // maps OrderStatus enum to string + }); + } + } +} diff --git a/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs b/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs new file mode 100644 index 0000000..6e221d7 --- /dev/null +++ b/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OrderTrackingApp.Application.Interfaces; +using OrderTrackingApp.Persistence.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.Persistence.Extensions +{ + public static class PersistenceServiceRegistration + { + public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration config) + { + services.AddDbContext(options => + options.UseSqlServer(config.GetConnectionString("DefaultConnection"))); + + services.AddScoped(); + + return services; + } + } +} diff --git a/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj b/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj new file mode 100644 index 0000000..e9d2b4e --- /dev/null +++ b/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs b/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs new file mode 100644 index 0000000..3cb382a --- /dev/null +++ b/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using OrderTrackingApp.Application.Interfaces; +using OrderTrackingApp.Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.Persistence.Repositories +{ + internal class OrderWriteRepository(AppDbContext dbContext) : IOrderWriteRepository + { + public async Task AddAsync(Order order) + { + dbContext.Orders.Add(order); + await dbContext.SaveChangesAsync(); + } + + public async Task DeleteAsync(Guid id) + { + var order = await dbContext.Orders.FirstOrDefaultAsync(x => x.Id == id); + + if(order != null) + { + dbContext.Orders.Remove(order); + await dbContext.SaveChangesAsync(); + } + } + + public async Task UpdateAsync(Order order) + { + dbContext.Orders.Update(order); + await dbContext.SaveChangesAsync(); + } + } +} diff --git a/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs b/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs new file mode 100644 index 0000000..ae1217f --- /dev/null +++ b/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OrderTrackingApp.ReadPersistence.Interfaces; +using OrderTrackingApp.ReadPersistence.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.ReadPersistence.Extensions +{ + public static class ReadPersistenceServiceRegistration + { + public static IServiceCollection AddReadPersistence(this IServiceCollection services, IConfiguration config) + { + services.AddSingleton(); + services.AddScoped(); + return services; + } + } +} diff --git a/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs b/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs new file mode 100644 index 0000000..05cfda5 --- /dev/null +++ b/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs @@ -0,0 +1,16 @@ +using OrderTrackingApp.Application.DTOs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.ReadPersistence.Interfaces +{ + public interface IOrderReadRepository + { + Task GetByIdAsync(Guid id); + + Task> GetAllAsync(); + } +} diff --git a/OrderTrackingApp.ReadPersistence/MongoDbContext.cs b/OrderTrackingApp.ReadPersistence/MongoDbContext.cs new file mode 100644 index 0000000..47dc78d --- /dev/null +++ b/OrderTrackingApp.ReadPersistence/MongoDbContext.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Configuration; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.ReadPersistence +{ + internal class MongoDbContext + { + public IMongoDatabase Database { get; } + + public MongoDbContext(IConfiguration config) + { + var connectionString = config.GetConnectionString("MongoDb"); + var mongoClient = new MongoClient(connectionString); + Database = mongoClient.GetDatabase("OrderReadDb"); + } + } +} diff --git a/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj b/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj new file mode 100644 index 0000000..70bed8b --- /dev/null +++ b/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs b/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs new file mode 100644 index 0000000..ea18d54 --- /dev/null +++ b/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs @@ -0,0 +1,31 @@ +using MongoDB.Driver; +using OrderTrackingApp.Application.DTOs; +using OrderTrackingApp.ReadPersistence.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OrderTrackingApp.ReadPersistence.Repositories +{ + internal class OrderReadRepository : IOrderReadRepository + { + private readonly IMongoCollection _collection; + + public OrderReadRepository(MongoDbContext context) + { + _collection = context.Database.GetCollection("Orders"); + } + + public async Task> GetAllAsync() + { + return await _collection.Find(_ => true).ToListAsync(); + } + + public async Task GetByIdAsync(Guid id) + { + return await _collection.Find(o => o.Id == id).FirstOrDefaultAsync(); + } + } +} diff --git a/OrderTrackingApp.SignalR/Class1.cs b/OrderTrackingApp.SignalR/Class1.cs new file mode 100644 index 0000000..4247796 --- /dev/null +++ b/OrderTrackingApp.SignalR/Class1.cs @@ -0,0 +1,7 @@ +namespace OrderTrackingApp.SignalR +{ + public class Class1 + { + + } +} diff --git a/OrderTrackingApp.SignalR/OrderTrackingApp.SignalR.csproj b/OrderTrackingApp.SignalR/OrderTrackingApp.SignalR.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/OrderTrackingApp.SignalR/OrderTrackingApp.SignalR.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/OrderTrackingApp.sln b/OrderTrackingApp.sln index 3c65601..3da0abf 100644 --- a/OrderTrackingApp.sln +++ b/OrderTrackingApp.sln @@ -3,29 +3,29 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36203.30 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Server", "OrderTrackingApp\OrderTrackingApp.Blazor.Server.csproj", "{C0F58571-B42E-4301-BD65-F3E939EA37AA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Server", "OrderTrackingApp.Blazor.Server\OrderTrackingApp.Blazor.Server.csproj", "{C0F58571-B42E-4301-BD65-F3E939EA37AA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Client", "OrderTrackingApp.Client\OrderTrackingApp.Blazor.Client.csproj", "{F4760EDE-BA06-4095-B2B5-6221E2EAC48F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Client", "OrderTrackingApp.Blazor.Client\OrderTrackingApp.Blazor.Client.csproj", "{F4760EDE-BA06-4095-B2B5-6221E2EAC48F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D901624D-8396-4393-B3AE-7D8405C89D69}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Api", "..\OrderTrackingApp.Api\OrderTrackingApp.Api.csproj", "{4957811C-F9FB-4259-9E1B-7F7275E75DDB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Api", "OrderTrackingApp.Api\OrderTrackingApp.Api.csproj", "{4957811C-F9FB-4259-9E1B-7F7275E75DDB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.SignalR", "..\OrderTrackingApp.SignalR\OrderTrackingApp.SignalR.csproj", "{8D2C3C59-0A56-4DFD-ABDD-D2C7521C351D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.SignalR", "OrderTrackingApp.SignalR\OrderTrackingApp.SignalR.csproj", "{8D2C3C59-0A56-4DFD-ABDD-D2C7521C351D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Application", "..\OrderTrackingApp.Application\OrderTrackingApp.Application.csproj", "{059A6F30-A0CC-4371-853A-50C76C01FEEE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Application", "OrderTrackingApp.Application\OrderTrackingApp.Application.csproj", "{059A6F30-A0CC-4371-853A-50C76C01FEEE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Domain", "..\OrderTrackingApp.Domain\OrderTrackingApp.Domain.csproj", "{FE84DD55-5B8A-4629-85FC-5E403D1EFADB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Domain", "OrderTrackingApp.Domain\OrderTrackingApp.Domain.csproj", "{FE84DD55-5B8A-4629-85FC-5E403D1EFADB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Infrastructure", "..\OrderTrackingApp.Infrastructure\OrderTrackingApp.Infrastructure.csproj", "{F8E303DF-A47D-41C5-AF05-9673DAF4033A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Infrastructure", "OrderTrackingApp.Infrastructure\OrderTrackingApp.Infrastructure.csproj", "{F8E303DF-A47D-41C5-AF05-9673DAF4033A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Persistence", "..\OrderTrackingApp.Persistence\OrderTrackingApp.Persistence.csproj", "{E0B58B04-C47B-42D8-8AC5-0134B811855C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Persistence", "OrderTrackingApp.Persistence\OrderTrackingApp.Persistence.csproj", "{E0B58B04-C47B-42D8-8AC5-0134B811855C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Consumer", "..\OrderTrackingApp.Consumer\OrderTrackingApp.Consumer.csproj", "{3885E6E2-5813-4669-9295-051E8BEA9B0F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Consumer", "OrderTrackingApp.Consumer\OrderTrackingApp.Consumer.csproj", "{3885E6E2-5813-4669-9295-051E8BEA9B0F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.ReadPersistence", "..\OrderTrackingApp.ReadPersistence\OrderTrackingApp.ReadPersistence.csproj", "{AC2F58F3-C7DF-4908-83B9-98841B365CA9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.ReadPersistence", "OrderTrackingApp.ReadPersistence\OrderTrackingApp.ReadPersistence.csproj", "{AC2F58F3-C7DF-4908-83B9-98841B365CA9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 05a96e19a29287eebad7a4f4e48dedb358bcf7b6 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Fri, 11 Jul 2025 16:54:54 +1000 Subject: [PATCH 02/17] xx --- .env | 3 + OrderTrackingApp.Api/Dockerfile | 41 ++++++++++ .../OrderTrackingApp.Api.http | 6 -- OrderTrackingApp.Blazor.Server/Dockerfile | 25 ++++++ OrderTrackingApp.Consumer/Class1.cs | 7 -- OrderTrackingApp.Consumer/Dockerfile | 27 ++++++ .../OrderTrackingApp.Consumer.csproj | 8 +- OrderTrackingApp.Consumer/Program.cs | 7 ++ .../Properties/launchSettings.json | 12 +++ OrderTrackingApp.Consumer/Worker.cs | 24 ++++++ .../appsettings.Development.json | 8 ++ OrderTrackingApp.Consumer/appsettings.json | 8 ++ OrderTrackingApp.sln | 27 +++--- docker-compose.yml | 82 +++++++++++++++++++ 14 files changed, 255 insertions(+), 30 deletions(-) create mode 100644 .env create mode 100644 OrderTrackingApp.Api/Dockerfile delete mode 100644 OrderTrackingApp.Api/OrderTrackingApp.Api.http create mode 100644 OrderTrackingApp.Blazor.Server/Dockerfile delete mode 100644 OrderTrackingApp.Consumer/Class1.cs create mode 100644 OrderTrackingApp.Consumer/Dockerfile create mode 100644 OrderTrackingApp.Consumer/Program.cs create mode 100644 OrderTrackingApp.Consumer/Properties/launchSettings.json create mode 100644 OrderTrackingApp.Consumer/Worker.cs create mode 100644 OrderTrackingApp.Consumer/appsettings.Development.json create mode 100644 OrderTrackingApp.Consumer/appsettings.json create mode 100644 docker-compose.yml diff --git a/.env b/.env new file mode 100644 index 0000000..5959827 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +SA_PASSWORD=YourStrong!Passw0rd +MONGO_INITDB_ROOT_USERNAME=mongoUser +MONGO_INITDB_ROOT_PASSWORD=mongoPass123 diff --git a/OrderTrackingApp.Api/Dockerfile b/OrderTrackingApp.Api/Dockerfile new file mode 100644 index 0000000..2e0d739 --- /dev/null +++ b/OrderTrackingApp.Api/Dockerfile @@ -0,0 +1,41 @@ +# Stage 1: Base runtime image +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +WORKDIR /app +EXPOSE 8080 + +# Stage 2: Build and publish +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +# Disable SSL verification (for development only) +ENV DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 + +# Copy project files +COPY ["OrderTrackingApp.Api/OrderTrackingApp.Api.csproj", "OrderTrackingApp.Api/"] +COPY ["OrderTrackingApp.Application/OrderTrackingApp.Application.csproj", "OrderTrackingApp.Application/"] +COPY ["OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj", "OrderTrackingApp.Domain/"] +COPY ["OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj", "OrderTrackingApp.Infrastructure/"] +COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] +COPY ["OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj", "OrderTrackingApp.ReadPersistence/"] + +# Restore dependencies +RUN dotnet restore "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" + +# Copy the rest of the source code +COPY . . + +# Build the application +WORKDIR "/src/OrderTrackingApp.Api" +RUN dotnet build "OrderTrackingApp.Api.csproj" -c Release -o /app/build --no-restore + +# Publish the application +FROM build AS publish +RUN dotnet publish "OrderTrackingApp.Api.csproj" -c Release -o /app/publish --no-restore + +# Stage 3: Final runtime image +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +ENTRYPOINT ["dotnet", "OrderTrackingApp.Api.dll"] diff --git a/OrderTrackingApp.Api/OrderTrackingApp.Api.http b/OrderTrackingApp.Api/OrderTrackingApp.Api.http deleted file mode 100644 index 994a93b..0000000 --- a/OrderTrackingApp.Api/OrderTrackingApp.Api.http +++ /dev/null @@ -1,6 +0,0 @@ -@OrderTrackingApp.Api_HostAddress = http://localhost:5077 - -GET {{OrderTrackingApp.Api_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/OrderTrackingApp.Blazor.Server/Dockerfile b/OrderTrackingApp.Blazor.Server/Dockerfile new file mode 100644 index 0000000..85cde2b --- /dev/null +++ b/OrderTrackingApp.Blazor.Server/Dockerfile @@ -0,0 +1,25 @@ +# Stage 1: Build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +COPY ../OrderTrackingApp.sln ./ + +COPY ../OrderTrackingApp.Blazor.Server/*.csproj ./OrderTrackingApp.Blazor.Server/ +COPY ../OrderTrackingApp.Blazor.Client/*.csproj ./OrderTrackingApp.Blazor.Client/ + +RUN dotnet restore + +COPY ../ ./ + +WORKDIR /src/OrderTrackingApp.Blazor.Server +RUN dotnet publish -c Release -o /out + +# Stage 2: Runtime +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +WORKDIR /app +COPY --from=build /out . + +ENV ASPNETCORE_URLS=http://+:80 +EXPOSE 80 + +ENTRYPOINT ["dotnet", "OrderTrackingApp.Blazor.Server.dll"] diff --git a/OrderTrackingApp.Consumer/Class1.cs b/OrderTrackingApp.Consumer/Class1.cs deleted file mode 100644 index b5a4e15..0000000 --- a/OrderTrackingApp.Consumer/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace OrderTrackingApp.Consumer -{ - public class Class1 - { - - } -} diff --git a/OrderTrackingApp.Consumer/Dockerfile b/OrderTrackingApp.Consumer/Dockerfile new file mode 100644 index 0000000..5771421 --- /dev/null +++ b/OrderTrackingApp.Consumer/Dockerfile @@ -0,0 +1,27 @@ +# Stage 1: Build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +COPY ../OrderTrackingApp.Consumer/*.csproj ./OrderTrackingApp.Consumer/ +COPY ../OrderTrackingApp.Application/*.csproj ./OrderTrackingApp.Application/ +COPY ../OrderTrackingApp.Domain/*.csproj ./OrderTrackingApp.Domain/ +COPY ../OrderTrackingApp.Infrastructure/*.csproj ./OrderTrackingApp.Infrastructure/ +COPY ../OrderTrackingApp.Persistence/*.csproj ./OrderTrackingApp.Persistence/ +COPY ../OrderTrackingApp.ReadPersistence/*.csproj ./OrderTrackingApp.ReadPersistence/ + +RUN dotnet restore ./OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj + +# Copy source +COPY ../ ./ + +# Build consumer +WORKDIR /src/OrderTrackingApp.Consumer +RUN dotnet publish -c Release -o /app + +# Stage 2: Runtime +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +WORKDIR /app +COPY --from=build /app . + +ENV DOTNET_RUNNING_IN_CONTAINER=true +ENTRYPOINT ["dotnet", "OrderTrackingApp.Consumer.dll"] diff --git a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj index fa71b7a..0fdd151 100644 --- a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj +++ b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj @@ -1,9 +1,13 @@ - + net8.0 - enable enable + enable + dotnet-OrderTrackingApp.Consumer-4c972be3-521f-42ce-87d8-815ba1ccddd7 + + + diff --git a/OrderTrackingApp.Consumer/Program.cs b/OrderTrackingApp.Consumer/Program.cs new file mode 100644 index 0000000..d13263b --- /dev/null +++ b/OrderTrackingApp.Consumer/Program.cs @@ -0,0 +1,7 @@ +using OrderTrackingApp.Consumer; + +var builder = Host.CreateApplicationBuilder(args); +builder.Services.AddHostedService(); + +var host = builder.Build(); +host.Run(); diff --git a/OrderTrackingApp.Consumer/Properties/launchSettings.json b/OrderTrackingApp.Consumer/Properties/launchSettings.json new file mode 100644 index 0000000..df9bcc3 --- /dev/null +++ b/OrderTrackingApp.Consumer/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "OrderTrackingApp.Consumer": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/OrderTrackingApp.Consumer/Worker.cs b/OrderTrackingApp.Consumer/Worker.cs new file mode 100644 index 0000000..4f9060e --- /dev/null +++ b/OrderTrackingApp.Consumer/Worker.cs @@ -0,0 +1,24 @@ +namespace OrderTrackingApp.Consumer +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + if (_logger.IsEnabled(LogLevel.Information)) + { + _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + } + await Task.Delay(1000, stoppingToken); + } + } + } +} diff --git a/OrderTrackingApp.Consumer/appsettings.Development.json b/OrderTrackingApp.Consumer/appsettings.Development.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/OrderTrackingApp.Consumer/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/OrderTrackingApp.Consumer/appsettings.json b/OrderTrackingApp.Consumer/appsettings.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/OrderTrackingApp.Consumer/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/OrderTrackingApp.sln b/OrderTrackingApp.sln index 3da0abf..c0e0ab9 100644 --- a/OrderTrackingApp.sln +++ b/OrderTrackingApp.sln @@ -1,20 +1,22 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.14.36203.30 d17.14 +VisualStudioVersion = 17.14.36203.30 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Server", "OrderTrackingApp.Blazor.Server\OrderTrackingApp.Blazor.Server.csproj", "{C0F58571-B42E-4301-BD65-F3E939EA37AA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Client", "OrderTrackingApp.Blazor.Client\OrderTrackingApp.Blazor.Client.csproj", "{F4760EDE-BA06-4095-B2B5-6221E2EAC48F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + .env = .env + docker-compose.yml = docker-compose.yml + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D901624D-8396-4393-B3AE-7D8405C89D69}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Api", "OrderTrackingApp.Api\OrderTrackingApp.Api.csproj", "{4957811C-F9FB-4259-9E1B-7F7275E75DDB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.SignalR", "OrderTrackingApp.SignalR\OrderTrackingApp.SignalR.csproj", "{8D2C3C59-0A56-4DFD-ABDD-D2C7521C351D}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Application", "OrderTrackingApp.Application\OrderTrackingApp.Application.csproj", "{059A6F30-A0CC-4371-853A-50C76C01FEEE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Domain", "OrderTrackingApp.Domain\OrderTrackingApp.Domain.csproj", "{FE84DD55-5B8A-4629-85FC-5E403D1EFADB}" @@ -23,10 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Infrastruc EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Persistence", "OrderTrackingApp.Persistence\OrderTrackingApp.Persistence.csproj", "{E0B58B04-C47B-42D8-8AC5-0134B811855C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Consumer", "OrderTrackingApp.Consumer\OrderTrackingApp.Consumer.csproj", "{3885E6E2-5813-4669-9295-051E8BEA9B0F}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.ReadPersistence", "OrderTrackingApp.ReadPersistence\OrderTrackingApp.ReadPersistence.csproj", "{AC2F58F3-C7DF-4908-83B9-98841B365CA9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Consumer", "OrderTrackingApp.Consumer\OrderTrackingApp.Consumer.csproj", "{773AD28D-63B8-4A78-90D0-A5282F3BAFDD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,10 +47,6 @@ Global {4957811C-F9FB-4259-9E1B-7F7275E75DDB}.Debug|Any CPU.Build.0 = Debug|Any CPU {4957811C-F9FB-4259-9E1B-7F7275E75DDB}.Release|Any CPU.ActiveCfg = Release|Any CPU {4957811C-F9FB-4259-9E1B-7F7275E75DDB}.Release|Any CPU.Build.0 = Release|Any CPU - {8D2C3C59-0A56-4DFD-ABDD-D2C7521C351D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D2C3C59-0A56-4DFD-ABDD-D2C7521C351D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D2C3C59-0A56-4DFD-ABDD-D2C7521C351D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D2C3C59-0A56-4DFD-ABDD-D2C7521C351D}.Release|Any CPU.Build.0 = Release|Any CPU {059A6F30-A0CC-4371-853A-50C76C01FEEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {059A6F30-A0CC-4371-853A-50C76C01FEEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {059A6F30-A0CC-4371-853A-50C76C01FEEE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -65,14 +63,14 @@ Global {E0B58B04-C47B-42D8-8AC5-0134B811855C}.Debug|Any CPU.Build.0 = Debug|Any CPU {E0B58B04-C47B-42D8-8AC5-0134B811855C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0B58B04-C47B-42D8-8AC5-0134B811855C}.Release|Any CPU.Build.0 = Release|Any CPU - {3885E6E2-5813-4669-9295-051E8BEA9B0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3885E6E2-5813-4669-9295-051E8BEA9B0F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3885E6E2-5813-4669-9295-051E8BEA9B0F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3885E6E2-5813-4669-9295-051E8BEA9B0F}.Release|Any CPU.Build.0 = Release|Any CPU {AC2F58F3-C7DF-4908-83B9-98841B365CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AC2F58F3-C7DF-4908-83B9-98841B365CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC2F58F3-C7DF-4908-83B9-98841B365CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC2F58F3-C7DF-4908-83B9-98841B365CA9}.Release|Any CPU.Build.0 = Release|Any CPU + {773AD28D-63B8-4A78-90D0-A5282F3BAFDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {773AD28D-63B8-4A78-90D0-A5282F3BAFDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {773AD28D-63B8-4A78-90D0-A5282F3BAFDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {773AD28D-63B8-4A78-90D0-A5282F3BAFDD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -81,13 +79,12 @@ Global {C0F58571-B42E-4301-BD65-F3E939EA37AA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {F4760EDE-BA06-4095-B2B5-6221E2EAC48F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {4957811C-F9FB-4259-9E1B-7F7275E75DDB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {8D2C3C59-0A56-4DFD-ABDD-D2C7521C351D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {059A6F30-A0CC-4371-853A-50C76C01FEEE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {FE84DD55-5B8A-4629-85FC-5E403D1EFADB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {F8E303DF-A47D-41C5-AF05-9673DAF4033A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {E0B58B04-C47B-42D8-8AC5-0134B811855C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {3885E6E2-5813-4669-9295-051E8BEA9B0F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {AC2F58F3-C7DF-4908-83B9-98841B365CA9} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {773AD28D-63B8-4A78-90D0-A5282F3BAFDD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECFA56EC-C322-4A64-8936-E930A2EEBE28} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5a7a676 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,82 @@ +version: '3.8' + +services: + + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + container_name: sqlserver + ports: + - "1433:1433" + environment: + SA_PASSWORD: ${SA_PASSWORD} + ACCEPT_EULA: "Y" + volumes: + - sqlvolume:/var/opt/mssql + + mongodb: + image: mongo + container_name: mongodb + ports: + - "27017:27017" + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} + volumes: + - mongodata:/data/db + + rabbitmq: + image: rabbitmq:4-management + container_name: rabbitmq + ports: + - "5672:5672" + - "15672:15672" + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + + api: + build: + context: . + dockerfile: OrderTrackingApp.Api/Dockerfile + container_name: api + ports: + - "5000:80" + depends_on: + - sqlserver + - rabbitmq + - mongodb + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ConnectionStrings__DefaultConnection=Server=sqlserver,1433;Database=OrderDb;User=sa;Password=${SA_PASSWORD}; + - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongodb:27017 + + consumer: + build: + context: . + dockerfile: OrderTrackingApp.Consumer/Dockerfile + container_name: consumer + depends_on: + - sqlserver + - rabbitmq + - mongodb + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ConnectionStrings__DefaultConnection=Server=sqlserver,1433;Database=OrderDb;User=sa;Password=${SA_PASSWORD}; + - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongodb:27017 + + blazor-server: + build: + context: . + dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile + container_name: blazor-server + ports: + - "7000:80" + depends_on: + - api + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ApiBaseUrl=http://api:80 + +volumes: + sqlvolume: + mongodata: From 4345ca354e3e695c457f2d91a2a2ef0910a0bdc6 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Sun, 13 Jul 2025 19:15:08 +1000 Subject: [PATCH 03/17] fix dockerfile for order tracking api --- OrderTrackingApp.Api/Dockerfile | 21 +++--------- .../Commands/Orders/CreateOrderCommand.cs | 5 --- .../Orders/CreateOrderCommandHandler.cs | 5 --- OrderTrackingApp.Application/DTOs/OrderDto.cs | 5 --- .../Interfaces/IOrderWriteRepository.cs | 5 --- OrderTrackingApp.Blazor.Server/Dockerfile | 30 ++++++++--------- OrderTrackingApp.Consumer/Dockerfile | 33 ++++++++++--------- OrderTrackingApp.Domain/Entities/Order.cs | 8 +---- .../Entities/OrderStatus.cs | 8 +---- OrderTrackingApp.Infrastructure/Class1.cs | 7 ---- OrderTrackingApp.Persistence/AppDbContext.cs | 5 --- .../PersistenceServiceRegistration.cs | 5 --- .../Repositories/OrderWriteRepository.cs | 5 --- .../ReadPersistenceServiceRegistration.cs | 5 --- .../Interfaces/IOrderReadRepository.cs | 5 --- .../MongoDbContext.cs | 5 --- .../Repositories/OrderReadRepository.cs | 5 --- 17 files changed, 38 insertions(+), 124 deletions(-) delete mode 100644 OrderTrackingApp.Infrastructure/Class1.cs diff --git a/OrderTrackingApp.Api/Dockerfile b/OrderTrackingApp.Api/Dockerfile index 2e0d739..415d5ab 100644 --- a/OrderTrackingApp.Api/Dockerfile +++ b/OrderTrackingApp.Api/Dockerfile @@ -1,17 +1,10 @@ -# Stage 1: Base runtime image FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 8080 -# Stage 2: Build and publish FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src -# Disable SSL verification (for development only) -ENV DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 -ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 - -# Copy project files COPY ["OrderTrackingApp.Api/OrderTrackingApp.Api.csproj", "OrderTrackingApp.Api/"] COPY ["OrderTrackingApp.Application/OrderTrackingApp.Application.csproj", "OrderTrackingApp.Application/"] COPY ["OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj", "OrderTrackingApp.Domain/"] @@ -19,23 +12,17 @@ COPY ["OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj", COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] COPY ["OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj", "OrderTrackingApp.ReadPersistence/"] -# Restore dependencies -RUN dotnet restore "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" - -# Copy the rest of the source code COPY . . -# Build the application -WORKDIR "/src/OrderTrackingApp.Api" -RUN dotnet build "OrderTrackingApp.Api.csproj" -c Release -o /app/build --no-restore +RUN dotnet build "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" -c Release -o /app/build -# Publish the application FROM build AS publish -RUN dotnet publish "OrderTrackingApp.Api.csproj" -c Release -o /app/publish --no-restore +RUN dotnet publish "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" -c Release -o /app/publish -# Stage 3: Final runtime image FROM base AS final WORKDIR /app COPY --from=publish /app/publish . +ENV ASPNETCORE_URLS=http://0.0.0.0:8080 + ENTRYPOINT ["dotnet", "OrderTrackingApp.Api.dll"] diff --git a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs index 7fbc789..dba7b19 100644 --- a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs +++ b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs @@ -1,10 +1,5 @@ using MediatR; using OrderTrackingApp.Application.DTOs; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.Application.Commands.Orders { diff --git a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs index 577d60e..170e8e4 100644 --- a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs +++ b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs @@ -2,11 +2,6 @@ using OrderTrackingApp.Application.DTOs; using OrderTrackingApp.Application.Interfaces; using OrderTrackingApp.Domain.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.Application.Commands.Orders { diff --git a/OrderTrackingApp.Application/DTOs/OrderDto.cs b/OrderTrackingApp.Application/DTOs/OrderDto.cs index fbb553b..558dd16 100644 --- a/OrderTrackingApp.Application/DTOs/OrderDto.cs +++ b/OrderTrackingApp.Application/DTOs/OrderDto.cs @@ -1,9 +1,4 @@ using OrderTrackingApp.Domain.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.Application.DTOs { diff --git a/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs b/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs index dd6d4f1..c31d16a 100644 --- a/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs +++ b/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs @@ -1,9 +1,4 @@ using OrderTrackingApp.Domain.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.Application.Interfaces { diff --git a/OrderTrackingApp.Blazor.Server/Dockerfile b/OrderTrackingApp.Blazor.Server/Dockerfile index 85cde2b..05f689c 100644 --- a/OrderTrackingApp.Blazor.Server/Dockerfile +++ b/OrderTrackingApp.Blazor.Server/Dockerfile @@ -1,25 +1,23 @@ -# Stage 1: Build +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +WORKDIR /app +EXPOSE 8080 + FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src -COPY ../OrderTrackingApp.sln ./ +COPY ["OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj", "OrderTrackingApp.Blazor.Server/"] +COPY ["OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj", "OrderTrackingApp.Blazor.Client/"] -COPY ../OrderTrackingApp.Blazor.Server/*.csproj ./OrderTrackingApp.Blazor.Server/ -COPY ../OrderTrackingApp.Blazor.Client/*.csproj ./OrderTrackingApp.Blazor.Client/ +RUN dotnet restore "OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj" -RUN dotnet restore +COPY . . -COPY ../ ./ +WORKDIR "/src/OrderTrackingApp.Blazor.Server" +RUN dotnet publish -c Release -o /app/publish --no-restore -WORKDIR /src/OrderTrackingApp.Blazor.Server -RUN dotnet publish -c Release -o /out - -# Stage 2: Runtime -FROM mcr.microsoft.com/dotnet/aspnet:8.0 +FROM base AS final WORKDIR /app -COPY --from=build /out . - -ENV ASPNETCORE_URLS=http://+:80 -EXPOSE 80 +COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "OrderTrackingApp.Blazor.Server.dll"] +# Start the app +ENTRYPOINT ["dotnet", "OrderTrackingApp.Blazor.Server.dll"] \ No newline at end of file diff --git a/OrderTrackingApp.Consumer/Dockerfile b/OrderTrackingApp.Consumer/Dockerfile index 5771421..20db7d2 100644 --- a/OrderTrackingApp.Consumer/Dockerfile +++ b/OrderTrackingApp.Consumer/Dockerfile @@ -1,27 +1,30 @@ -# Stage 1: Build +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base + FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src -COPY ../OrderTrackingApp.Consumer/*.csproj ./OrderTrackingApp.Consumer/ -COPY ../OrderTrackingApp.Application/*.csproj ./OrderTrackingApp.Application/ -COPY ../OrderTrackingApp.Domain/*.csproj ./OrderTrackingApp.Domain/ -COPY ../OrderTrackingApp.Infrastructure/*.csproj ./OrderTrackingApp.Infrastructure/ -COPY ../OrderTrackingApp.Persistence/*.csproj ./OrderTrackingApp.Persistence/ -COPY ../OrderTrackingApp.ReadPersistence/*.csproj ./OrderTrackingApp.ReadPersistence/ +COPY ["OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj", "OrderTrackingApp.Consumer/"] +COPY ["OrderTrackingApp.Application/OrderTrackingApp.Application.csproj", "OrderTrackingApp.Application/"] +COPY ["OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj", "OrderTrackingApp.Domain/"] +COPY ["OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj", "OrderTrackingApp.Infrastructure/"] +COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] +COPY ["OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj", "OrderTrackingApp.ReadPersistence/"] -RUN dotnet restore ./OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj +RUN dotnet restore "OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj" -# Copy source -COPY ../ ./ +COPY . . -# Build consumer WORKDIR /src/OrderTrackingApp.Consumer RUN dotnet publish -c Release -o /app -# Stage 2: Runtime -FROM mcr.microsoft.com/dotnet/aspnet:8.0 +WORKDIR "/src/OrderTrackingApp.Consumer" +RUN dotnet build "OrderTrackingApp.Consumer.csproj" -c Release -o /app/build --no-restore + +FROM build AS publish +RUN dotnet publish "OrderTrackingApp.Consumer.csproj" -c Release -o /app/publish --no-restore + +FROM base AS final WORKDIR /app -COPY --from=build /app . +COPY --from=publish /app/publish . -ENV DOTNET_RUNNING_IN_CONTAINER=true ENTRYPOINT ["dotnet", "OrderTrackingApp.Consumer.dll"] diff --git a/OrderTrackingApp.Domain/Entities/Order.cs b/OrderTrackingApp.Domain/Entities/Order.cs index c7861a4..be533df 100644 --- a/OrderTrackingApp.Domain/Entities/Order.cs +++ b/OrderTrackingApp.Domain/Entities/Order.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace OrderTrackingApp.Domain.Entities +namespace OrderTrackingApp.Domain.Entities { public class Order { diff --git a/OrderTrackingApp.Domain/Entities/OrderStatus.cs b/OrderTrackingApp.Domain/Entities/OrderStatus.cs index 3ec3e56..e404730 100644 --- a/OrderTrackingApp.Domain/Entities/OrderStatus.cs +++ b/OrderTrackingApp.Domain/Entities/OrderStatus.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace OrderTrackingApp.Domain.Entities +namespace OrderTrackingApp.Domain.Entities { public enum OrderStatus { diff --git a/OrderTrackingApp.Infrastructure/Class1.cs b/OrderTrackingApp.Infrastructure/Class1.cs deleted file mode 100644 index bd16946..0000000 --- a/OrderTrackingApp.Infrastructure/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace OrderTrackingApp.Infrastructure -{ - public class Class1 - { - - } -} diff --git a/OrderTrackingApp.Persistence/AppDbContext.cs b/OrderTrackingApp.Persistence/AppDbContext.cs index 83c03b1..c825e68 100644 --- a/OrderTrackingApp.Persistence/AppDbContext.cs +++ b/OrderTrackingApp.Persistence/AppDbContext.cs @@ -1,10 +1,5 @@ using Microsoft.EntityFrameworkCore; using OrderTrackingApp.Domain.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.Persistence { diff --git a/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs b/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs index 6e221d7..72bcb90 100644 --- a/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs +++ b/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs @@ -3,11 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using OrderTrackingApp.Application.Interfaces; using OrderTrackingApp.Persistence.Repositories; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.Persistence.Extensions { diff --git a/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs b/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs index 3cb382a..7a89df6 100644 --- a/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs +++ b/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs @@ -1,11 +1,6 @@ using Microsoft.EntityFrameworkCore; using OrderTrackingApp.Application.Interfaces; using OrderTrackingApp.Domain.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.Persistence.Repositories { diff --git a/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs b/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs index ae1217f..f08fe3f 100644 --- a/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs +++ b/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs @@ -2,11 +2,6 @@ using Microsoft.Extensions.DependencyInjection; using OrderTrackingApp.ReadPersistence.Interfaces; using OrderTrackingApp.ReadPersistence.Repositories; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.ReadPersistence.Extensions { diff --git a/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs b/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs index 05cfda5..bac5b4f 100644 --- a/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs +++ b/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs @@ -1,9 +1,4 @@ using OrderTrackingApp.Application.DTOs; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.ReadPersistence.Interfaces { diff --git a/OrderTrackingApp.ReadPersistence/MongoDbContext.cs b/OrderTrackingApp.ReadPersistence/MongoDbContext.cs index 47dc78d..5635d56 100644 --- a/OrderTrackingApp.ReadPersistence/MongoDbContext.cs +++ b/OrderTrackingApp.ReadPersistence/MongoDbContext.cs @@ -1,10 +1,5 @@ using Microsoft.Extensions.Configuration; using MongoDB.Driver; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.ReadPersistence { diff --git a/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs b/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs index ea18d54..9d55de0 100644 --- a/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs +++ b/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs @@ -1,11 +1,6 @@ using MongoDB.Driver; using OrderTrackingApp.Application.DTOs; using OrderTrackingApp.ReadPersistence.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OrderTrackingApp.ReadPersistence.Repositories { From 2d256c3c3ffe597e5072aa4a45da57b37011130f Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Sun, 13 Jul 2025 23:10:46 +1000 Subject: [PATCH 04/17] docker compose support with hot reload - WIP --- .dockerignore | 18 +++++++++++ OrderTrackingApp.Api/dev.Dockerfile | 11 +++++++ .../OrderTrackingApp.Blazor.Client.csproj | 2 +- .../Components/App.razor | 2 +- .../Components/Layout/NavMenu.razor | 2 +- .../Components/_Imports.razor | 3 +- .../OrderTrackingApp.Blazor.Server.csproj | 4 +-- OrderTrackingApp.Blazor.Server/Program.cs | 3 +- .../Properties/launchSettings.json | 8 ++--- OrderTrackingApp.Consumer/Worker.cs | 4 +-- OrderTrackingApp.Consumer/dev.Dockerfile | 11 +++++++ OrderTrackingApp.sln | 30 ++++++++++--------- README.md | 27 ++++++++++++++++- docker-compose.override.yml | 25 ++++++++++++++++ docker-compose.yml | 28 ++++++++--------- 15 files changed, 135 insertions(+), 43 deletions(-) create mode 100644 .dockerignore create mode 100644 OrderTrackingApp.Api/dev.Dockerfile create mode 100644 OrderTrackingApp.Consumer/dev.Dockerfile create mode 100644 docker-compose.override.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8144c5d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +**/bin/ +**/obj/ +**/.vs/ +**/.vscode/ +**/*.user +**/*.suo +**/*.pdb +**/*.db +**/*.log +**/node_modules/ +**/wwwroot/ +Dockerfile* +docker-compose* +.env +.git +.gitignore +README.md +/vsdbg \ No newline at end of file diff --git a/OrderTrackingApp.Api/dev.Dockerfile b/OrderTrackingApp.Api/dev.Dockerfile new file mode 100644 index 0000000..6260527 --- /dev/null +++ b/OrderTrackingApp.Api/dev.Dockerfile @@ -0,0 +1,11 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 + +WORKDIR /app + +# Install Visual Studio Debugger +RUN apt-get update && \ + apt-get install -y curl unzip && \ + curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg + +# Default command is to use dotnet watch +ENTRYPOINT ["dotnet", "watch", "run"] diff --git a/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj b/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj index 81d27a4..9f530b9 100644 --- a/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj +++ b/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj @@ -9,7 +9,7 @@ - + diff --git a/OrderTrackingApp.Blazor.Server/Components/App.razor b/OrderTrackingApp.Blazor.Server/Components/App.razor index 207b995..a009aa6 100644 --- a/OrderTrackingApp.Blazor.Server/Components/App.razor +++ b/OrderTrackingApp.Blazor.Server/Components/App.razor @@ -7,7 +7,7 @@ - + diff --git a/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor b/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor index d3c64c9..d7efbf2 100644 --- a/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor +++ b/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor @@ -1,6 +1,6 @@  diff --git a/OrderTrackingApp.Blazor.Server/Components/_Imports.razor b/OrderTrackingApp.Blazor.Server/Components/_Imports.razor index 903f847..564b32f 100644 --- a/OrderTrackingApp.Blazor.Server/Components/_Imports.razor +++ b/OrderTrackingApp.Blazor.Server/Components/_Imports.razor @@ -6,4 +6,5 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop -@using OrderTrackingApp +@using OrderTrackingApp.Blazor.Server +@using OrderTrackingApp.Blazor.Server.Components diff --git a/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj b/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj index a91bf87..f001903 100644 --- a/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj +++ b/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -7,7 +7,7 @@ - + diff --git a/OrderTrackingApp.Blazor.Server/Program.cs b/OrderTrackingApp.Blazor.Server/Program.cs index d784bf9..06777be 100644 --- a/OrderTrackingApp.Blazor.Server/Program.cs +++ b/OrderTrackingApp.Blazor.Server/Program.cs @@ -1,3 +1,4 @@ +using OrderTrackingApp.Blazor.Server.Client.Pages; using OrderTrackingApp.Blazor.Server.Components; var builder = WebApplication.CreateBuilder(args); @@ -29,6 +30,6 @@ app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() - .AddAdditionalAssemblies(typeof(OrderTrackingApp.Blazor.Client._Imports).Assembly); + .AddAdditionalAssemblies(typeof(OrderTrackingApp.Blazor.Server.Client._Imports).Assembly); app.Run(); diff --git a/OrderTrackingApp.Blazor.Server/Properties/launchSettings.json b/OrderTrackingApp.Blazor.Server/Properties/launchSettings.json index f4d5c4c..46d14d4 100644 --- a/OrderTrackingApp.Blazor.Server/Properties/launchSettings.json +++ b/OrderTrackingApp.Blazor.Server/Properties/launchSettings.json @@ -4,8 +4,8 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:12383", - "sslPort": 44332 + "applicationUrl": "http://localhost:48834", + "sslPort": 44398 } }, "profiles": { @@ -14,7 +14,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5166", + "applicationUrl": "http://localhost:5229", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -24,7 +24,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:7299;http://localhost:5166", + "applicationUrl": "https://localhost:7262;http://localhost:5229", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/OrderTrackingApp.Consumer/Worker.cs b/OrderTrackingApp.Consumer/Worker.cs index 4f9060e..c08d5b4 100644 --- a/OrderTrackingApp.Consumer/Worker.cs +++ b/OrderTrackingApp.Consumer/Worker.cs @@ -15,9 +15,9 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (_logger.IsEnabled(LogLevel.Information)) { - _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + _logger.LogInformation("Worker running at../: {time}", DateTimeOffset.Now); } - await Task.Delay(1000, stoppingToken); + await Task.Delay(60000, stoppingToken); } } } diff --git a/OrderTrackingApp.Consumer/dev.Dockerfile b/OrderTrackingApp.Consumer/dev.Dockerfile new file mode 100644 index 0000000..6260527 --- /dev/null +++ b/OrderTrackingApp.Consumer/dev.Dockerfile @@ -0,0 +1,11 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 + +WORKDIR /app + +# Install Visual Studio Debugger +RUN apt-get update && \ + apt-get install -y curl unzip && \ + curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg + +# Default command is to use dotnet watch +ENTRYPOINT ["dotnet", "watch", "run"] diff --git a/OrderTrackingApp.sln b/OrderTrackingApp.sln index c0e0ab9..1b44c58 100644 --- a/OrderTrackingApp.sln +++ b/OrderTrackingApp.sln @@ -3,14 +3,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36203.30 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Server", "OrderTrackingApp.Blazor.Server\OrderTrackingApp.Blazor.Server.csproj", "{C0F58571-B42E-4301-BD65-F3E939EA37AA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Client", "OrderTrackingApp.Blazor.Client\OrderTrackingApp.Blazor.Client.csproj", "{F4760EDE-BA06-4095-B2B5-6221E2EAC48F}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" ProjectSection(SolutionItems) = preProject .env = .env + docker-compose.override.yml = docker-compose.override.yml docker-compose.yml = docker-compose.yml + README.md = README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D901624D-8396-4393-B3AE-7D8405C89D69}" @@ -29,20 +27,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.ReadPersis EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Consumer", "OrderTrackingApp.Consumer\OrderTrackingApp.Consumer.csproj", "{773AD28D-63B8-4A78-90D0-A5282F3BAFDD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Server", "OrderTrackingApp.Blazor.Server\OrderTrackingApp.Blazor.Server.csproj", "{1FD0A91F-99A3-4D45-AA2B-3081183FEAC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Client", "OrderTrackingApp.Blazor.Client\OrderTrackingApp.Blazor.Client.csproj", "{43A85AAD-5188-4103-BEF6-F88971B638F2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C0F58571-B42E-4301-BD65-F3E939EA37AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0F58571-B42E-4301-BD65-F3E939EA37AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0F58571-B42E-4301-BD65-F3E939EA37AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0F58571-B42E-4301-BD65-F3E939EA37AA}.Release|Any CPU.Build.0 = Release|Any CPU - {F4760EDE-BA06-4095-B2B5-6221E2EAC48F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4760EDE-BA06-4095-B2B5-6221E2EAC48F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4760EDE-BA06-4095-B2B5-6221E2EAC48F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4760EDE-BA06-4095-B2B5-6221E2EAC48F}.Release|Any CPU.Build.0 = Release|Any CPU {4957811C-F9FB-4259-9E1B-7F7275E75DDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4957811C-F9FB-4259-9E1B-7F7275E75DDB}.Debug|Any CPU.Build.0 = Debug|Any CPU {4957811C-F9FB-4259-9E1B-7F7275E75DDB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -71,13 +65,19 @@ Global {773AD28D-63B8-4A78-90D0-A5282F3BAFDD}.Debug|Any CPU.Build.0 = Debug|Any CPU {773AD28D-63B8-4A78-90D0-A5282F3BAFDD}.Release|Any CPU.ActiveCfg = Release|Any CPU {773AD28D-63B8-4A78-90D0-A5282F3BAFDD}.Release|Any CPU.Build.0 = Release|Any CPU + {1FD0A91F-99A3-4D45-AA2B-3081183FEAC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FD0A91F-99A3-4D45-AA2B-3081183FEAC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FD0A91F-99A3-4D45-AA2B-3081183FEAC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FD0A91F-99A3-4D45-AA2B-3081183FEAC3}.Release|Any CPU.Build.0 = Release|Any CPU + {43A85AAD-5188-4103-BEF6-F88971B638F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43A85AAD-5188-4103-BEF6-F88971B638F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43A85AAD-5188-4103-BEF6-F88971B638F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43A85AAD-5188-4103-BEF6-F88971B638F2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {C0F58571-B42E-4301-BD65-F3E939EA37AA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {F4760EDE-BA06-4095-B2B5-6221E2EAC48F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {4957811C-F9FB-4259-9E1B-7F7275E75DDB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {059A6F30-A0CC-4371-853A-50C76C01FEEE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {FE84DD55-5B8A-4629-85FC-5E403D1EFADB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} @@ -85,6 +85,8 @@ Global {E0B58B04-C47B-42D8-8AC5-0134B811855C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {AC2F58F3-C7DF-4908-83B9-98841B365CA9} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {773AD28D-63B8-4A78-90D0-A5282F3BAFDD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {1FD0A91F-99A3-4D45-AA2B-3081183FEAC3} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {43A85AAD-5188-4103-BEF6-F88971B638F2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECFA56EC-C322-4A64-8936-E930A2EEBE28} diff --git a/README.md b/README.md index 0a94371..a7a71fc 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ -# OrderTrackingApp \ No newline at end of file +# 🧱 OrderTrackingApp - Docker Commands + +This section lists the Docker build and run commands for each project in the solution. + +--- + +## 🐳 Docker Commands for Local Development + +### 📦 `OrderTrackingApp.Api` + +```bash +# Build +docker build -f OrderTrackingApp.Api/Dockerfile -t ordertrackingapp-api . + +# Run + docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Api -p 8080:8080 mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run --urls=http://0.0.0.0:8080 + ``` +### 📦 `OrderTrackingApp.Consumer` + +```bash +# Build +docker build -f OrderTrackingApp.Consumer/Dockerfile -t ordertrackingapp-consumer . + +# Run + docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Consumer mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run + ``` \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..d5f69df --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,25 @@ +services: + + api: + build: + context: . + dockerfile: OrderTrackingApp.Api/dev.Dockerfile + command: ["dotnet", "watch", "run"] + volumes: + - .:/app + working_dir: /app/OrderTrackingApp.Api + environment: + - ASPNETCORE_ENVIRONMENT=Development + ports: + - "5000:8080" + + consumer: + build: + context: . + dockerfile: OrderTrackingApp.Consumer/dev.Dockerfile + command: ["dotnet", "watch", "run"] + volumes: + - .:/app + working_dir: /app/OrderTrackingApp.Consumer + environment: + - ASPNETCORE_ENVIRONMENT=Development diff --git a/docker-compose.yml b/docker-compose.yml index 5a7a676..01f9588 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: sqlserver: @@ -40,7 +38,7 @@ services: dockerfile: OrderTrackingApp.Api/Dockerfile container_name: api ports: - - "5000:80" + - "5000:8080" depends_on: - sqlserver - rabbitmq @@ -64,18 +62,18 @@ services: - ConnectionStrings__DefaultConnection=Server=sqlserver,1433;Database=OrderDb;User=sa;Password=${SA_PASSWORD}; - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongodb:27017 - blazor-server: - build: - context: . - dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile - container_name: blazor-server - ports: - - "7000:80" - depends_on: - - api - environment: - - ASPNETCORE_ENVIRONMENT=Production - - ApiBaseUrl=http://api:80 + # blazor-server: + # build: + # context: . + # dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile + # container_name: blazor-server + # ports: + # - "7000:80" + # depends_on: + # - api + # environment: + # - ASPNETCORE_ENVIRONMENT=Production + # - ApiBaseUrl=http://api:80 volumes: sqlvolume: From 21c5cfff090a62a53b167cd9fdce1d9ca430478e Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Mon, 14 Jul 2025 15:01:43 +1000 Subject: [PATCH 05/17] WIP --- OrderTrackingApp.Api/Dockerfile | 12 +++- OrderTrackingApp.Api/dev.Dockerfile | 11 --- OrderTrackingApp.Blazor.Server/Dockerfile | 14 +++- .../OrderTrackingApp.Blazor.Server.csproj | 12 ++++ OrderTrackingApp.Blazor.Server/Program.cs | 4 +- OrderTrackingApp.Consumer/Dockerfile | 14 ++-- OrderTrackingApp.Consumer/dev.Dockerfile | 11 --- OrderTrackingApp.sln | 1 - README.md | 23 ++++++- docker-compose.override.yml | 25 ------- docker-compose.yml | 67 ++++++++++++------- nscacert.crt | 24 +++++++ 12 files changed, 132 insertions(+), 86 deletions(-) delete mode 100644 OrderTrackingApp.Api/dev.Dockerfile delete mode 100644 OrderTrackingApp.Consumer/dev.Dockerfile delete mode 100644 docker-compose.override.yml create mode 100644 nscacert.crt diff --git a/OrderTrackingApp.Api/Dockerfile b/OrderTrackingApp.Api/Dockerfile index 415d5ab..b77026f 100644 --- a/OrderTrackingApp.Api/Dockerfile +++ b/OrderTrackingApp.Api/Dockerfile @@ -12,12 +12,20 @@ COPY ["OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj", COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] COPY ["OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj", "OrderTrackingApp.ReadPersistence/"] +# Copy your root certificate into the container +COPY nscacert.crt /usr/local/share/ca-certificates/nscacert.crt + +# Update the certificate store +RUN update-ca-certificates + +RUN dotnet restore "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" + COPY . . -RUN dotnet build "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" -c Release -o /app/build +RUN dotnet build "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" -c Release -o /app/build --no-restore FROM build AS publish -RUN dotnet publish "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" -c Release -o /app/publish +RUN dotnet publish "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" -c Release -o /app/publish --no-restore FROM base AS final WORKDIR /app diff --git a/OrderTrackingApp.Api/dev.Dockerfile b/OrderTrackingApp.Api/dev.Dockerfile deleted file mode 100644 index 6260527..0000000 --- a/OrderTrackingApp.Api/dev.Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0 - -WORKDIR /app - -# Install Visual Studio Debugger -RUN apt-get update && \ - apt-get install -y curl unzip && \ - curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg - -# Default command is to use dotnet watch -ENTRYPOINT ["dotnet", "watch", "run"] diff --git a/OrderTrackingApp.Blazor.Server/Dockerfile b/OrderTrackingApp.Blazor.Server/Dockerfile index 05f689c..a442a11 100644 --- a/OrderTrackingApp.Blazor.Server/Dockerfile +++ b/OrderTrackingApp.Blazor.Server/Dockerfile @@ -8,16 +8,26 @@ WORKDIR /src COPY ["OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj", "OrderTrackingApp.Blazor.Server/"] COPY ["OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj", "OrderTrackingApp.Blazor.Client/"] +# Copy your root certificate into the container +COPY nscacert.crt /usr/local/share/ca-certificates/nscacert.crt + +# Update the certificate store +RUN update-ca-certificates + RUN dotnet restore "OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj" COPY . . -WORKDIR "/src/OrderTrackingApp.Blazor.Server" -RUN dotnet publish -c Release -o /app/publish --no-restore +RUN dotnet build "OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj" -c Release -o /app/build --no-restore + +FROM build AS publish +RUN dotnet publish "OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj" -c Release -o /app/publish --no-restore FROM base AS final WORKDIR /app COPY --from=publish /app/publish . +ENV ASPNETCORE_URLS=http://0.0.0.0:8080 + # Start the app ENTRYPOINT ["dotnet", "OrderTrackingApp.Blazor.Server.dll"] \ No newline at end of file diff --git a/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj b/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj index f001903..88d4203 100644 --- a/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj +++ b/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj @@ -14,4 +14,16 @@ + + + Always + + + Always + + + Always + + + diff --git a/OrderTrackingApp.Blazor.Server/Program.cs b/OrderTrackingApp.Blazor.Server/Program.cs index 06777be..fd8b203 100644 --- a/OrderTrackingApp.Blazor.Server/Program.cs +++ b/OrderTrackingApp.Blazor.Server/Program.cs @@ -1,4 +1,4 @@ -using OrderTrackingApp.Blazor.Server.Client.Pages; +using OrderTrackingApp.Blazor.Client.Pages; using OrderTrackingApp.Blazor.Server.Components; var builder = WebApplication.CreateBuilder(args); @@ -30,6 +30,6 @@ app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() - .AddAdditionalAssemblies(typeof(OrderTrackingApp.Blazor.Server.Client._Imports).Assembly); + .AddAdditionalAssemblies(typeof(OrderTrackingApp.Blazor.Client._Imports).Assembly); app.Run(); diff --git a/OrderTrackingApp.Consumer/Dockerfile b/OrderTrackingApp.Consumer/Dockerfile index 20db7d2..5cd2a65 100644 --- a/OrderTrackingApp.Consumer/Dockerfile +++ b/OrderTrackingApp.Consumer/Dockerfile @@ -10,18 +10,20 @@ COPY ["OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj", COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] COPY ["OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj", "OrderTrackingApp.ReadPersistence/"] +# Copy your root certificate into the container +COPY nscacert.crt /usr/local/share/ca-certificates/nscacert.crt + +# Update the certificate store +RUN update-ca-certificates + RUN dotnet restore "OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj" COPY . . -WORKDIR /src/OrderTrackingApp.Consumer -RUN dotnet publish -c Release -o /app - -WORKDIR "/src/OrderTrackingApp.Consumer" -RUN dotnet build "OrderTrackingApp.Consumer.csproj" -c Release -o /app/build --no-restore +RUN dotnet build "OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj" -c Release -o /app/build --no-restore FROM build AS publish -RUN dotnet publish "OrderTrackingApp.Consumer.csproj" -c Release -o /app/publish --no-restore +RUN dotnet publish "OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj" -c Release -o /app/publish --no-restore FROM base AS final WORKDIR /app diff --git a/OrderTrackingApp.Consumer/dev.Dockerfile b/OrderTrackingApp.Consumer/dev.Dockerfile deleted file mode 100644 index 6260527..0000000 --- a/OrderTrackingApp.Consumer/dev.Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0 - -WORKDIR /app - -# Install Visual Studio Debugger -RUN apt-get update && \ - apt-get install -y curl unzip && \ - curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg - -# Default command is to use dotnet watch -ENTRYPOINT ["dotnet", "watch", "run"] diff --git a/OrderTrackingApp.sln b/OrderTrackingApp.sln index 1b44c58..3792842 100644 --- a/OrderTrackingApp.sln +++ b/OrderTrackingApp.sln @@ -6,7 +6,6 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" ProjectSection(SolutionItems) = preProject .env = .env - docker-compose.override.yml = docker-compose.override.yml docker-compose.yml = docker-compose.yml README.md = README.md EndProjectSection diff --git a/README.md b/README.md index a7a71fc..8ec594c 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,12 @@ This section lists the Docker build and run commands for each project in the sol docker build -f OrderTrackingApp.Api/Dockerfile -t ordertrackingapp-api . # Run - docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Api -p 8080:8080 mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run --urls=http://0.0.0.0:8080 - ``` + docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Api -p 5000:8080 mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run --urls=http://0.0.0.0:8080 + +#Run within corporate network where internet is not available and need to use company certificates +docker run -it --rm -v "$(Get-Location):/app" -v "$(Get-Location)/nscacert.crt:/usr/local/share/ca-certificates/nscacert.crt:ro" -w /app/OrderTrackingApp.Api -p 5000:8080 mcr.microsoft.com/dotnet/sdk:8.0 bash -c "update-ca-certificates && dotnet watch run --urls=http://0.0.0.0:8080" +``` + ### 📦 `OrderTrackingApp.Consumer` ```bash @@ -23,4 +27,17 @@ docker build -f OrderTrackingApp.Consumer/Dockerfile -t ordertrackingapp-consume # Run docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Consumer mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run - ``` \ No newline at end of file + +#Run within corporate network where internet is not available and need to use company certificates + docker run -it --rm -v "$(Get-Location):/app" -v "$(Get-Location)/nscacert.crt:/usr/local/share/ca-certificates/nscacert.crt:ro" -w /app/OrderTrackingApp.Consumer mcr.microsoft.com/dotnet/sdk:8.0 bash -c "update-ca-certificates && dotnet watch run" +``` + +### 📦 `OrderTrackingApp.Blazor.Server` + +```bash +# Build +docker build -f OrderTrackingApp.Blazor.Server/Dockerfile -t ordertrackingapp-blazor-server . + +# Run + docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Blazor.Server mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run +``` diff --git a/docker-compose.override.yml b/docker-compose.override.yml deleted file mode 100644 index d5f69df..0000000 --- a/docker-compose.override.yml +++ /dev/null @@ -1,25 +0,0 @@ -services: - - api: - build: - context: . - dockerfile: OrderTrackingApp.Api/dev.Dockerfile - command: ["dotnet", "watch", "run"] - volumes: - - .:/app - working_dir: /app/OrderTrackingApp.Api - environment: - - ASPNETCORE_ENVIRONMENT=Development - ports: - - "5000:8080" - - consumer: - build: - context: . - dockerfile: OrderTrackingApp.Consumer/dev.Dockerfile - command: ["dotnet", "watch", "run"] - volumes: - - .:/app - working_dir: /app/OrderTrackingApp.Consumer - environment: - - ASPNETCORE_ENVIRONMENT=Development diff --git a/docker-compose.yml b/docker-compose.yml index 01f9588..cafe6cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,10 +6,15 @@ services: ports: - "1433:1433" environment: - SA_PASSWORD: ${SA_PASSWORD} + SA_PASSWORD: ${SA_PASSWORD:-Your_password123} ACCEPT_EULA: "Y" volumes: - - sqlvolume:/var/opt/mssql + - sql_data:/var/opt/mssql + healthcheck: + test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", "${SA_PASSWORD:-Your_password123}", "-Q", "SELECT 1"] + interval: 10s + timeout: 5s + retries: 10 mongodb: image: mongo @@ -17,10 +22,15 @@ services: ports: - "27017:27017" environment: - MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME} - MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} + MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:-admin} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:-password} volumes: - - mongodata:/data/db + - mongo_data:/data/db + healthcheck: + test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"] + interval: 10s + timeout: 5s + retries: 5 rabbitmq: image: rabbitmq:4-management @@ -31,6 +41,13 @@ services: environment: RABBITMQ_DEFAULT_USER: guest RABBITMQ_DEFAULT_PASS: guest + volumes: + - rabbitmq_data:/var/lib/rabbitmq + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "status"] + interval: 10s + timeout: 5s + retries: 5 api: build: @@ -45,8 +62,8 @@ services: - mongodb environment: - ASPNETCORE_ENVIRONMENT=Production - - ConnectionStrings__DefaultConnection=Server=sqlserver,1433;Database=OrderDb;User=sa;Password=${SA_PASSWORD}; - - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongodb:27017 + - ConnectionStrings__DefaultConnection=Server=sqlserver;Database=OrderDb;User=sa;Password=${SA_PASSWORD:-Your_password123}; + - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME:-admin}:${MONGO_INITDB_ROOT_PASSWORD:-password}@mongodb:27017 consumer: build: @@ -59,22 +76,26 @@ services: - mongodb environment: - ASPNETCORE_ENVIRONMENT=Production - - ConnectionStrings__DefaultConnection=Server=sqlserver,1433;Database=OrderDb;User=sa;Password=${SA_PASSWORD}; - - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongodb:27017 + - ConnectionStrings__DefaultConnection=Server=sqlserver;Database=OrderDb;User=sa;Password=${SA_PASSWORD:-Your_password123}; + - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME:-admin}:${MONGO_INITDB_ROOT_PASSWORD:-password}@mongodb:27017 - # blazor-server: - # build: - # context: . - # dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile - # container_name: blazor-server - # ports: - # - "7000:80" - # depends_on: - # - api - # environment: - # - ASPNETCORE_ENVIRONMENT=Production - # - ApiBaseUrl=http://api:80 + blazor-server: + build: + context: . + dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile + container_name: blazor-server + ports: + - "7000:8080" + depends_on: + - api + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ApiBaseUrl=http://api:80 volumes: - sqlvolume: - mongodata: + sql_data: + name: sql_data + mongo_data: + name: mongo_data + rabbitmq_data: + name: rabbitmq_data diff --git a/nscacert.crt b/nscacert.crt new file mode 100644 index 0000000..8fd2269 --- /dev/null +++ b/nscacert.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID+jCCAuKgAwIBAgICATgwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNVBAYTAkFV +MQwwCgYDVQQIEwNWSUMxEjAQBgNVBAcTCU1lbGJvdXJuZTEWMBQGA1UEChMNTmV0 +c2tvcGUgSW5jLjESMBAGA1UECxMJY2VydGFkbWluMRIwEAYDVQQDEwljZXJ0YWRt +aW4xJTAjBgkqhkiG9w0BCQEWFmNlcnRhZG1pbkBuZXRza29wZS5jb20wHhcNMjEw +MzIxMDUzODMyWhcNMzEwMzE5MDUzODMyWjCBljELMAkGA1UEBhMCQVUxDDAKBgNV +BAgTA1ZJQzESMBAGA1UEBxMJTWVsYm91cm5lMRYwFAYDVQQKEw1OZXRza29wZSBJ +bmMuMRIwEAYDVQQLEwljZXJ0YWRtaW4xEjAQBgNVBAMTCWNlcnRhZG1pbjElMCMG +CSqGSIb3DQEJARYWY2VydGFkbWluQG5ldHNrb3BlLmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAK5aO8+dAaPOlPmc+YtaYpnxByrOtx8HDapDM8WD +7/8Y6obWjn0+uP6A0P3rtlECif0NHpKMIY10Kd+WImrMKeEDBkjK4IN4fHff2ji9 +dVPpvqBakGxpFdSDikLHkxswIJBwXPiAfvPPDYNt5tnWRp+C23u3Wj2ocLqAdyuM ++GGUc8lO5yCpDZXsyYZT6ycgAfzRf2IVYCS88l0hqMN45Z/9w/EixAFMamNho61x +x6YQhOzTugSdEr3sFm2hs+ijKZCorGe6eQIlSm4abAQXFLGOFPmRM+Q6MAx6nuV7 +d8+PIyDf+SHBxlAbQkeBucznByLG+HcZAlK6nnuWRgT97B0CAwEAAaNQME4wDAYD +VR0TBAUwAwEB/zAdBgNVHQ4EFgQUN2YGoXPDWG0auDkFXzQEwfPdSrYwHwYDVR0j +BBgwFoAUN2YGoXPDWG0auDkFXzQEwfPdSrYwDQYJKoZIhvcNAQELBQADggEBAC3D +qtNM0uZYsNeBwLCU2MEV9gV+BYBPwJvsXgIBiP2wH8VQ/QXbFw2HW3adVaEb0JgX +Qn3SUaw2sd2SeCw6KyRoXQg+Y06wEl3iTenzbpClp7sxVFB6II9o5aQPNXiAwcM1 +lRUSWOU8kUMYFbq2ndUonJX8eB4QiOzA7JppYf4dNgBXETxBngG1uFlZgGEN6Ars +o6iTHfZ+CTptlkuSjv2sWbK0pkIdsMHf2bScw3lu1oR3hwZCSdxI6Q5tXK4XsLLe +hAM5vIcMC434S3RV6iN9VW2wWYrQ9DPnHxw7x7fwUAcTaxIhOEJF7AhuCtg5mjlP +qwf9GAyaVt+8ilbHYdw= +-----END CERTIFICATE----- From ff95534aef1b1c841b6364c07ffd2019a7e7baa4 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Mon, 14 Jul 2025 15:52:58 +1000 Subject: [PATCH 06/17] wip --- .../Components/Layout/NavMenu.razor | 2 +- README.md | 4 ++-- docker-compose.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor b/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor index d7efbf2..2580c74 100644 --- a/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor +++ b/OrderTrackingApp.Blazor.Server/Components/Layout/NavMenu.razor @@ -1,6 +1,6 @@  diff --git a/README.md b/README.md index 8ec594c..a0c7cec 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ docker build -f OrderTrackingApp.Api/Dockerfile -t ordertrackingapp-api . # Run docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Api -p 5000:8080 mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run --urls=http://0.0.0.0:8080 -#Run within corporate network where internet is not available and need to use company certificates +# Run within corporate network where internet is not available and need to use company certificates docker run -it --rm -v "$(Get-Location):/app" -v "$(Get-Location)/nscacert.crt:/usr/local/share/ca-certificates/nscacert.crt:ro" -w /app/OrderTrackingApp.Api -p 5000:8080 mcr.microsoft.com/dotnet/sdk:8.0 bash -c "update-ca-certificates && dotnet watch run --urls=http://0.0.0.0:8080" ``` @@ -28,7 +28,7 @@ docker build -f OrderTrackingApp.Consumer/Dockerfile -t ordertrackingapp-consume # Run docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Consumer mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run -#Run within corporate network where internet is not available and need to use company certificates +# Run within corporate network where internet is not available and need to use company certificates docker run -it --rm -v "$(Get-Location):/app" -v "$(Get-Location)/nscacert.crt:/usr/local/share/ca-certificates/nscacert.crt:ro" -w /app/OrderTrackingApp.Consumer mcr.microsoft.com/dotnet/sdk:8.0 bash -c "update-ca-certificates && dotnet watch run" ``` diff --git a/docker-compose.yml b/docker-compose.yml index cafe6cc..8fdc50f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -90,7 +90,7 @@ services: - api environment: - ASPNETCORE_ENVIRONMENT=Production - - ApiBaseUrl=http://api:80 + - ApiBaseUrl=http://api:8080 volumes: sql_data: From 3f652c0170816ec6fb09b724eeb75d5fde2fa50c Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Tue, 15 Jul 2025 10:22:05 +1000 Subject: [PATCH 07/17] setup github actions (#1) * setup github actions * fix * fix * fix * updated CI * rename * fix * trigger build fail * set fail fast to false * fix --------- Co-authored-by: Parag Naikade --- .github/workflows/ci-pipeline.yml | 45 +++++++++++++++++++++++++++++++ README.md | 4 ++- docker-compose.yml | 15 ++++++----- 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/ci-pipeline.yml diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml new file mode 100644 index 0000000..9d4a877 --- /dev/null +++ b/.github/workflows/ci-pipeline.yml @@ -0,0 +1,45 @@ +name: Parallel Docker Builds with Cache + +on: + push: + branches: [develop, master] + pull_request: + branches: [develop, master] + +jobs: + build-docker: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - dockerfile: OrderTrackingApp.Api/Dockerfile + image-name: ordertrackingapp-api + - dockerfile: OrderTrackingApp.Consumer/Dockerfile + image-name: ordertrackingapp-consumer + - dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile + image-name: ordertrackingapp-blazor-server + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ matrix.image-name }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Build Docker image with cache + run: | + docker buildx build \ + --cache-from=type=local,src=/tmp/.buildx-cache \ + --cache-to=type=local,dest=/tmp/.buildx-cache \ + -f ${{ matrix.dockerfile }} \ + -t ${{ matrix.image-name }} \ + --load . diff --git a/README.md b/README.md index a0c7cec..f1fdfce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# 🧱 OrderTrackingApp - Docker Commands +[![CI](https://github.com/ParagNaikade/OrderTrackingApp/actions/workflows/ci-compose.yml/badge.svg?branch=develop)](https://github.com/ParagNaikade/OrderTrackingApp/actions/workflows/ci-compose.yml) + +# 🧱 OrderTrackingApp - Docker Commands This section lists the Docker build and run commands for each project in the solution. diff --git a/docker-compose.yml b/docker-compose.yml index 8fdc50f..36dde19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,22 @@ services: - sqlserver: image: mcr.microsoft.com/mssql/server:2022-latest container_name: sqlserver ports: - "1433:1433" environment: - SA_PASSWORD: ${SA_PASSWORD:-Your_password123} + MSSQL_SA_PASSWORD: ${SA_PASSWORD} ACCEPT_EULA: "Y" volumes: - sql_data:/var/opt/mssql healthcheck: - test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", "${SA_PASSWORD:-Your_password123}", "-Q", "SELECT 1"] + test: + [ + "CMD-SHELL", + '/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$${SA_PASSWORD}" -Q "SELECT 1"', + ] interval: 10s - timeout: 5s + timeout: 100s retries: 10 mongodb: @@ -22,8 +25,8 @@ services: ports: - "27017:27017" environment: - MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:-admin} - MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:-password} + MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} volumes: - mongo_data:/data/db healthcheck: From 330453dba64308379b0a0cf3e8f0666a9debf427 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Tue, 15 Jul 2025 10:28:56 +1000 Subject: [PATCH 08/17] Update README.md (#2) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1fdfce..76ed2a7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![CI](https://github.com/ParagNaikade/OrderTrackingApp/actions/workflows/ci-compose.yml/badge.svg?branch=develop)](https://github.com/ParagNaikade/OrderTrackingApp/actions/workflows/ci-compose.yml) +[![Parallel Docker Builds with Cache](https://github.com/ParagNaikade/OrderTrackingApp/actions/workflows/ci-pipeline.yml/badge.svg)](https://github.com/ParagNaikade/OrderTrackingApp/actions/workflows/ci-pipeline.yml) # 🧱 OrderTrackingApp - Docker Commands From fb448195ae6ca60424d2d5fce624339cf8f66380 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Tue, 15 Jul 2025 11:50:52 +1000 Subject: [PATCH 09/17] Update ci-pipeline.yml (#3) --- .github/workflows/ci-pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 9d4a877..f247233 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -8,6 +8,7 @@ on: jobs: build-docker: + name: "Build docker images" runs-on: ubuntu-latest strategy: fail-fast: false From 609e9449453255a6650d37cc6d9043b38411e836 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Tue, 15 Jul 2025 15:49:31 +1000 Subject: [PATCH 10/17] fix blazor dockerfile (#5) Co-authored-by: Parag Naikade --- .dockerignore | 1 - OrderTrackingApp.Blazor.Server/Dockerfile | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.dockerignore b/.dockerignore index 8144c5d..47565c6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,7 +8,6 @@ **/*.db **/*.log **/node_modules/ -**/wwwroot/ Dockerfile* docker-compose* .env diff --git a/OrderTrackingApp.Blazor.Server/Dockerfile b/OrderTrackingApp.Blazor.Server/Dockerfile index a442a11..1e9cd1b 100644 --- a/OrderTrackingApp.Blazor.Server/Dockerfile +++ b/OrderTrackingApp.Blazor.Server/Dockerfile @@ -1,7 +1,3 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -WORKDIR /app -EXPOSE 8080 - FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src @@ -23,11 +19,11 @@ RUN dotnet build "OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server. FROM build AS publish RUN dotnet publish "OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj" -c Release -o /app/publish --no-restore -FROM base AS final +FROM mcr.microsoft.com/dotnet/aspnet:8.0 WORKDIR /app COPY --from=publish /app/publish . -ENV ASPNETCORE_URLS=http://0.0.0.0:8080 +EXPOSE 8080 # Start the app ENTRYPOINT ["dotnet", "OrderTrackingApp.Blazor.Server.dll"] \ No newline at end of file From edaf18132a43ae68ec3be9edbee6f4e639206c61 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Wed, 16 Jul 2025 09:31:18 +1000 Subject: [PATCH 11/17] fix (#6) Co-authored-by: Parag Naikade --- .env | 8 ++++-- docker-compose.yml | 68 ++-------------------------------------------- 2 files changed, 8 insertions(+), 68 deletions(-) diff --git a/.env b/.env index 5959827..88aaa35 100644 --- a/.env +++ b/.env @@ -1,3 +1,5 @@ -SA_PASSWORD=YourStrong!Passw0rd -MONGO_INITDB_ROOT_USERNAME=mongoUser -MONGO_INITDB_ROOT_PASSWORD=mongoPass123 +MSSQL_SA_PASSWORD=YourStrong!Passw0rd +MONGO_INITDB_ROOT_USERNAME=admin +MONGO_INITDB_ROOT_PASSWORD=secret +RABBITMQ_DEFAULT_USER=guest +RABBITMQ_DEFAULT_PASS=guest \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 36dde19..fab4db5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,19 +5,10 @@ services: ports: - "1433:1433" environment: - MSSQL_SA_PASSWORD: ${SA_PASSWORD} + MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD} ACCEPT_EULA: "Y" volumes: - sql_data:/var/opt/mssql - healthcheck: - test: - [ - "CMD-SHELL", - '/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$${SA_PASSWORD}" -Q "SELECT 1"', - ] - interval: 10s - timeout: 100s - retries: 10 mongodb: image: mongo @@ -29,11 +20,6 @@ services: MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} volumes: - mongo_data:/data/db - healthcheck: - test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"] - interval: 10s - timeout: 5s - retries: 5 rabbitmq: image: rabbitmq:4-management @@ -42,58 +28,10 @@ services: - "5672:5672" - "15672:15672" environment: - RABBITMQ_DEFAULT_USER: guest - RABBITMQ_DEFAULT_PASS: guest + RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS} volumes: - rabbitmq_data:/var/lib/rabbitmq - healthcheck: - test: ["CMD", "rabbitmq-diagnostics", "status"] - interval: 10s - timeout: 5s - retries: 5 - - api: - build: - context: . - dockerfile: OrderTrackingApp.Api/Dockerfile - container_name: api - ports: - - "5000:8080" - depends_on: - - sqlserver - - rabbitmq - - mongodb - environment: - - ASPNETCORE_ENVIRONMENT=Production - - ConnectionStrings__DefaultConnection=Server=sqlserver;Database=OrderDb;User=sa;Password=${SA_PASSWORD:-Your_password123}; - - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME:-admin}:${MONGO_INITDB_ROOT_PASSWORD:-password}@mongodb:27017 - - consumer: - build: - context: . - dockerfile: OrderTrackingApp.Consumer/Dockerfile - container_name: consumer - depends_on: - - sqlserver - - rabbitmq - - mongodb - environment: - - ASPNETCORE_ENVIRONMENT=Production - - ConnectionStrings__DefaultConnection=Server=sqlserver;Database=OrderDb;User=sa;Password=${SA_PASSWORD:-Your_password123}; - - ConnectionStrings__MongoDb=mongodb://${MONGO_INITDB_ROOT_USERNAME:-admin}:${MONGO_INITDB_ROOT_PASSWORD:-password}@mongodb:27017 - - blazor-server: - build: - context: . - dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile - container_name: blazor-server - ports: - - "7000:8080" - depends_on: - - api - environment: - - ASPNETCORE_ENVIRONMENT=Production - - ApiBaseUrl=http://api:8080 volumes: sql_data: From 45e4a9b431f80f0f9dd8e4e1f92490c315db5a5b Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Thu, 17 Jul 2025 20:08:16 +1000 Subject: [PATCH 12/17] Setup create order API (#7) * WIP * fix * WIP * fix * fix * fix * fix * fix * fix --------- Co-authored-by: Parag Naikade --- .github/workflows/ci-pipeline.yml | 2 + .../Controllers/OrdersController.cs | 22 ++++ .../Controllers/WeatherForecastController.cs | 33 ----- .../Extensions/ApiServiceRegistrations.cs | 27 ++++ .../MappingProfiles/OrderMappingProfile.cs | 15 +++ .../Models/CreateOrderRequest.cs | 22 ++++ .../OrderTrackingApp.Api.csproj | 5 + OrderTrackingApp.Api/Program.cs | 24 ++-- .../Properties/launchSettings.json | 5 +- .../Validators/CreateOrderRequestValidator.cs | 20 +++ OrderTrackingApp.Api/appsettings.json | 6 +- .../Commands/Orders/CreateOrderCommand.cs | 14 ++- .../Orders/CreateOrderCommandHandler.cs | 59 ++++++--- .../ApplicationServiceRegistrations.cs | 14 +++ .../Interfaces/IOrderWriteRepository.cs | 2 + OrderTrackingApp.Blazor.Server/Program.cs | 1 - .../OrderTrackingApp.Consumer.csproj | 6 + OrderTrackingApp.Consumer/Program.cs | 5 + .../Properties/launchSettings.json | 1 + OrderTrackingApp.Consumer/Worker.cs | 102 +++++++++++++-- OrderTrackingApp.Consumer/appsettings.json | 5 + OrderTrackingApp.Domain/Entities/Order.cs | 10 +- OrderTrackingApp.Domain/Entities/OrderItem.cs | 14 +++ OrderTrackingApp.Domain/Entities/Product.cs | 17 +++ .../Events/OrderCreatedEvent.cs | 9 ++ .../Interfaces/IProductRepository.cs | 17 +++ .../OrderTrackingApp.Domain.csproj | 4 + .../EventHandlers/OrderCreatedEventHandler.cs | 14 +++ .../InfrastructureServiceRegistrations.cs | 17 +++ .../Messaging/IMessagePublisher.cs | 7 ++ .../Messaging/RabbitMqPublisher.cs | 38 ++++++ .../OrderTrackingApp.Infrastructure.csproj | 10 ++ OrderTrackingApp.MigrationRunner/Dockerfile | 31 +++++ .../OrderTrackingApp.MigrationRunner.csproj | 30 +++++ .../ProductSeeder.cs | 42 +++++++ OrderTrackingApp.MigrationRunner/Program.cs | 21 ++++ .../Properties/launchSettings.json | 16 +++ .../appsettings.json | 5 + .../OrderTrackingApp.Persistence.Tests.csproj | 27 ++++ .../UnitTest1.cs | 44 +++++++ OrderTrackingApp.Persistence/AppDbContext.cs | 53 +++++++- .../DesignTimeDbContextFactory.cs | 16 +++ ....cs => PersistenceServiceRegistrations.cs} | 4 +- .../20250716055404_InitSchema.Designer.cs | 116 ++++++++++++++++++ .../Migrations/20250716055404_InitSchema.cs | 78 ++++++++++++ .../Migrations/AppDbContextModelSnapshot.cs | 113 +++++++++++++++++ .../OrderTrackingApp.Persistence.csproj | 4 + .../Repositories/OrderWriteRepository.cs | 7 +- .../Repositories/ProductRepository.cs | 36 ++++++ .../ReadPersistenceServiceRegistration.cs | 5 +- .../Interfaces/IOrderReadRepository.cs | 8 +- .../Models/OrderItemReadModel.cs | 11 ++ .../Models/OrderReadModel.cs | 17 +++ .../MongoDbContext.cs | 2 +- .../Repositories/OrderReadRepository.cs | 18 +-- OrderTrackingApp.sln | 14 +++ 56 files changed, 1156 insertions(+), 109 deletions(-) create mode 100644 OrderTrackingApp.Api/Controllers/OrdersController.cs delete mode 100644 OrderTrackingApp.Api/Controllers/WeatherForecastController.cs create mode 100644 OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs create mode 100644 OrderTrackingApp.Api/MappingProfiles/OrderMappingProfile.cs create mode 100644 OrderTrackingApp.Api/Models/CreateOrderRequest.cs create mode 100644 OrderTrackingApp.Api/Validators/CreateOrderRequestValidator.cs create mode 100644 OrderTrackingApp.Application/Extensions/ApplicationServiceRegistrations.cs create mode 100644 OrderTrackingApp.Domain/Entities/OrderItem.cs create mode 100644 OrderTrackingApp.Domain/Entities/Product.cs create mode 100644 OrderTrackingApp.Domain/Events/OrderCreatedEvent.cs create mode 100644 OrderTrackingApp.Domain/Interfaces/IProductRepository.cs create mode 100644 OrderTrackingApp.Infrastructure/EventHandlers/OrderCreatedEventHandler.cs create mode 100644 OrderTrackingApp.Infrastructure/Extensions/InfrastructureServiceRegistrations.cs create mode 100644 OrderTrackingApp.Infrastructure/Messaging/IMessagePublisher.cs create mode 100644 OrderTrackingApp.Infrastructure/Messaging/RabbitMqPublisher.cs create mode 100644 OrderTrackingApp.MigrationRunner/Dockerfile create mode 100644 OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj create mode 100644 OrderTrackingApp.MigrationRunner/ProductSeeder.cs create mode 100644 OrderTrackingApp.MigrationRunner/Program.cs create mode 100644 OrderTrackingApp.MigrationRunner/Properties/launchSettings.json create mode 100644 OrderTrackingApp.MigrationRunner/appsettings.json create mode 100644 OrderTrackingApp.Persistence.Tests/OrderTrackingApp.Persistence.Tests.csproj create mode 100644 OrderTrackingApp.Persistence.Tests/UnitTest1.cs create mode 100644 OrderTrackingApp.Persistence/DesignTimeDbContextFactory.cs rename OrderTrackingApp.Persistence/Extensions/{PersistenceServiceRegistration.cs => PersistenceServiceRegistrations.cs} (84%) create mode 100644 OrderTrackingApp.Persistence/Migrations/20250716055404_InitSchema.Designer.cs create mode 100644 OrderTrackingApp.Persistence/Migrations/20250716055404_InitSchema.cs create mode 100644 OrderTrackingApp.Persistence/Migrations/AppDbContextModelSnapshot.cs create mode 100644 OrderTrackingApp.Persistence/Repositories/ProductRepository.cs create mode 100644 OrderTrackingApp.ReadPersistence/Models/OrderItemReadModel.cs create mode 100644 OrderTrackingApp.ReadPersistence/Models/OrderReadModel.cs diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index f247233..c8c5052 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -20,6 +20,8 @@ jobs: image-name: ordertrackingapp-consumer - dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile image-name: ordertrackingapp-blazor-server + - dockerfile: OrderTrackingApp.MigrationRunner/Dockerfile + image-name: ordertrackingapp-migration-runner steps: - name: Checkout code diff --git a/OrderTrackingApp.Api/Controllers/OrdersController.cs b/OrderTrackingApp.Api/Controllers/OrdersController.cs new file mode 100644 index 0000000..13dd792 --- /dev/null +++ b/OrderTrackingApp.Api/Controllers/OrdersController.cs @@ -0,0 +1,22 @@ +using AutoMapper; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using OrderTrackingApp.Api.Models; +using OrderTrackingApp.Application.Commands.Orders; + +namespace OrderTrackingApp.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class OrdersController(IMediator mediator, IMapper mapper) : ControllerBase + { + [HttpPost] + public async Task CreateOrder([FromBody] CreateOrderRequest request) + { + var command = mapper.Map(request); + var orderId = await mediator.Send(command); + + return CreatedAtAction(nameof(CreateOrder), new { id = orderId }, new { OrderId = orderId }); + } + } +} diff --git a/OrderTrackingApp.Api/Controllers/WeatherForecastController.cs b/OrderTrackingApp.Api/Controllers/WeatherForecastController.cs deleted file mode 100644 index 13b5931..0000000 --- a/OrderTrackingApp.Api/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace OrderTrackingApp.Api.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs b/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs new file mode 100644 index 0000000..d0d3e4f --- /dev/null +++ b/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs @@ -0,0 +1,27 @@ +using FluentValidation; +using OrderTrackingApp.Api.MappingProfiles; +using OrderTrackingApp.Api.Validators; +using System.Text.Json; + +namespace OrderTrackingApp.Api.Extensions +{ + public static class ApiServiceRegistrations + { + public static IServiceCollection AddApiServices(this IServiceCollection services) + { + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + + services.AddControllers() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + }); + + services.AddValidatorsFromAssemblyContaining(); + services.AddAutoMapper(config => { }, typeof(OrderMappingProfile).Assembly); + + return services; + } + } +} diff --git a/OrderTrackingApp.Api/MappingProfiles/OrderMappingProfile.cs b/OrderTrackingApp.Api/MappingProfiles/OrderMappingProfile.cs new file mode 100644 index 0000000..72900a1 --- /dev/null +++ b/OrderTrackingApp.Api/MappingProfiles/OrderMappingProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using OrderTrackingApp.Api.Models; +using OrderTrackingApp.Application.Commands.Orders; + +namespace OrderTrackingApp.Api.MappingProfiles +{ + public class OrderMappingProfile : Profile + { + public OrderMappingProfile() + { + CreateMap(); + CreateMap(); + } + } +} diff --git a/OrderTrackingApp.Api/Models/CreateOrderRequest.cs b/OrderTrackingApp.Api/Models/CreateOrderRequest.cs new file mode 100644 index 0000000..2b699f0 --- /dev/null +++ b/OrderTrackingApp.Api/Models/CreateOrderRequest.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; + +namespace OrderTrackingApp.Api.Models +{ + public class CreateOrderRequest + { + [Required] + public Guid CustomerId { get; set; } + + [Required] + public List Items { get; set; } = []; + } + + public class CreateOrderItemRequest + { + [Required] + public Guid ProductId { get; set; } + + [Range(1, int.MaxValue)] + public int Quantity { get; set; } + } +} diff --git a/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj b/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj index 21a8bb8..2dfe61f 100644 --- a/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj +++ b/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj @@ -10,10 +10,15 @@ + + + + + diff --git a/OrderTrackingApp.Api/Program.cs b/OrderTrackingApp.Api/Program.cs index 6950881..571889f 100644 --- a/OrderTrackingApp.Api/Program.cs +++ b/OrderTrackingApp.Api/Program.cs @@ -1,25 +1,27 @@ +using OrderTrackingApp.Infrastructure.Extensions; using OrderTrackingApp.Persistence.Extensions; using OrderTrackingApp.ReadPersistence.Extensions; +using OrderTrackingApp.Application.Extensions; +using OrderTrackingApp.Api.Extensions; +using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); -// Add services to the container. - -builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - builder.Services.AddPersistenceServices(builder.Configuration); -builder.Services.AddReadPersistence(builder.Configuration); +builder.Services.AddReadPersistence(); +builder.Services.AddInfrastructureServices(); +builder.Services.AddApplicationServices(); +builder.Services.AddApiServices(); var app = builder.Build(); -// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwagger(options => + { + options.RouteTemplate = "/openapi/{documentName}.json"; + }); + app.MapScalarApiReference(); } app.UseHttpsRedirection(); diff --git a/OrderTrackingApp.Api/Properties/launchSettings.json b/OrderTrackingApp.Api/Properties/launchSettings.json index a5c6743..4c1dc4f 100644 --- a/OrderTrackingApp.Api/Properties/launchSettings.json +++ b/OrderTrackingApp.Api/Properties/launchSettings.json @@ -12,9 +12,10 @@ "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", + "launchBrowser": false, + "launchUrl": "scalar", "applicationUrl": "http://localhost:5077", + "hotReloadEnabled": false, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/OrderTrackingApp.Api/Validators/CreateOrderRequestValidator.cs b/OrderTrackingApp.Api/Validators/CreateOrderRequestValidator.cs new file mode 100644 index 0000000..e5fe048 --- /dev/null +++ b/OrderTrackingApp.Api/Validators/CreateOrderRequestValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using OrderTrackingApp.Api.Models; + +namespace OrderTrackingApp.Api.Validators +{ + public class CreateOrderRequestValidator : AbstractValidator + { + public CreateOrderRequestValidator() + { + RuleFor(x => x.CustomerId).NotEmpty(); + RuleFor(x => x.Items).NotEmpty(); + + RuleForEach(x => x.Items).ChildRules(items => + { + items.RuleFor(i => i.ProductId).NotEmpty(); + items.RuleFor(i => i.Quantity).GreaterThan(0); + }); + } + } +} diff --git a/OrderTrackingApp.Api/appsettings.json b/OrderTrackingApp.Api/appsettings.json index b558c5c..917aef7 100644 --- a/OrderTrackingApp.Api/appsettings.json +++ b/OrderTrackingApp.Api/appsettings.json @@ -7,8 +7,8 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "", - "MongoDb": "" + "SqlConnection": "Server=localhost,1433;Database=OrderDb;User=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True", + "MongoDb": "mongodb://mongoadmin:secret123@localhost:27017", + "RabbitMq": "amqp://guest:guest@localhost:5672/" } - } diff --git a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs index dba7b19..3ec6b5c 100644 --- a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs +++ b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommand.cs @@ -1,10 +1,18 @@ using MediatR; -using OrderTrackingApp.Application.DTOs; namespace OrderTrackingApp.Application.Commands.Orders { - public class CreateOrderCommand : IRequest + public class CreateOrderCommand : IRequest { - public string CustomerName { get; set; } = string.Empty; + public Guid CustomerId { get; set; } + + public List Items { get; set; } = []; + } + + public class CreateOrderItemDto + { + public Guid ProductId { get; set; } + + public int Quantity { get; set; } } } diff --git a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs index 170e8e4..9f77403 100644 --- a/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs +++ b/OrderTrackingApp.Application/Commands/Orders/CreateOrderCommandHandler.cs @@ -1,36 +1,59 @@ using MediatR; -using OrderTrackingApp.Application.DTOs; using OrderTrackingApp.Application.Interfaces; using OrderTrackingApp.Domain.Entities; +using OrderTrackingApp.Domain.Events; +using OrderTrackingApp.Domain.Interfaces; namespace OrderTrackingApp.Application.Commands.Orders { - public class CreateOrderCommandHandler : IRequestHandler + public class CreateOrderCommandHandler(IOrderWriteRepository orderRepository, IProductRepository productRepository, IMediator mediator) + : IRequestHandler { - private readonly IOrderWriteRepository _orderRepository; - - public CreateOrderCommandHandler(IOrderWriteRepository orderRepository) + public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) { - _orderRepository = orderRepository; - } + var orderItems = new List(); + var updatedProducts = new List(); + + var productIds = request.Items.Select(item => item.ProductId).ToList(); + var products = await productRepository.GetByIdsAsync(productIds); + var productDict = products.ToDictionary(p => p.Id, p => p); + + foreach (var item in request.Items) + { + if (!productDict.TryGetValue(item.ProductId, out Product? product)) + { + throw new KeyNotFoundException($"Product with ID {item.ProductId} not found."); + } + + if(product.StockQuantity < item.Quantity) + { + 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 }); + + product.StockQuantity -= item.Quantity; + + updatedProducts.Add(product); + } + + await productRepository.UpdateProducts(updatedProducts); - public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) - { var order = new Order { - CustomerName = request.CustomerName, + Id = Guid.NewGuid(), + OrderNumber = $"ORD-{DateTime.UtcNow.Ticks}", + CustomerId = request.CustomerId, + OrderDate = DateTime.UtcNow, Status = OrderStatus.Pending, + Items = orderItems }; - await _orderRepository.AddAsync(order); + await orderRepository.AddAsync(order); - return new OrderDto - { - Id = order.Id, - CustomerName = order.CustomerName, - Status = order.Status, - CreatedAt = order.CreatedAt - }; + await mediator.Publish(new OrderCreatedEvent(order.Id), cancellationToken); + + return order.Id; } } } diff --git a/OrderTrackingApp.Application/Extensions/ApplicationServiceRegistrations.cs b/OrderTrackingApp.Application/Extensions/ApplicationServiceRegistrations.cs new file mode 100644 index 0000000..92f2ea3 --- /dev/null +++ b/OrderTrackingApp.Application/Extensions/ApplicationServiceRegistrations.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using OrderTrackingApp.Application.Commands.Orders; + +namespace OrderTrackingApp.Application.Extensions +{ + public static class ApplicationServiceRegistrations + { + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateOrderCommandHandler).Assembly)); + return services; + } + } +} diff --git a/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs b/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs index c31d16a..183b1fb 100644 --- a/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs +++ b/OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs @@ -9,5 +9,7 @@ public interface IOrderWriteRepository Task UpdateAsync(Order order); Task DeleteAsync(Guid id); + + Task GetByIdAsync(Guid id); } } diff --git a/OrderTrackingApp.Blazor.Server/Program.cs b/OrderTrackingApp.Blazor.Server/Program.cs index fd8b203..d784bf9 100644 --- a/OrderTrackingApp.Blazor.Server/Program.cs +++ b/OrderTrackingApp.Blazor.Server/Program.cs @@ -1,4 +1,3 @@ -using OrderTrackingApp.Blazor.Client.Pages; using OrderTrackingApp.Blazor.Server.Components; var builder = WebApplication.CreateBuilder(args); diff --git a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj index 0fdd151..48f1333 100644 --- a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj +++ b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj @@ -9,5 +9,11 @@ + + + + + + diff --git a/OrderTrackingApp.Consumer/Program.cs b/OrderTrackingApp.Consumer/Program.cs index d13263b..3cb619d 100644 --- a/OrderTrackingApp.Consumer/Program.cs +++ b/OrderTrackingApp.Consumer/Program.cs @@ -1,7 +1,12 @@ using OrderTrackingApp.Consumer; +using OrderTrackingApp.Persistence.Extensions; +using OrderTrackingApp.ReadPersistence.Extensions; var builder = Host.CreateApplicationBuilder(args); builder.Services.AddHostedService(); +builder.Services.AddPersistenceServices(builder.Configuration); +builder.Services.AddReadPersistence(); + var host = builder.Build(); host.Run(); diff --git a/OrderTrackingApp.Consumer/Properties/launchSettings.json b/OrderTrackingApp.Consumer/Properties/launchSettings.json index df9bcc3..49b44b4 100644 --- a/OrderTrackingApp.Consumer/Properties/launchSettings.json +++ b/OrderTrackingApp.Consumer/Properties/launchSettings.json @@ -4,6 +4,7 @@ "OrderTrackingApp.Consumer": { "commandName": "Project", "dotnetRunMessages": true, + "hotReloadEnabled": false, "environmentVariables": { "DOTNET_ENVIRONMENT": "Development" } diff --git a/OrderTrackingApp.Consumer/Worker.cs b/OrderTrackingApp.Consumer/Worker.cs index c08d5b4..683e1f2 100644 --- a/OrderTrackingApp.Consumer/Worker.cs +++ b/OrderTrackingApp.Consumer/Worker.cs @@ -1,24 +1,100 @@ -namespace OrderTrackingApp.Consumer +using OrderTrackingApp.Application.Interfaces; +using OrderTrackingApp.Domain.Events; +using OrderTrackingApp.ReadPersistence.Interfaces; +using OrderTrackingApp.ReadPersistence.Models; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using System.Text; +using System.Text.Json; + +namespace OrderTrackingApp.Consumer; + +public class Worker : BackgroundService { - public class Worker : BackgroundService + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + private IConnection? _connection; + private IChannel? _channel; + + public Worker(ILogger logger, IServiceProvider serviceProvider) { - private readonly ILogger _logger; + _logger = logger; + _serviceProvider = serviceProvider; + } - public Worker(ILogger logger) - { - _logger = logger; - } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await InitializeRabbitMqListener(); - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + var consumer = new AsyncEventingBasicConsumer(_channel!); + + consumer.ReceivedAsync += async (model, ea) => { - while (!stoppingToken.IsCancellationRequested) + try { - if (_logger.IsEnabled(LogLevel.Information)) + 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); + + var orderEvent = JsonSerializer.Deserialize(message); + + if (orderEvent != null) { - _logger.LogInformation("Worker running at../: {time}", DateTimeOffset.Now); + 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); + + _logger.LogInformation("Synced OrderId {orderId} to MongoDB", order.Id); + + await _channel!.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false); } - await Task.Delay(60000, stoppingToken); } - } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to process RabbitMQ message."); + + await _channel!.BasicNackAsync(deliveryTag: ea.DeliveryTag, multiple: false, requeue: true); + } + }; + + await _channel!.BasicConsumeAsync(queue: "orders", + autoAck: false, + consumer: consumer, + stoppingToken); + } + + private async Task InitializeRabbitMqListener() + { + var factory = new ConnectionFactory() { HostName = "localhost" }; + _connection = await factory.CreateConnectionAsync(); + _channel = await _connection.CreateChannelAsync(); + + await _channel.BasicQosAsync(0, 1, false); + + await _channel.QueueDeclareAsync(queue: "orders", + durable: true, + exclusive: false, + autoDelete: false, + arguments: null); } } diff --git a/OrderTrackingApp.Consumer/appsettings.json b/OrderTrackingApp.Consumer/appsettings.json index b2dcdb6..576f5d9 100644 --- a/OrderTrackingApp.Consumer/appsettings.json +++ b/OrderTrackingApp.Consumer/appsettings.json @@ -4,5 +4,10 @@ "Default": "Information", "Microsoft.Hosting.Lifetime": "Information" } + }, + "ConnectionStrings": { + "SqlConnection": "Server=localhost,1433;Database=OrderDb;User=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True", + "MongoDb": "mongodb://mongoadmin:secret123@localhost:27017", + "RabbitMq": "amqp://guest:guest@localhost:5672/" } } diff --git a/OrderTrackingApp.Domain/Entities/Order.cs b/OrderTrackingApp.Domain/Entities/Order.cs index be533df..045d6c2 100644 --- a/OrderTrackingApp.Domain/Entities/Order.cs +++ b/OrderTrackingApp.Domain/Entities/Order.cs @@ -2,12 +2,16 @@ { public class Order { - public Guid Id { get; set; } = Guid.NewGuid(); + public Guid Id { get; set; } - public string CustomerName { get; set; } = string.Empty; + public string OrderNumber { get; set; } = string.Empty; - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public Guid CustomerId { get; set; } + + public DateTime OrderDate { get; set; } public OrderStatus Status { get; set; } + + public List Items { get; set; } = []; } } diff --git a/OrderTrackingApp.Domain/Entities/OrderItem.cs b/OrderTrackingApp.Domain/Entities/OrderItem.cs new file mode 100644 index 0000000..a73e340 --- /dev/null +++ b/OrderTrackingApp.Domain/Entities/OrderItem.cs @@ -0,0 +1,14 @@ +namespace OrderTrackingApp.Domain.Entities +{ + public class OrderItem + { + public Guid ProductId { get; set; } + + public int Quantity { get; set; } + + public decimal UnitPrice { get; set; } + + public decimal TotalPrice => Quantity * UnitPrice; + } + +} diff --git a/OrderTrackingApp.Domain/Entities/Product.cs b/OrderTrackingApp.Domain/Entities/Product.cs new file mode 100644 index 0000000..2ca56ef --- /dev/null +++ b/OrderTrackingApp.Domain/Entities/Product.cs @@ -0,0 +1,17 @@ +namespace OrderTrackingApp.Domain.Entities +{ + public class Product + { + public Guid Id { get; set; } + + public string Name { get; set; } = string.Empty; + + public string Sku { get; set; } = string.Empty; + + public string Description { get; set; } = string.Empty; + + public decimal Price { get; set; } + + public int StockQuantity { get; set; } + } +} diff --git a/OrderTrackingApp.Domain/Events/OrderCreatedEvent.cs b/OrderTrackingApp.Domain/Events/OrderCreatedEvent.cs new file mode 100644 index 0000000..8e3f471 --- /dev/null +++ b/OrderTrackingApp.Domain/Events/OrderCreatedEvent.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace OrderTrackingApp.Domain.Events +{ + public class OrderCreatedEvent(Guid orderId) : INotification + { + public Guid OrderId { get; } = orderId; + } +} diff --git a/OrderTrackingApp.Domain/Interfaces/IProductRepository.cs b/OrderTrackingApp.Domain/Interfaces/IProductRepository.cs new file mode 100644 index 0000000..60b1367 --- /dev/null +++ b/OrderTrackingApp.Domain/Interfaces/IProductRepository.cs @@ -0,0 +1,17 @@ +using OrderTrackingApp.Domain.Entities; + +namespace OrderTrackingApp.Domain.Interfaces +{ + public interface IProductRepository + { + Task GetByIdAsync(Guid id); + + Task> GetByIdsAsync(IEnumerable ids); + + Task AddAsync(Product product); + + Task UpdateAsync(Product product); + + Task UpdateProducts(List updatedProducts); + } +} diff --git a/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj b/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj index fa71b7a..db4c3f8 100644 --- a/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj +++ b/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/OrderTrackingApp.Infrastructure/EventHandlers/OrderCreatedEventHandler.cs b/OrderTrackingApp.Infrastructure/EventHandlers/OrderCreatedEventHandler.cs new file mode 100644 index 0000000..d035896 --- /dev/null +++ b/OrderTrackingApp.Infrastructure/EventHandlers/OrderCreatedEventHandler.cs @@ -0,0 +1,14 @@ +using MediatR; +using OrderTrackingApp.Domain.Events; +using OrderTrackingApp.Infrastructure.Messaging; + +namespace OrderTrackingApp.Infrastructure.EventHandlers +{ + public class OrderCreatedEventHandler(IMessagePublisher messagePublisher) : INotificationHandler + { + public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken) + { + await messagePublisher.PublishAsync("orders", notification); + } + } +} diff --git a/OrderTrackingApp.Infrastructure/Extensions/InfrastructureServiceRegistrations.cs b/OrderTrackingApp.Infrastructure/Extensions/InfrastructureServiceRegistrations.cs new file mode 100644 index 0000000..e587344 --- /dev/null +++ b/OrderTrackingApp.Infrastructure/Extensions/InfrastructureServiceRegistrations.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using OrderTrackingApp.Infrastructure.EventHandlers; +using OrderTrackingApp.Infrastructure.Messaging; + +namespace OrderTrackingApp.Infrastructure.Extensions +{ + public static class InfrastructureServiceRegistrations + { + public static IServiceCollection AddInfrastructureServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(OrderCreatedEventHandler).Assembly)); + + return services; + } + } +} diff --git a/OrderTrackingApp.Infrastructure/Messaging/IMessagePublisher.cs b/OrderTrackingApp.Infrastructure/Messaging/IMessagePublisher.cs new file mode 100644 index 0000000..d2559e7 --- /dev/null +++ b/OrderTrackingApp.Infrastructure/Messaging/IMessagePublisher.cs @@ -0,0 +1,7 @@ +namespace OrderTrackingApp.Infrastructure.Messaging +{ + public interface IMessagePublisher + { + Task PublishAsync(string queue, T message); + } +} diff --git a/OrderTrackingApp.Infrastructure/Messaging/RabbitMqPublisher.cs b/OrderTrackingApp.Infrastructure/Messaging/RabbitMqPublisher.cs new file mode 100644 index 0000000..cfd908f --- /dev/null +++ b/OrderTrackingApp.Infrastructure/Messaging/RabbitMqPublisher.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Configuration; +using RabbitMQ.Client; +using System.Text; +using System.Text.Json; + +namespace OrderTrackingApp.Infrastructure.Messaging +{ + internal class RabbitMqPublisher : IMessagePublisher + { + private readonly IConnection _connection; + + public RabbitMqPublisher(IConfiguration configuration) + { + var connectionString = configuration.GetConnectionString("RabbitMq"); + if (string.IsNullOrWhiteSpace(connectionString)) + { + throw new Exception("RabbitMQ connection string is not configured."); + } + + var factory = new ConnectionFactory + { + Uri = new Uri(connectionString) + }; + + _connection = factory.CreateConnectionAsync().GetAwaiter().GetResult(); + } + + public async Task PublishAsync(string topic, T message) + { + using var channel = await _connection.CreateChannelAsync(); + await channel.QueueDeclareAsync(queue: topic, durable: true, exclusive: false, autoDelete: false); + + var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message)); + await channel.BasicPublishAsync(exchange: "", routingKey: topic, body: body); + } + + } +} diff --git a/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj b/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj index fa71b7a..ed80f58 100644 --- a/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj +++ b/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj @@ -6,4 +6,14 @@ enable + + + + + + + + + + diff --git a/OrderTrackingApp.MigrationRunner/Dockerfile b/OrderTrackingApp.MigrationRunner/Dockerfile new file mode 100644 index 0000000..2e7e226 --- /dev/null +++ b/OrderTrackingApp.MigrationRunner/Dockerfile @@ -0,0 +1,31 @@ +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +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.Domain/OrderTrackingApp.Domain.csproj", "OrderTrackingApp.Domain/"] + +# Copy your root certificate into the container +COPY nscacert.crt /usr/local/share/ca-certificates/nscacert.crt + +# Update the certificate store +RUN update-ca-certificates + +RUN dotnet restore "OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj" + +COPY . . + +RUN dotnet build "OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj" -c Release -o /app/build --no-restore + +FROM build AS publish +RUN dotnet publish "OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +ENTRYPOINT ["dotnet", "OrderTrackingApp.MigrationRunner.dll"] \ No newline at end of file diff --git a/OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj b/OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj new file mode 100644 index 0000000..117f12c --- /dev/null +++ b/OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj @@ -0,0 +1,30 @@ + + + + Exe + net8.0 + enable + enable + Linux + + + + + + + + + Always + + + + + + + + + + + + + diff --git a/OrderTrackingApp.MigrationRunner/ProductSeeder.cs b/OrderTrackingApp.MigrationRunner/ProductSeeder.cs new file mode 100644 index 0000000..15c49bf --- /dev/null +++ b/OrderTrackingApp.MigrationRunner/ProductSeeder.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore; +using OrderTrackingApp.Domain.Entities; +using OrderTrackingApp.Persistence; + +namespace OrderTrackingApp.MigrationRunner +{ + internal static class ProductSeeder + { + public static async Task SeedAsync(AppDbContext context, CancellationToken cancellationToken = default) + { + if (await context.Products.AnyAsync(cancellationToken)) + { + return; // Already seeded + } + + var products = new List + { + new() + { + Id = Guid.Parse("b111a4dd-f45c-4d40-a6a3-3002f62f823f"), + Name = "Wireless Mouse", + Sku = "MSE-001", + Description = "Ergonomic wireless mouse", + Price = 29.99m, + StockQuantity = 100 + }, + new() + { + Id = Guid.Parse("2a13551b-059c-4035-b275-8a8cb82bd272"), + Name = "Mechanical Keyboard", + Sku = "KEY-101", + Description = "RGB mechanical keyboard", + Price = 79.99m, + StockQuantity = 50 + } + }; + + context.Products.AddRange(products); + await context.SaveChangesAsync(cancellationToken); + } + } +} diff --git a/OrderTrackingApp.MigrationRunner/Program.cs b/OrderTrackingApp.MigrationRunner/Program.cs new file mode 100644 index 0000000..4aa3674 --- /dev/null +++ b/OrderTrackingApp.MigrationRunner/Program.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OrderTrackingApp.MigrationRunner; +using OrderTrackingApp.Persistence; + +var host = Host.CreateDefaultBuilder(args) + .ConfigureServices((context, services) => + { + var connStr = context.Configuration["ConnectionStrings:SqlConnection"]; + services.AddDbContext(options => + options.UseSqlServer(connStr)); + }) + .Build(); + +using var scope = host.Services.CreateScope(); +var db = scope.ServiceProvider.GetRequiredService(); +db.Database.Migrate(); + +await ProductSeeder.SeedAsync(db); + diff --git a/OrderTrackingApp.MigrationRunner/Properties/launchSettings.json b/OrderTrackingApp.MigrationRunner/Properties/launchSettings.json new file mode 100644 index 0000000..139a592 --- /dev/null +++ b/OrderTrackingApp.MigrationRunner/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "OrderTrackingApp.MigrationRunner": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/OrderTrackingApp.MigrationRunner/appsettings.json b/OrderTrackingApp.MigrationRunner/appsettings.json new file mode 100644 index 0000000..ab66b29 --- /dev/null +++ b/OrderTrackingApp.MigrationRunner/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "SqlConnection": "Server=localhost,1433;Database=OrderDb;User=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True" + } +} diff --git a/OrderTrackingApp.Persistence.Tests/OrderTrackingApp.Persistence.Tests.csproj b/OrderTrackingApp.Persistence.Tests/OrderTrackingApp.Persistence.Tests.csproj new file mode 100644 index 0000000..034bcb2 --- /dev/null +++ b/OrderTrackingApp.Persistence.Tests/OrderTrackingApp.Persistence.Tests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/OrderTrackingApp.Persistence.Tests/UnitTest1.cs b/OrderTrackingApp.Persistence.Tests/UnitTest1.cs new file mode 100644 index 0000000..9376a2a --- /dev/null +++ b/OrderTrackingApp.Persistence.Tests/UnitTest1.cs @@ -0,0 +1,44 @@ +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/AppDbContext.cs b/OrderTrackingApp.Persistence/AppDbContext.cs index c825e68..bb81642 100644 --- a/OrderTrackingApp.Persistence/AppDbContext.cs +++ b/OrderTrackingApp.Persistence/AppDbContext.cs @@ -3,20 +3,65 @@ namespace OrderTrackingApp.Persistence { - public class AppDbContext : DbContext + public class AppDbContext(DbContextOptions options) : DbContext(options) { - public AppDbContext(DbContextOptions options) : base(options) { } - public DbSet Orders => Set(); + public DbSet Products => Set(); + protected override void OnModelCreating(ModelBuilder modelBuilder) { + // Order modelBuilder.Entity(entity => { entity.HasKey(x => x.Id); + entity.Property(x => x.OrderNumber) + .IsRequired() + .HasMaxLength(50); + entity.Property(x => x.Status) - .HasConversion(); // maps OrderStatus enum to string + .HasConversion(); // Enum -> string + + entity.Property(x => x.OrderDate) + .IsRequired(); + + // OrderItem - owned collection + entity.OwnsMany(x => x.Items, items => + { + items.WithOwner().HasForeignKey("OrderId"); + items.HasKey("OrderId", "ProductId"); // Composite Key + + items.Property(x => x.ProductId).IsRequired(); + items.Property(x => x.Quantity).IsRequired(); + items.Property(x => x.UnitPrice).IsRequired().HasColumnType("decimal(18,2)"); + items.Ignore(x => x.TotalPrice); // Computed property + items.ToTable("OrderItems"); + }); + }); + + // Product + modelBuilder.Entity(entity => + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.Name) + .IsRequired() + .HasMaxLength(100); + + entity.Property(x => x.Sku) + .IsRequired() + .HasMaxLength(50); + + entity.Property(x => x.Description) + .HasMaxLength(500); + + entity.Property(x => x.Price) + .IsRequired() + .HasColumnType("decimal(18,2)"); + + entity.Property(x => x.StockQuantity) + .IsRequired(); }); } } diff --git a/OrderTrackingApp.Persistence/DesignTimeDbContextFactory.cs b/OrderTrackingApp.Persistence/DesignTimeDbContextFactory.cs new file mode 100644 index 0000000..b316aa2 --- /dev/null +++ b/OrderTrackingApp.Persistence/DesignTimeDbContextFactory.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace OrderTrackingApp.Persistence +{ + public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory + { + public AppDbContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlServer("Server=localhost,1433;Database=OrderDb;User=sa;Password=YourStrong!Pass123;"); + + return new AppDbContext(optionsBuilder.Options); + } + } +} diff --git a/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs b/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistrations.cs similarity index 84% rename from OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs rename to OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistrations.cs index 72bcb90..aa8ac99 100644 --- a/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistration.cs +++ b/OrderTrackingApp.Persistence/Extensions/PersistenceServiceRegistrations.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using OrderTrackingApp.Application.Interfaces; +using OrderTrackingApp.Domain.Interfaces; using OrderTrackingApp.Persistence.Repositories; namespace OrderTrackingApp.Persistence.Extensions @@ -11,9 +12,10 @@ public static class PersistenceServiceRegistration public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration config) { services.AddDbContext(options => - options.UseSqlServer(config.GetConnectionString("DefaultConnection"))); + options.UseSqlServer(config.GetConnectionString("SqlConnection"))); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/OrderTrackingApp.Persistence/Migrations/20250716055404_InitSchema.Designer.cs b/OrderTrackingApp.Persistence/Migrations/20250716055404_InitSchema.Designer.cs new file mode 100644 index 0000000..c52b9a4 --- /dev/null +++ b/OrderTrackingApp.Persistence/Migrations/20250716055404_InitSchema.Designer.cs @@ -0,0 +1,116 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OrderTrackingApp.Persistence; + +#nullable disable + +namespace OrderTrackingApp.Persistence.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20250716055404_InitSchema")] + partial class InitSchema + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.18") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("OrderTrackingApp.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("OrderNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("OrderTrackingApp.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("OrderTrackingApp.Domain.Entities.Order", b => + { + b.OwnsMany("OrderTrackingApp.Domain.Entities.OrderItem", "Items", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("ProductId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b1.HasKey("OrderId", "ProductId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OrderTrackingApp.Persistence/Migrations/20250716055404_InitSchema.cs b/OrderTrackingApp.Persistence/Migrations/20250716055404_InitSchema.cs new file mode 100644 index 0000000..cc9a3cf --- /dev/null +++ b/OrderTrackingApp.Persistence/Migrations/20250716055404_InitSchema.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OrderTrackingApp.Persistence.Migrations +{ + /// + public partial class InitSchema : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + OrderNumber = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + CustomerId = table.Column(type: "uniqueidentifier", nullable: false), + OrderDate = table.Column(type: "datetime2", nullable: false), + Status = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Sku = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + Price = table.Column(type: "decimal(18,2)", nullable: false), + StockQuantity = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + ProductId = table.Column(type: "uniqueidentifier", nullable: false), + OrderId = table.Column(type: "uniqueidentifier", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => new { x.OrderId, x.ProductId }); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Products"); + + migrationBuilder.DropTable( + name: "Orders"); + } + } +} diff --git a/OrderTrackingApp.Persistence/Migrations/AppDbContextModelSnapshot.cs b/OrderTrackingApp.Persistence/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..834383a --- /dev/null +++ b/OrderTrackingApp.Persistence/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,113 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OrderTrackingApp.Persistence; + +#nullable disable + +namespace OrderTrackingApp.Persistence.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.18") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("OrderTrackingApp.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("OrderNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("OrderTrackingApp.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("OrderTrackingApp.Domain.Entities.Order", b => + { + b.OwnsMany("OrderTrackingApp.Domain.Entities.OrderItem", "Items", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("ProductId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b1.HasKey("OrderId", "ProductId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj b/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj index e9d2b4e..ba93773 100644 --- a/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj +++ b/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj @@ -8,6 +8,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs b/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs index 7a89df6..c660a71 100644 --- a/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs +++ b/OrderTrackingApp.Persistence/Repositories/OrderWriteRepository.cs @@ -4,7 +4,7 @@ namespace OrderTrackingApp.Persistence.Repositories { - internal class OrderWriteRepository(AppDbContext dbContext) : IOrderWriteRepository + public class OrderWriteRepository(AppDbContext dbContext) : IOrderWriteRepository { public async Task AddAsync(Order order) { @@ -28,5 +28,10 @@ public async Task UpdateAsync(Order order) dbContext.Orders.Update(order); await dbContext.SaveChangesAsync(); } + + public async Task GetByIdAsync(Guid id) + { + return await dbContext.Orders.FirstOrDefaultAsync(x => x.Id == id); + } } } diff --git a/OrderTrackingApp.Persistence/Repositories/ProductRepository.cs b/OrderTrackingApp.Persistence/Repositories/ProductRepository.cs new file mode 100644 index 0000000..3ad607d --- /dev/null +++ b/OrderTrackingApp.Persistence/Repositories/ProductRepository.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; +using OrderTrackingApp.Domain.Entities; +using OrderTrackingApp.Domain.Interfaces; + +namespace OrderTrackingApp.Persistence.Repositories +{ + public class ProductRepository(AppDbContext appDbContext) : IProductRepository + { + public Task AddAsync(Product product) + { + throw new NotImplementedException(); + } + + public Task GetByIdAsync(Guid id) + { + throw new NotImplementedException(); + } + + public Task> GetByIdsAsync(IEnumerable ids) + { + return appDbContext.Products.Where(p => ids.Any(id => id == p.Id)).ToListAsync(); + } + + public Task UpdateAsync(Product product) + { + throw new NotImplementedException(); + } + + public async Task UpdateProducts(List updatedProducts) + { + appDbContext.Products.UpdateRange(updatedProducts); + + await appDbContext.SaveChangesAsync(); + } + } +} diff --git a/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs b/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs index f08fe3f..02d71ac 100644 --- a/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs +++ b/OrderTrackingApp.ReadPersistence/Extensions/ReadPersistenceServiceRegistration.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using OrderTrackingApp.ReadPersistence.Interfaces; using OrderTrackingApp.ReadPersistence.Repositories; @@ -7,7 +6,7 @@ namespace OrderTrackingApp.ReadPersistence.Extensions { public static class ReadPersistenceServiceRegistration { - public static IServiceCollection AddReadPersistence(this IServiceCollection services, IConfiguration config) + public static IServiceCollection AddReadPersistence(this IServiceCollection services) { services.AddSingleton(); services.AddScoped(); diff --git a/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs b/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs index bac5b4f..93a1b2f 100644 --- a/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs +++ b/OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs @@ -1,11 +1,13 @@ -using OrderTrackingApp.Application.DTOs; +using OrderTrackingApp.ReadPersistence.Models; namespace OrderTrackingApp.ReadPersistence.Interfaces { public interface IOrderReadRepository { - Task GetByIdAsync(Guid id); + Task GetByIdAsync(Guid id); - Task> GetAllAsync(); + Task> GetAllAsync(); + + Task InsertAsync(OrderReadModel order); } } diff --git a/OrderTrackingApp.ReadPersistence/Models/OrderItemReadModel.cs b/OrderTrackingApp.ReadPersistence/Models/OrderItemReadModel.cs new file mode 100644 index 0000000..2b70674 --- /dev/null +++ b/OrderTrackingApp.ReadPersistence/Models/OrderItemReadModel.cs @@ -0,0 +1,11 @@ +namespace OrderTrackingApp.ReadPersistence.Models +{ + public class OrderItemReadModel + { + public Guid ProductId { get; set; } + + public int Quantity { get; set; } + + public decimal UnitPrice { get; set; } + } +} \ No newline at end of file diff --git a/OrderTrackingApp.ReadPersistence/Models/OrderReadModel.cs b/OrderTrackingApp.ReadPersistence/Models/OrderReadModel.cs new file mode 100644 index 0000000..b5bfaa3 --- /dev/null +++ b/OrderTrackingApp.ReadPersistence/Models/OrderReadModel.cs @@ -0,0 +1,17 @@ +using OrderTrackingApp.Domain.Entities; + +namespace OrderTrackingApp.ReadPersistence.Models +{ + public class OrderReadModel + { + public Guid Id { get; set; } + + public Guid CustomerId { get; set; } + + public OrderStatus Status { get; set; } + + public DateTime CreatedAt { get; set; } + + public List Items { get; set; } = []; + } +} \ No newline at end of file diff --git a/OrderTrackingApp.ReadPersistence/MongoDbContext.cs b/OrderTrackingApp.ReadPersistence/MongoDbContext.cs index 5635d56..179e539 100644 --- a/OrderTrackingApp.ReadPersistence/MongoDbContext.cs +++ b/OrderTrackingApp.ReadPersistence/MongoDbContext.cs @@ -3,7 +3,7 @@ namespace OrderTrackingApp.ReadPersistence { - internal class MongoDbContext + public class MongoDbContext { public IMongoDatabase Database { get; } diff --git a/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs b/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs index 9d55de0..de4a620 100644 --- a/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs +++ b/OrderTrackingApp.ReadPersistence/Repositories/OrderReadRepository.cs @@ -1,26 +1,26 @@ using MongoDB.Driver; -using OrderTrackingApp.Application.DTOs; using OrderTrackingApp.ReadPersistence.Interfaces; +using OrderTrackingApp.ReadPersistence.Models; namespace OrderTrackingApp.ReadPersistence.Repositories { - internal class OrderReadRepository : IOrderReadRepository + public class OrderReadRepository(MongoDbContext context) : IOrderReadRepository { - private readonly IMongoCollection _collection; + private readonly IMongoCollection _collection = context.Database.GetCollection("Orders"); - public OrderReadRepository(MongoDbContext context) + public async Task> GetAllAsync() { - _collection = context.Database.GetCollection("Orders"); + return await _collection.Find(_ => true).ToListAsync(); } - public async Task> GetAllAsync() + public async Task GetByIdAsync(Guid id) { - return await _collection.Find(_ => true).ToListAsync(); + return await _collection.Find(o => o.Id == id).FirstOrDefaultAsync(); } - public async Task GetByIdAsync(Guid id) + public async Task InsertAsync(OrderReadModel order) { - return await _collection.Find(o => o.Id == id).FirstOrDefaultAsync(); + await _collection.InsertOneAsync(order); } } } diff --git a/OrderTrackingApp.sln b/OrderTrackingApp.sln index 3792842..008bed0 100644 --- a/OrderTrackingApp.sln +++ b/OrderTrackingApp.sln @@ -30,6 +30,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Ser EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Blazor.Client", "OrderTrackingApp.Blazor.Client\OrderTrackingApp.Blazor.Client.csproj", "{43A85AAD-5188-4103-BEF6-F88971B638F2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.Persistence.Tests", "OrderTrackingApp.Persistence.Tests\OrderTrackingApp.Persistence.Tests.csproj", "{77A97D9B-3156-47EE-91A1-8D6F0D9BABE3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTrackingApp.MigrationRunner", "OrderTrackingApp.MigrationRunner\OrderTrackingApp.MigrationRunner.csproj", "{C9DE7296-2132-490F-97DE-96FF65C7A5BB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,6 +76,14 @@ Global {43A85AAD-5188-4103-BEF6-F88971B638F2}.Debug|Any CPU.Build.0 = Debug|Any CPU {43A85AAD-5188-4103-BEF6-F88971B638F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {43A85AAD-5188-4103-BEF6-F88971B638F2}.Release|Any CPU.Build.0 = Release|Any CPU + {77A97D9B-3156-47EE-91A1-8D6F0D9BABE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77A97D9B-3156-47EE-91A1-8D6F0D9BABE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77A97D9B-3156-47EE-91A1-8D6F0D9BABE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77A97D9B-3156-47EE-91A1-8D6F0D9BABE3}.Release|Any CPU.Build.0 = Release|Any CPU + {C9DE7296-2132-490F-97DE-96FF65C7A5BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -86,6 +98,8 @@ Global {773AD28D-63B8-4A78-90D0-A5282F3BAFDD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {1FD0A91F-99A3-4D45-AA2B-3081183FEAC3} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECFA56EC-C322-4A64-8936-E930A2EEBE28} From 835d33a820562e49912bf988951dc8758e8c8f73 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Sun, 20 Jul 2025 18:55:07 +1000 Subject: [PATCH 13/17] fix mongo db guid issue (#8) --- OrderTrackingApp.Consumer/appsettings.json | 2 +- OrderTrackingApp.ReadPersistence/MongoDbContext.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/OrderTrackingApp.Consumer/appsettings.json b/OrderTrackingApp.Consumer/appsettings.json index 576f5d9..ba98022 100644 --- a/OrderTrackingApp.Consumer/appsettings.json +++ b/OrderTrackingApp.Consumer/appsettings.json @@ -7,7 +7,7 @@ }, "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.ReadPersistence/MongoDbContext.cs b/OrderTrackingApp.ReadPersistence/MongoDbContext.cs index 179e539..cf395fc 100644 --- a/OrderTrackingApp.ReadPersistence/MongoDbContext.cs +++ b/OrderTrackingApp.ReadPersistence/MongoDbContext.cs @@ -1,4 +1,7 @@ using Microsoft.Extensions.Configuration; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver; namespace OrderTrackingApp.ReadPersistence @@ -9,6 +12,8 @@ public class MongoDbContext public MongoDbContext(IConfiguration config) { + BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard)); + var connectionString = config.GetConnectionString("MongoDb"); var mongoClient = new MongoClient(connectionString); Database = mongoClient.GetDatabase("OrderReadDb"); From 6afa94f74290150c8848d9a67ba92c1b66a192a3 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Mon, 21 Jul 2025 19:58:01 +1000 Subject: [PATCH 14/17] added scalar api example (#9) Co-authored-by: Parag Naikade --- .../Controllers/OrdersController.cs | 5 ++++ .../Examples/CreateOrderRequestExample.cs | 28 +++++++++++++++++++ .../Extensions/ApiServiceRegistrations.cs | 9 +++++- .../OrderTrackingApp.Api.csproj | 1 + OrderTrackingApp.Api/WeatherForecast.cs | 13 --------- 5 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 OrderTrackingApp.Api/Examples/CreateOrderRequestExample.cs delete mode 100644 OrderTrackingApp.Api/WeatherForecast.cs diff --git a/OrderTrackingApp.Api/Controllers/OrdersController.cs b/OrderTrackingApp.Api/Controllers/OrdersController.cs index 13dd792..4778258 100644 --- a/OrderTrackingApp.Api/Controllers/OrdersController.cs +++ b/OrderTrackingApp.Api/Controllers/OrdersController.cs @@ -1,8 +1,10 @@ using AutoMapper; using MediatR; using Microsoft.AspNetCore.Mvc; +using OrderTrackingApp.Api.Examples; using OrderTrackingApp.Api.Models; using OrderTrackingApp.Application.Commands.Orders; +using Swashbuckle.AspNetCore.Filters; namespace OrderTrackingApp.Api.Controllers { @@ -11,6 +13,9 @@ namespace OrderTrackingApp.Api.Controllers public class OrdersController(IMediator mediator, IMapper mapper) : ControllerBase { [HttpPost] + + [SwaggerRequestExample(typeof(CreateOrderRequest), typeof(CreateOrderRequestExample))] + [ProducesResponseType(StatusCodes.Status201Created)] public async Task CreateOrder([FromBody] CreateOrderRequest request) { var command = mapper.Map(request); diff --git a/OrderTrackingApp.Api/Examples/CreateOrderRequestExample.cs b/OrderTrackingApp.Api/Examples/CreateOrderRequestExample.cs new file mode 100644 index 0000000..bcbc646 --- /dev/null +++ b/OrderTrackingApp.Api/Examples/CreateOrderRequestExample.cs @@ -0,0 +1,28 @@ +using OrderTrackingApp.Api.Models; +using Swashbuckle.AspNetCore.Filters; + +namespace OrderTrackingApp.Api.Examples; + +public class CreateOrderRequestExample : IExamplesProvider +{ + public CreateOrderRequest GetExamples() + { + return new CreateOrderRequest + { + CustomerId = Guid.NewGuid(), + Items = new List + { + new CreateOrderItemRequest + { + ProductId = Guid.Parse("b111a4dd-f45c-4d40-a6a3-3002f62f823f"), + Quantity = 2 + }, + new CreateOrderItemRequest + { + ProductId = Guid.Parse("2a13551b-059c-4035-b275-8a8cb82bd272"), + Quantity = 3 + } + } + }; + } +} diff --git a/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs b/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs index d0d3e4f..aaa2245 100644 --- a/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs +++ b/OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs @@ -1,6 +1,8 @@ using FluentValidation; +using OrderTrackingApp.Api.Examples; using OrderTrackingApp.Api.MappingProfiles; using OrderTrackingApp.Api.Validators; +using Swashbuckle.AspNetCore.Filters; using System.Text.Json; namespace OrderTrackingApp.Api.Extensions @@ -10,7 +12,12 @@ public static class ApiServiceRegistrations public static IServiceCollection AddApiServices(this IServiceCollection services) { services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(); + + services.AddSwaggerGen(options => { + options.ExampleFilters(); + }); + + services.AddSwaggerExamplesFromAssemblyOf(); services.AddControllers() .AddJsonOptions(options => diff --git a/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj b/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj index 2dfe61f..a6aebfd 100644 --- a/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj +++ b/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj @@ -15,6 +15,7 @@ + diff --git a/OrderTrackingApp.Api/WeatherForecast.cs b/OrderTrackingApp.Api/WeatherForecast.cs deleted file mode 100644 index f9643e9..0000000 --- a/OrderTrackingApp.Api/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace OrderTrackingApp.Api -{ - public class WeatherForecast - { - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -} From b3c64d813d9cd18d42b60c3de540b58cd508bb0b Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Tue, 22 Jul 2025 17:10:59 +1000 Subject: [PATCH 15/17] Get Order API (#10) * 1 * fix build * fix * fix --------- Co-authored-by: Parag Naikade --- .../Controllers/OrdersController.cs | 19 +++++- OrderTrackingApp.Api/Dockerfile | 1 + .../Extensions/ApiServiceRegistrations.cs | 2 +- ...OrderMappingProfile.cs => OrderProfile.cs} | 9 ++- .../Models/GetOrdersRequest.cs | 15 +++++ OrderTrackingApp.Api/appsettings.json | 2 +- ...erTrackingApp.Application.Contracts.csproj | 13 ++++ .../Orders/IOrderReadRepository.cs | 16 +++++ .../Orders/IOrderWriteRepository.cs | 13 ++++ .../Orders/OrderDto.cs | 28 +++++++++ .../PaginatedResult.cs | 13 ++++ OrderTrackingApp.Application/DTOs/OrderDto.cs | 15 ----- .../ApplicationServiceRegistrations.cs | 4 +- .../Interfaces/IOrderWriteRepository.cs | 15 ----- .../MappingProfiles/OrderProfile.cs | 15 +++++ .../OrderTrackingApp.Application.csproj | 2 + .../Commands}/CreateOrderCommand.cs | 2 +- .../Commands}/CreateOrderCommandHandler.cs | 14 ++--- .../Orders/Queries/GetOrderByIdQuery.cs | 7 +++ .../Queries/GetOrderByIdQueryHandler.cs | 14 +++++ .../Orders/Queries/GetOrdersQuery.cs | 19 ++++++ .../Orders/Queries/GetOrdersQueryHandler.cs | 20 ++++++ OrderTrackingApp.Consumer/Dockerfile | 1 + .../OrderTrackingApp.Consumer.csproj | 1 + OrderTrackingApp.Consumer/Program.cs | 2 + OrderTrackingApp.Consumer/Worker.cs | 38 +++--------- OrderTrackingApp.MigrationRunner/Dockerfile | 1 + .../ProductSeeder.cs | 4 +- .../UnitTest1.cs | 44 ------------- .../PersistenceServiceRegistrations.cs | 2 +- .../OrderTrackingApp.Persistence.csproj | 1 + .../Repositories/OrderWriteRepository.cs | 23 ++++--- .../ReadPersistenceServiceRegistration.cs | 4 +- .../Interfaces/IOrderReadRepository.cs | 13 ---- .../MappingProfiles/OrderProfile.cs | 15 +++++ .../OrderTrackingApp.ReadPersistence.csproj | 1 + .../Repositories/OrderReadRepository.cs | 62 ++++++++++++++++--- OrderTrackingApp.sln | 7 +++ 38 files changed, 325 insertions(+), 152 deletions(-) rename OrderTrackingApp.Api/MappingProfiles/{OrderMappingProfile.cs => OrderProfile.cs} (54%) create mode 100644 OrderTrackingApp.Api/Models/GetOrdersRequest.cs create mode 100644 OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj create mode 100644 OrderTrackingApp.Application.Contracts/Orders/IOrderReadRepository.cs create mode 100644 OrderTrackingApp.Application.Contracts/Orders/IOrderWriteRepository.cs create mode 100644 OrderTrackingApp.Application.Contracts/Orders/OrderDto.cs create mode 100644 OrderTrackingApp.Application.Contracts/PaginatedResult.cs delete mode 100644 OrderTrackingApp.Application/DTOs/OrderDto.cs delete mode 100644 OrderTrackingApp.Application/Interfaces/IOrderWriteRepository.cs create mode 100644 OrderTrackingApp.Application/MappingProfiles/OrderProfile.cs rename OrderTrackingApp.Application/{Commands/Orders => Orders/Commands}/CreateOrderCommand.cs (85%) rename OrderTrackingApp.Application/{Commands/Orders => Orders/Commands}/CreateOrderCommandHandler.cs (83%) create mode 100644 OrderTrackingApp.Application/Orders/Queries/GetOrderByIdQuery.cs create mode 100644 OrderTrackingApp.Application/Orders/Queries/GetOrderByIdQueryHandler.cs create mode 100644 OrderTrackingApp.Application/Orders/Queries/GetOrdersQuery.cs create mode 100644 OrderTrackingApp.Application/Orders/Queries/GetOrdersQueryHandler.cs delete mode 100644 OrderTrackingApp.Persistence.Tests/UnitTest1.cs delete mode 100644 OrderTrackingApp.ReadPersistence/Interfaces/IOrderReadRepository.cs create mode 100644 OrderTrackingApp.ReadPersistence/MappingProfiles/OrderProfile.cs 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} From f2a2658a84ffb051773f51ce86de6a434eed5af5 Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Tue, 29 Jul 2025 12:31:00 +1000 Subject: [PATCH 16/17] Update README.md (#11) --- README.md | 119 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 76ed2a7..c6669d0 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,112 @@ [![Parallel Docker Builds with Cache](https://github.com/ParagNaikade/OrderTrackingApp/actions/workflows/ci-pipeline.yml/badge.svg)](https://github.com/ParagNaikade/OrderTrackingApp/actions/workflows/ci-pipeline.yml) -# 🧱 OrderTrackingApp - Docker Commands +# 🧾 Order Tracking System -This section lists the Docker build and run commands for each project in the solution. +A distributed order processing system built with **.NET Clean Architecture**, featuring **CQRS**, **RabbitMQ messaging**, and dual persistence in **SQL Server** and **MongoDB**. + +This project demonstrates a scalable microservice pattern using domain events and message queues to decouple the system. --- -## 🐳 Docker Commands for Local Development +## 📐 Architecture Overview -### 📦 `OrderTrackingApp.Api` +``` +API ──> Application ──> Infra ──> RabbitMQ ──> Consumer + │ │ + SQL Server MongoDB +``` -```bash -# Build -docker build -f OrderTrackingApp.Api/Dockerfile -t ordertrackingapp-api . +### 🔄 Flow Description -# Run - docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Api -p 5000:8080 mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run --urls=http://0.0.0.0:8080 +1. **API** + - Exposes endpoints to create and retrieve orders. + - Triggers a `SaveOrderCommand` when a POST request is received. -# Run within corporate network where internet is not available and need to use company certificates -docker run -it --rm -v "$(Get-Location):/app" -v "$(Get-Location)/nscacert.crt:/usr/local/share/ca-certificates/nscacert.crt:ro" -w /app/OrderTrackingApp.Api -p 5000:8080 mcr.microsoft.com/dotnet/sdk:8.0 bash -c "update-ca-certificates && dotnet watch run --urls=http://0.0.0.0:8080" -``` +2. **Application** + - Implements **CQRS** to separate command and query logic. + - Handles business rules (e.g., inventory validation). + - Saves the order to **SQL Server**. + - Raises an `OrderCreatedEvent` as a domain event. -### 📦 `OrderTrackingApp.Consumer` +3. **Infra** + - Listens for domain events. + - Publishes messages to **RabbitMQ** queue (`order.created`). -```bash -# Build -docker build -f OrderTrackingApp.Consumer/Dockerfile -t ordertrackingapp-consumer . +4. **Consumer** + - Subscribes to the RabbitMQ queue. + - Reads order details from SQL. + - Syncs the order into **MongoDB** for read-side optimization or analytics. + +--- -# Run - docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Consumer mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run +## 🛠️ Tech Stack + +| Layer | Technology | +|--------------|--------------------------------| +| API | ASP.NET Core Web API | +| Application | MediatR, CQRS, FluentValidation| +| Infra | Entity Framework Core, RabbitMQ| +| Consumer | Background Worker, MongoDB | +| Messaging | RabbitMQ | +| Databases | SQL Server, MongoDB | +| Architecture | Clean Architecture | +| DevOps | Docker, GitHub Actions (CI/CD) | + +--- + +## 📂 Project Structure -# Run within corporate network where internet is not available and need to use company certificates - docker run -it --rm -v "$(Get-Location):/app" -v "$(Get-Location)/nscacert.crt:/usr/local/share/ca-certificates/nscacert.crt:ro" -w /app/OrderTrackingApp.Consumer mcr.microsoft.com/dotnet/sdk:8.0 bash -c "update-ca-certificates && dotnet watch run" +``` +OrderTrackingApp/ +├── API # Entry point +├── Application # CQRS, Commands/Queries, COre app logic +├── Application.Contracts # Interfaces, DTOs, Validation +├── Domain # Entities, Value Objects, Events +├── Infrastructure # Event Handlers, RabbitMQ +├── Consumer # Background service that syncs to MongoDB +├── ReadPersistence # MongoDB Read Models +├── Persistence # EF Core +└── docker-compose.yml # Services for RabbitMQ, SQL Server, MongoDB ``` -### 📦 `OrderTrackingApp.Blazor.Server` +--- -```bash -# Build -docker build -f OrderTrackingApp.Blazor.Server/Dockerfile -t ordertrackingapp-blazor-server . +## 🚀 Getting Started -# Run - docker run -it --rm -v "$(Get-Location):/app" -w /app/OrderTrackingApp.Blazor.Server mcr.microsoft.com/dotnet/sdk:8.0 dotnet watch run +### Prerequisites + +- [.NET 8 SDK](https://dotnet.microsoft.com/) +- [Docker](https://www.docker.com/) +- [MongoDB Compass](https://www.mongodb.com/products/compass) (optional for viewing MongoDB) +- [RabbitMQ Management UI](http://localhost:15672) (user: `guest`, password: `guest`) + +### Run Locally + +```bash +git clone https://github.com/ParagNaikade/order-tracking-app.git +cd order-tracking-app +docker-compose up --build ``` + +### Test API + +- `POST /api/orders` → Creates an order +- `GET /api/orders/{id}` → Retrieves an order +- `GET /api/orders` → Retrieves paginated list + +--- + +## 🧪 Features + +- ✅ Clean Architecture (Separation of Concerns) +- ✅ CQRS with MediatR +- ✅ Domain Events and Messaging +- ✅ Async Communication via RabbitMQ +- ✅ SQL Write DB & MongoDB Read DB (eventual consistency) +- ✅ Dockerized Microservices + +--- + +## 📬 Contact + +Created with ❤️ by [Parag Naikade](https://paragnaikade.com/) From 10a6b271e2f43513b71049ca52d534f64084a35d Mon Sep 17 00:00:00 2001 From: Parag Naikade Date: Fri, 5 Dec 2025 14:29:04 +1100 Subject: [PATCH 17/17] Upgrade to .net10 * First draft * Fix * updated dockerfiles * fix --- .vscode/launch.json | 12 +++++++ .vscode/tasks.json | 12 +++++++ Directory.Packages.props | 36 +++++++++++++++++++ OrderTrackingApp.Api/Dockerfile | 7 ++-- .../OrderTrackingApp.Api.csproj | 14 ++++---- ...erTrackingApp.Application.Contracts.csproj | 2 +- .../OrderTrackingApp.Application.csproj | 6 ++-- .../OrderTrackingApp.Blazor.Client.csproj | 4 +-- OrderTrackingApp.Blazor.Server/Dockerfile | 7 ++-- .../OrderTrackingApp.Blazor.Server.csproj | 4 +-- OrderTrackingApp.Consumer/Dockerfile | 7 ++-- .../OrderTrackingApp.Consumer.csproj | 6 ++-- .../OrderTrackingApp.Domain.csproj | 4 +-- .../OrderTrackingApp.Infrastructure.csproj | 8 ++--- OrderTrackingApp.MigrationRunner/Dockerfile | 7 ++-- .../OrderTrackingApp.MigrationRunner.csproj | 6 ++-- .../OrderTrackingApp.Persistence.Tests.csproj | 10 +++--- .../OrderTrackingApp.Persistence.csproj | 10 +++--- .../OrderTrackingApp.ReadPersistence.csproj | 6 ++-- OrderTrackingApp.SignalR/Class1.cs | 7 ---- .../OrderTrackingApp.SignalR.csproj | 9 ----- 21 files changed, 120 insertions(+), 64 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 Directory.Packages.props delete mode 100644 OrderTrackingApp.SignalR/Class1.cs delete mode 100644 OrderTrackingApp.SignalR/OrderTrackingApp.SignalR.csproj diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..992dcd4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..b1c79f6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,12 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "dotnet", + "task": "build", + "group": "build", + "problemMatcher": [], + "label": "dotnet: build" + } + ] +} \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..c1bc24b --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,36 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/OrderTrackingApp.Api/Dockerfile b/OrderTrackingApp.Api/Dockerfile index f4fe51f..85667b6 100644 --- a/OrderTrackingApp.Api/Dockerfile +++ b/OrderTrackingApp.Api/Dockerfile @@ -1,10 +1,13 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base WORKDIR /app EXPOSE 8080 -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src +# Copy root package props first +COPY ["Directory.Packages.props", "."] + 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/"] diff --git a/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj b/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj index a6aebfd..b4123b8 100644 --- a/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj +++ b/OrderTrackingApp.Api/OrderTrackingApp.Api.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable fd420179-d5b0-44df-b29b-018fbfbdb9f1 @@ -10,12 +10,12 @@ - - - - - - + + + + + + diff --git a/OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj b/OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj index 85ef9ce..55356d8 100644 --- a/OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj +++ b/OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable diff --git a/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj b/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj index eab0a09..3e668a6 100644 --- a/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj +++ b/OrderTrackingApp.Application/OrderTrackingApp.Application.csproj @@ -1,14 +1,14 @@  - net8.0 + net10.0 enable enable - - + + diff --git a/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj b/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj index 9f530b9..aaeb1dd 100644 --- a/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj +++ b/OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable true @@ -9,7 +9,7 @@ - + diff --git a/OrderTrackingApp.Blazor.Server/Dockerfile b/OrderTrackingApp.Blazor.Server/Dockerfile index 1e9cd1b..52f2bdb 100644 --- a/OrderTrackingApp.Blazor.Server/Dockerfile +++ b/OrderTrackingApp.Blazor.Server/Dockerfile @@ -1,9 +1,12 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY ["OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj", "OrderTrackingApp.Blazor.Server/"] COPY ["OrderTrackingApp.Blazor.Client/OrderTrackingApp.Blazor.Client.csproj", "OrderTrackingApp.Blazor.Client/"] +# Copy root package props first +COPY ["Directory.Packages.props", "."] + # Copy your root certificate into the container COPY nscacert.crt /usr/local/share/ca-certificates/nscacert.crt @@ -19,7 +22,7 @@ RUN dotnet build "OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server. FROM build AS publish RUN dotnet publish "OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj" -c Release -o /app/publish --no-restore -FROM mcr.microsoft.com/dotnet/aspnet:8.0 +FROM mcr.microsoft.com/dotnet/aspnet:10.0 WORKDIR /app COPY --from=publish /app/publish . diff --git a/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj b/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj index 88d4203..96d03b4 100644 --- a/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj +++ b/OrderTrackingApp.Blazor.Server/OrderTrackingApp.Blazor.Server.csproj @@ -1,13 +1,13 @@ - net8.0 + net10.0 enable enable - + diff --git a/OrderTrackingApp.Consumer/Dockerfile b/OrderTrackingApp.Consumer/Dockerfile index b4aa87a..9b2b11d 100644 --- a/OrderTrackingApp.Consumer/Dockerfile +++ b/OrderTrackingApp.Consumer/Dockerfile @@ -1,8 +1,11 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src +# Copy root package props first +COPY ["Directory.Packages.props", "."] + 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/"] diff --git a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj index 4140bee..bf42411 100644 --- a/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj +++ b/OrderTrackingApp.Consumer/OrderTrackingApp.Consumer.csproj @@ -1,15 +1,15 @@ - net8.0 + net10.0 enable enable dotnet-OrderTrackingApp.Consumer-4c972be3-521f-42ce-87d8-815ba1ccddd7 - - + + diff --git a/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj b/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj index db4c3f8..12bdd8c 100644 --- a/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj +++ b/OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj @@ -1,13 +1,13 @@  - net8.0 + net10.0 enable enable - + diff --git a/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj b/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj index ed80f58..2007db0 100644 --- a/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj +++ b/OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj @@ -1,15 +1,15 @@  - net8.0 + net10.0 enable enable - - - + + + diff --git a/OrderTrackingApp.MigrationRunner/Dockerfile b/OrderTrackingApp.MigrationRunner/Dockerfile index 1186c8e..bbfbb43 100644 --- a/OrderTrackingApp.MigrationRunner/Dockerfile +++ b/OrderTrackingApp.MigrationRunner/Dockerfile @@ -1,9 +1,12 @@ -FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +FROM mcr.microsoft.com/dotnet/runtime:10.0 AS base WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src +# Copy root package props first +COPY ["Directory.Packages.props", "."] + COPY ["OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj", "OrderTrackingApp.MigrationRunner/"] COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"] COPY ["OrderTrackingApp.Application/OrderTrackingApp.Application.csproj", "OrderTrackingApp.Application/"] diff --git a/OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj b/OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj index 117f12c..7ea555c 100644 --- a/OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj +++ b/OrderTrackingApp.MigrationRunner/OrderTrackingApp.MigrationRunner.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable Linux @@ -19,8 +19,8 @@ - - + + diff --git a/OrderTrackingApp.Persistence.Tests/OrderTrackingApp.Persistence.Tests.csproj b/OrderTrackingApp.Persistence.Tests/OrderTrackingApp.Persistence.Tests.csproj index 034bcb2..38307a0 100644 --- a/OrderTrackingApp.Persistence.Tests/OrderTrackingApp.Persistence.Tests.csproj +++ b/OrderTrackingApp.Persistence.Tests/OrderTrackingApp.Persistence.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj b/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj index edce8ab..3be187e 100644 --- a/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj +++ b/OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj @@ -1,19 +1,19 @@  - net8.0 + net10.0 enable enable - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj b/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj index 527abbf..112c0f0 100644 --- a/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj +++ b/OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj @@ -1,14 +1,14 @@  - net8.0 + net10.0 enable enable - - + + diff --git a/OrderTrackingApp.SignalR/Class1.cs b/OrderTrackingApp.SignalR/Class1.cs deleted file mode 100644 index 4247796..0000000 --- a/OrderTrackingApp.SignalR/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace OrderTrackingApp.SignalR -{ - public class Class1 - { - - } -} diff --git a/OrderTrackingApp.SignalR/OrderTrackingApp.SignalR.csproj b/OrderTrackingApp.SignalR/OrderTrackingApp.SignalR.csproj deleted file mode 100644 index fa71b7a..0000000 --- a/OrderTrackingApp.SignalR/OrderTrackingApp.SignalR.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net8.0 - enable - enable - - -