├── src ├── frontend │ ├── .dockerignore │ ├── src │ │ ├── components │ │ │ └── navbar │ │ │ │ ├── Index.css │ │ │ │ └── Index.jsx │ │ ├── App.css │ │ ├── App.jsx │ │ ├── pages │ │ │ ├── stock │ │ │ │ ├── Index.css │ │ │ │ └── ColumnDef.tsx │ │ │ └── report │ │ │ │ └── Index.tsx │ │ ├── hooks │ │ │ ├── useReport.ts │ │ │ ├── useOrder.ts │ │ │ ├── useProduct.ts │ │ │ └── useStock.ts │ │ ├── types │ │ │ └── Stock.type.ts │ │ ├── utils │ │ │ └── HttpClient.ts │ │ ├── main.jsx │ │ └── index.css │ ├── .env │ ├── nginx │ │ └── nginx.conf │ ├── .gitignore │ ├── index.html │ ├── .eslintrc.cjs │ ├── vite.config.js │ ├── Dockerfile │ ├── package.json │ ├── public │ │ └── vite.svg │ └── tsconfig.json ├── domains │ ├── NotSoSimpleEcommerce.Shared │ │ ├── InOut │ │ │ ├── Responses │ │ │ │ ├── TokenResponse.cs │ │ │ │ ├── ProductResponse.cs │ │ │ │ ├── StockResponse.cs │ │ │ │ └── OrderResponse.cs │ │ │ └── Requests │ │ │ │ ├── OrderRequest.cs │ │ │ │ └── AuthRequest.cs │ │ ├── Enums │ │ │ └── OrderStatus.cs │ │ ├── Events │ │ │ └── OrderConfirmedEvent.cs │ │ ├── Models │ │ │ ├── ProductEntity.cs │ │ │ ├── StockEntity.cs │ │ │ ├── StatusEntity.cs │ │ │ └── OrderEntity.cs │ │ ├── HttpHandlers │ │ │ └── Contracts │ │ │ │ ├── IIdentityServerApi.cs │ │ │ │ ├── IOrderApi.cs │ │ │ │ └── IMainApi.cs │ │ ├── Consts │ │ │ └── Email.cs │ │ ├── NotSoSimpleEcommerce.Shared.csproj │ │ └── Repositories │ │ │ └── Configurations │ │ │ └── ProductEntityTypeConfiguration.cs │ ├── NotSoSimpleEcommerce.Main.Domain │ │ ├── InOut │ │ │ └── Requests │ │ │ │ ├── StockRequest.cs │ │ │ │ └── ProductRequest.cs │ │ ├── Commands │ │ │ ├── DeleteProductStockCommand.cs │ │ │ ├── DeleteProductCommand.cs │ │ │ ├── RegisterProductCommand.cs │ │ │ ├── RegisterProductStockCommand.cs │ │ │ ├── UpdateProductCommand.cs │ │ │ ├── DeleteProductImageCommand.cs │ │ │ ├── UpdateProductImageCommand.cs │ │ │ └── UpdateProductStockCommand.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Repositories │ │ │ ├── Contracts │ │ │ │ └── IStockReadRepository.cs │ │ │ ├── Contexts │ │ │ │ └── ProductContext.cs │ │ │ ├── Implementations │ │ │ │ └── StockReadRepository.cs │ │ │ └── Configurations │ │ │ │ └── StockEntityTypeConfiguration.cs │ │ ├── Models │ │ │ └── ReportEntity.cs │ │ ├── CommandHandlers │ │ │ ├── DeleteProductImageCommandHandler.cs │ │ │ ├── RegisterProductCommandHandler.cs │ │ │ ├── DeleteProductCommandHandler.cs │ │ │ ├── DeleteProductStockCommandHandler.cs │ │ │ ├── UpdateProductCommandHandler.cs │ │ │ ├── UpdateProductImageCommandHandler.cs │ │ │ ├── UpdateProductStockCommandHandler.cs │ │ │ └── RegisterProductStockCommandHandler.cs │ │ ├── Mappings │ │ │ ├── EntityToResponseMapping.cs │ │ │ ├── CommandToEntityMapping.cs │ │ │ └── RequestToCommandMapping.cs │ │ ├── NotSoSimpleEcommerce.Main.Domain.csproj │ │ └── Migrations │ │ │ └── 20250108182910_InitialCreate.cs │ ├── NotSoSimpleEcommerce.IdentityServer.Domain │ │ ├── Services │ │ │ ├── Contracts │ │ │ │ └── IUserService.cs │ │ │ └── Implementations │ │ │ │ └── UserService.cs │ │ ├── Models │ │ │ └── UserEntity.cs │ │ ├── NotSoSimpleEcommerce.IdentityServer.Domain.csproj │ │ ├── Repositories │ │ │ ├── Configurations │ │ │ │ └── UserEntityTypeConfiguration.cs │ │ │ └── Contexts │ │ │ │ └── IdentityServerContext.cs │ │ └── Migrations │ │ │ ├── 20230720174257_InitialCreate.cs │ │ │ ├── IdentityServerContextModelSnapshot.cs │ │ │ └── 20230720174257_InitialCreate.Designer.cs │ └── NotSoSimpleEcommerce.Order.Domain │ │ ├── Commands │ │ ├── DeleteProductCommand.cs │ │ ├── CreateOrderCommand.cs │ │ └── UpdateProductCommand.cs │ │ ├── Repositories │ │ ├── Contracts │ │ │ └── IOrderReadRepository.cs │ │ ├── Configurations │ │ │ ├── StatusEntityTypeConfiguration.cs │ │ │ └── OrderEntityTypeConfiguration.cs │ │ ├── Implementations │ │ │ └── OrderReadRepository.cs │ │ └── Contexts │ │ │ └── OrderContext.cs │ │ ├── Events │ │ └── OrderCreatedEvent.cs │ │ ├── Mappings │ │ ├── ProductRequestToCommandMapping.cs │ │ ├── OrderCommandToEntityMapping.cs │ │ └── OrderEntityToResponseMapping.cs │ │ ├── CommandHandlers │ │ ├── DeleteOrderCommandHandler.cs │ │ └── UpdateOrderCommanHandler.cs │ │ ├── NotSoSimpleEcommerce.Order.Domain.csproj │ │ └── EventHandlers │ │ └── OrderCreatedEventHandler.cs ├── infrastructure │ ├── NotSoSimpleEcommerce.SnsHandler │ │ ├── Models │ │ │ └── AwsSnsMessageParams.cs │ │ ├── Abstractions │ │ │ └── IMessageSender.cs │ │ ├── NotSoSimpleEcommerce.SnsHandler.csproj │ │ ├── Exceptions │ │ │ └── AwsSnsMessageSenderException.cs │ │ └── Implementations │ │ │ └── AwsSnsMessageSender.cs │ ├── NotSoSimpleEcommerce.SesHandler │ │ ├── Enums │ │ │ └── BodyContentType.cs │ │ ├── Abstractions │ │ │ └── IEmailSender.cs │ │ ├── Models │ │ │ └── EmailParams.cs │ │ ├── NotSoSimpleEcommerce.SesHandler.csproj │ │ └── Implementations │ │ │ └── AwsSesEmailSender.cs │ ├── NotSoSimpleEcommerce.S3Handler │ │ ├── Models │ │ │ ├── AwsS3BucketParams.cs │ │ │ └── ObjectRegister.cs │ │ ├── Abstractions │ │ │ └── IObjectManager.cs │ │ └── NotSoSimpleEcommerce.S3Handler.csproj │ ├── NotSoSimpleEcommerce.SqsHandler │ │ ├── Abstractions │ │ │ ├── IMessageSender.cs │ │ │ └── IMessageProcessor.cs │ │ ├── Models │ │ │ ├── AwsQueueMessageParams.cs │ │ │ ├── AwsSqsMessageSenderParams.cs │ │ │ └── AwsSqsQueueMonitorParams.cs │ │ ├── Exceptions │ │ │ └── AwsSqsMessageSenderException.cs │ │ └── NotSoSimpleEcommerce.SqsHandler.csproj │ ├── NotSoSimpleEcommerce.Repositories │ │ ├── Contracts │ │ │ ├── ICreateEntityRepository.cs │ │ │ ├── IDeleteEntityRepository.cs │ │ │ ├── IUpdateEntityRepository.cs │ │ │ └── IGetEntityRepository.cs │ │ ├── NotSoSimpleEcommerce.Repositories.csproj │ │ └── Implementations │ │ │ ├── DeleteEntityRepository.cs │ │ │ ├── UpdateEntityRepository.cs │ │ │ ├── CreateEntityRepository.cs │ │ │ └── GetEntityRepository.cs │ └── NotSoSimpleEcommerce.Utils │ │ ├── NotSoSimpleEcommerce.Utils.csproj │ │ └── Encryption │ │ └── Hasher.cs ├── services │ ├── NotSoSimpleEcommerce.HealthChecker │ │ ├── appsettings.json │ │ ├── appsettings.Development.json │ │ ├── Dockerfile │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── NotSoSimpleEcommerce.HealthChecker.csproj │ │ └── Program.cs │ ├── NotSoSimpleEcommerce.Main │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── Dockerfile │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Controllers │ │ │ ├── StockController.cs │ │ │ └── ReportController.cs │ │ ├── Modules │ │ │ ├── DomainModule.cs │ │ │ └── AwsModule.cs │ │ ├── Middlewares │ │ │ └── GlobalErrorHandlerMiddleware.cs │ │ └── NotSoSimpleEcommerce.Main.csproj │ ├── NotSoSimpleEcommerce.Order │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── Dockerfile │ │ ├── Modules │ │ │ ├── DomainModule.cs │ │ │ └── AwsModule.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Middlewares │ │ │ └── GlobalErrorHandlerMiddleware.cs │ │ └── NotSoSimpleEcommerce.Order.csproj │ └── NotSoSimpleEcommerce.IdentityServer │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── Modules │ │ ├── DomainModule.cs │ │ └── InfrastructureModule.cs │ │ ├── Dockerfile │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── Middlewares │ │ └── GlobalErrorHandlerMiddleware.cs │ │ ├── Controllers │ │ └── AuthController.cs │ │ ├── NotSoSimpleEcommerce.IdentityServer.csproj │ │ └── Program.cs └── workers │ ├── NotSoSimpleEcommerce.Notificator │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Templates │ │ └── order-created-template.json │ ├── Dockerfile │ ├── Modules │ │ ├── DomainModule.cs │ │ └── AwsModule.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Tasks │ │ └── AwsSesEmailSenderProcessor.cs │ ├── NotSoSimpleEcommerce.Notificator.csproj │ └── Program.cs │ └── NotSoSimpleEcommerce.InvoiceGenerator │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Dockerfile │ ├── Modules │ ├── DomainModule.cs │ └── AwsModule.cs │ ├── Properties │ └── launchSettings.json │ ├── NotSoSimpleEcommerce.InvoiceGenerator.csproj │ ├── Tasks │ └── InvoiceProcessor.cs │ └── Program.cs ├── certificates ├── root-ca.srl ├── config.ext ├── kestrel-certificate.pfx ├── signing-request.csr ├── root-ca.crt └── nginx-certificate.crt ├── .vscode └── settings.json ├── package.json ├── .dockerignore ├── docker-compose.dcproj ├── .github └── workflows │ ├── continuous-integration.yml │ └── continuous-deployment.yml ├── docker-compose.workers.yml ├── iac └── main.tf ├── docker-compose.infra.yml ├── docker-compose.yml └── nginx └── nginx.conf /src/frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /src/frontend/src/components/navbar/Index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /certificates/root-ca.srl: -------------------------------------------------------------------------------- 1 | 61C1549728F279D391B151801825D958B10AAF51 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet.defaultSolution": "NotSoSimpleEcommerce.sln" 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@types/node": "^20.5.6" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_ENVIRONMENT="local" 2 | VITE_APP_BASE_URL="https://devopsnanuvem.internal:44300" 3 | ``` -------------------------------------------------------------------------------- /src/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | top: 0; 3 | width: 100%; 4 | z-index: 1; 5 | position: fixed; 6 | } -------------------------------------------------------------------------------- /certificates/config.ext: -------------------------------------------------------------------------------- 1 | subjectAltName=@altNames 2 | 3 | [altNames] 4 | DNS.1=devopsnanuvem.internal 5 | DNS.2=localhost -------------------------------------------------------------------------------- /certificates/kestrel-certificate.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenerry-serain/not-so-simple-ecommerce/HEAD/certificates/kestrel-certificate.pfx -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/InOut/Responses/TokenResponse.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.InOut.Responses 2 | { 3 | public record TokenResponse(string Token); 4 | } 5 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/Enums/OrderStatus.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.Enums; 2 | 3 | public enum OrderStatus 4 | { 5 | Pendente, 6 | Confirmada 7 | } 8 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/InOut/Requests/OrderRequest.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.InOut.Requests; 2 | 3 | public record OrderRequest(int ProductId, int Quantity); 4 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SnsHandler/Models/AwsSnsMessageParams.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.SnsHandler.Models; 2 | 3 | public record AwsSnsMessageParams(string TopicArn); 4 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/InOut/Requests/StockRequest.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Main.Domain.InOut.Requests 2 | { 3 | public sealed record StockRequest(int Quantity); 4 | } 5 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/Events/OrderConfirmedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.Events; 2 | 3 | public class OrderConfirmedEvent 4 | { 5 | public int Id {get;set;} 6 | } 7 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SesHandler/Enums/BodyContentType.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.SesHandler.Enums; 2 | 3 | public enum BodyContentType 4 | { 5 | PlainText, 6 | Html 7 | } 8 | -------------------------------------------------------------------------------- /src/frontend/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen *:3000; 3 | location / { 4 | root /usr/share/nginx/html; 5 | index index.html; 6 | try_files $uri /index.html; 7 | } 8 | } -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.HealthChecker/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.HealthChecker/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Order/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/InOut/Requests/ProductRequest.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Main.Domain.InOut.Requests 2 | { 3 | public sealed record ProductRequest(string Name, decimal Price); 4 | } 5 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/InOut/Requests/AuthRequest.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.InOut.Requests; 2 | 3 | public record AuthRequest(string Email, string Password, bool OnlyTokenBody=false); 4 | 5 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/InOut/Responses/ProductResponse.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.InOut.Responses 2 | { 3 | public record ProductResponse(int Id, string? Name, decimal Price); 4 | } 5 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/InOut/Responses/StockResponse.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.InOut.Responses; 2 | 3 | public record StockResponse(int Id, ProductResponse Product, int Quantity); 4 | 5 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/Models/ProductEntity.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.Models; 2 | 3 | public class ProductEntity 4 | { 5 | public int Id { get; set; } 6 | public string? Name { get; set; } 7 | public decimal Price { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.S3Handler/Models/AwsS3BucketParams.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.S3Handler.Models; 2 | 3 | public sealed record AwsS3BucketParams(string BucketName, int UriExpirationMinutes) 4 | { 5 | public AwsS3BucketParams(): this(string.Empty, int.MinValue){} 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | 3 | import { Outlet } from 'react-router-dom'; 4 | import { NavBar } from './components/navbar/Index'; 5 | 6 | function App() { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | ) 13 | } 14 | 15 | export default App 16 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SnsHandler/Abstractions/IMessageSender.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.SnsHandler.Abstractions 2 | { 3 | public interface IMessageSender 4 | { 5 | Task EnqueueAsync(TObject messageBody, CancellationToken cancellationToken); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SqsHandler/Abstractions/IMessageSender.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.SqsHandler.Abstractions 2 | { 3 | public interface IMessageSender 4 | { 5 | Task EnqueueAsync(TObject messageBody, CancellationToken cancellationToken); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/Contracts/ICreateEntityRepository.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Repositories.Contracts 2 | { 3 | public interface ICreateEntityRepository 4 | { 5 | Task ExecuteAsync(TEntity entity, CancellationToken cancellationToken); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Utils/NotSoSimpleEcommerce.Utils.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/Contracts/IDeleteEntityRepository.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Repositories.Contracts 2 | { 3 | public interface IDeleteEntityRepository 4 | where TEntity : class 5 | { 6 | Task ExecuteAsync(TEntity entity, CancellationToken cancellationToken); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/Templates/order-created-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "Template": { 3 | "TemplateName": "OrderCreated", 4 | "SubjectPart": "Received Order", 5 | "TextPart": "Hi {{Username}}, your order was successfully created!", 6 | "HtmlPart": "

Hi {{Username}}, your order was successfully created!

" 7 | } 8 | } -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/Contracts/IUpdateEntityRepository.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Repositories.Contracts 2 | { 3 | public interface IUpdateEntityRepository 4 | where TEntity : class 5 | { 6 | Task ExecuteAsync(TEntity entity, CancellationToken cancellationToken); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/Services/Contracts/IUserService.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.InOut.Requests; 2 | 3 | namespace NotSoSimpleEcommerce.IdentityServer.Domain.Services.Contracts 4 | { 5 | public interface IUserService 6 | { 7 | Task CheckPasswordAsync(AuthRequest userRequest); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.S3Handler/Models/ObjectRegister.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.S3Handler.Models; 2 | 3 | public record ObjectRegister 4 | ( 5 | string BucketName, 6 | string ObjectKey, 7 | byte[] Object, 8 | string ContentType= "application/json", 9 | IDictionary? Metadata = null 10 | ); 11 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SesHandler/Abstractions/IEmailSender.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.SesHandler.Models; 2 | 3 | namespace NotSoSimpleEcommerce.SesHandler.Abstractions; 4 | 5 | public interface IEmailSender 6 | { 7 | Task SendAsync 8 | ( 9 | EmailParams @params, 10 | CancellationToken cancellationToken 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/Models/StockEntity.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.Models 2 | { 3 | public class StockEntity 4 | { 5 | public int Id { get; set; } 6 | public int ProductId { get; set; } 7 | public int Quantity { get; set; } 8 | public ProductEntity Product { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SqsHandler/Models/AwsQueueMessageParams.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.SqsHandler.Models 2 | { 3 | public record AwsQueueMessageParams(string Body, string MessageId); 4 | public class SnsEnvelope 5 | { 6 | public string MessageId { get; set; } 7 | public string Message { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Commands/DeleteProductStockCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace NotSoSimpleEcommerce.Main.Domain.Commands; 4 | 5 | public class DeleteProductStockCommand: IRequest 6 | { 7 | public DeleteProductStockCommand(int id) 8 | { 9 | Id = id; 10 | } 11 | 12 | public int Id { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "ConnectionStrings": { 9 | "Default": "User ID=postgres;Password=postgres;Host=127.0.0.1;Port=5432;Database=notSoSimpleEcommerce;" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/frontend/src/pages/stock/Index.css: -------------------------------------------------------------------------------- 1 | .center { 2 | margin-top: 8%; 3 | width: 90%; 4 | margin-right: 5%; 5 | margin-left: 5%; 6 | } 7 | 8 | a{ 9 | color: white !important; 10 | } 11 | 12 | a:hover{ 13 | color: #1976d2 !important; 14 | } 15 | 16 | .right { 17 | float: right; 18 | } 19 | 20 | .row{ 21 | display: flow-root; 22 | } -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SqsHandler/Abstractions/IMessageProcessor.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.SqsHandler.Models; 2 | 3 | namespace NotSoSimpleEcommerce.SqsHandler.Abstractions 4 | { 5 | public interface IMessageProcessor 6 | { 7 | Task ProcessMessageAsync(AwsQueueMessageParams awsQueueMessage, CancellationToken cancellationToken); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "Default": "User ID=postgres;Password=postgres;Host=localhost;Port=5432;Database=notSoSimpleEcommerce;" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Order/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "Default": "User ID=postgres;Password=postgres;Host=localhost;Port=5432;Database=notSoSimpleEcommerce;" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/HttpHandlers/Contracts/IIdentityServerApi.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.InOut.Requests; 2 | using Refit; 3 | 4 | namespace NotSoSimpleEcommerce.Shared.HttpHandlers.Contracts 5 | { 6 | public interface IIdentityServerApi 7 | { 8 | [Post("/api/auth")] 9 | Task AuthAsync(AuthRequest userRequest); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "Default": "User ID=postgres;Password=postgres;Host=127.0.0.1;Port=5432;Database=notSoSimpleEcommerce;" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Commands/DeleteProductCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace NotSoSimpleEcommerce.Main.Domain.Commands 4 | { 5 | public sealed class DeleteProductCommand: IRequest 6 | { 7 | public DeleteProductCommand(int id) 8 | { 9 | Id = id; 10 | } 11 | 12 | public int Id { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/Consts/Email.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Shared.Consts; 2 | 3 | public static class Email 4 | { 5 | public static class Subjects 6 | { 7 | public const string OrderCreated= "Order Created"; 8 | } 9 | 10 | public static class Templates 11 | { 12 | public const string OrderCreated = "OrderCreated"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "NotSoSimpleEcommerce.Main": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:59619;http://localhost:59620" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/InOut/Responses/OrderResponse.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.Enums; 2 | 3 | namespace NotSoSimpleEcommerce.Shared.InOut.Responses; 4 | 5 | public record OrderResponse 6 | ( 7 | int Id, 8 | ProductResponse Product, 9 | int Quantity, 10 | string BoughtBy, 11 | OrderStatus StatusId, 12 | string Status 13 | ); 14 | 15 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Commands/DeleteProductCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace NotSoSimpleEcommerce.Order.Domain.Commands 4 | { 5 | public sealed class DeleteOrderCommand: IRequest 6 | { 7 | public DeleteOrderCommand(int id) 8 | { 9 | Id = id; 10 | } 11 | 12 | public int Id { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/HttpHandlers/Contracts/IOrderApi.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 2 | using Refit; 3 | 4 | namespace NotSoSimpleEcommerce.Shared.HttpHandlers.Contracts; 5 | 6 | public interface IOrderApi 7 | { 8 | [Get("/api/request/{id}")] 9 | [Headers("Authorization: Bearer")] 10 | Task> GetOrderByIdAsync(int id); 11 | } 12 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Commands/RegisterProductCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Commands 5 | { 6 | public sealed class RegisterProductCommand: IRequest 7 | { 8 | public string? Name { get; set; } 9 | public decimal Price { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SesHandler/Models/EmailParams.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.SesHandler.Models; 2 | 3 | public record EmailParams 4 | ( 5 | string FromAddress, 6 | ICollection ToAddresses, 7 | string Subject, 8 | string TemplateName, 9 | string TemplateData, 10 | ICollection? CcAddresses = default, 11 | ICollection? BccAddresses = default 12 | ); 13 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Commands/RegisterProductStockCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Commands 5 | { 6 | public sealed class RegisterProductStockCommand : IRequest 7 | { 8 | public int ProductId { get; set; } 9 | public int Quantity { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/Models/StatusEntity.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.Enums; 2 | 3 | namespace NotSoSimpleEcommerce.Shared.Models; 4 | 5 | public class StatusEntity 6 | { 7 | public StatusEntity(OrderStatus id, string description) 8 | { 9 | Id = id; 10 | Description = description; 11 | } 12 | 13 | public OrderStatus Id{ get; set; } 14 | public string Description { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Not So Simple ecommerce App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Repositories/Contracts/IStockReadRepository.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Repositories.Contracts; 2 | using NotSoSimpleEcommerce.Shared.Models; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Repositories.Contracts; 5 | 6 | public interface IStockReadRepository: IReadEntityRepository 7 | { 8 | Task GetByProductIdAsync(int productId, CancellationToken cancellationToken); 9 | } 10 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Commands/UpdateProductCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Commands 5 | { 6 | public sealed class UpdateProductCommand: IRequest 7 | { 8 | public int Id { get; set; } 9 | public string? Name { get; set; } 10 | public decimal Price { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/NotSoSimpleEcommerce.Repositories.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Repositories/Contracts/IOrderReadRepository.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Repositories.Contracts; 2 | using NotSoSimpleEcommerce.Shared.Models; 3 | 4 | namespace NotSoSimpleEcommerce.Order.Domain.Repositories.Contracts; 5 | 6 | public interface IOrderReadRepository: IReadEntityRepository 7 | { 8 | Task GetByProductIdAsync(int productId, CancellationToken cancellationToken); 9 | } 10 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Commands/DeleteProductImageCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace NotSoSimpleEcommerce.Main.Domain.Commands; 4 | 5 | public sealed class DeleteProductImageCommand: IRequest 6 | { 7 | public DeleteProductImageCommand(int id, string objectKey) 8 | { 9 | Id = id; 10 | ObjectKey = objectKey; 11 | } 12 | 13 | public int Id { get; set; } 14 | public string ObjectKey { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SqsHandler/Models/AwsSqsMessageSenderParams.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.SqsHandler.Models 2 | { 3 | public record AwsSqsMessageSenderParams 4 | ( 5 | bool IsFifo, 6 | bool EnableMessageDeduplication, 7 | string QueueName, 8 | string QueueOwnerAwsAccountId 9 | ) 10 | { 11 | public AwsSqsMessageSenderParams() : this(false, false, string.Empty, String.Empty) { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/Contracts/IGetEntityRepository.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.Repositories.Contracts 2 | { 3 | public interface IReadEntityRepository 4 | where TEntity : class 5 | { 6 | IQueryable GetAll(); 7 | Task GetByIdAsync(int primaryKey, CancellationToken cancellationToken); 8 | Task> GetAllAsync(CancellationToken cancellationToken); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | export default { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:react/jsx-runtime', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | settings: { react: { version: '18.2' } }, 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': 'warn', 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /src/frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | host: true, 9 | port: 3000, // This is the port which we will use in docker 10 | // Thanks @sergiomoura for the window fix 11 | // add the next lines if you're using windows and hot reload doesn't work 12 | // watch: { 13 | // usePolling: true 14 | // } 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/Models/UserEntity.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.IdentityServer.Domain.Models 2 | { 3 | public class UserEntity 4 | { 5 | public UserEntity(int id, string email, string password) 6 | { 7 | Id = id; 8 | Email = email; 9 | Password = password; 10 | } 11 | 12 | public int Id { get; set; } 13 | public string Email { get; init; } 14 | public string Password { get; init; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/frontend/src/hooks/useReport.ts: -------------------------------------------------------------------------------- 1 | import { UseQueryResult, useQuery } from "@tanstack/react-query"; 2 | import { ReportEntity } from "../types/Stock.type"; 3 | import { http } from "../utils/HttpClient"; 4 | 5 | export const getReports = async () => { 6 | const { data } = await http.get("/main/api/report/order"); 7 | return data; 8 | }; 9 | 10 | export const useReport = (): UseQueryResult => 11 | useQuery({ 12 | queryKey: ["report"], 13 | queryFn: getReports, 14 | }); 15 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Commands/UpdateProductImageCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Commands 5 | { 6 | public sealed class UpdateProductImageCommand: IRequest 7 | { 8 | public UpdateProductImageCommand(int id, IFormFile image) 9 | { 10 | Id = id; 11 | Image = image; 12 | } 13 | 14 | public int Id { get; } 15 | public IFormFile Image{ get; init; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:23-alpine3.19 as build 2 | WORKDIR /nsse-frontend 3 | COPY package*.json ./ 4 | RUN npm ci --silent --only=production 5 | COPY . . 6 | RUN npm run build 7 | 8 | FROM nginxinc/nginx-unprivileged:1.26-alpine 9 | COPY --from=build /nsse-frontend/dist /usr/share/nginx/html 10 | RUN rm /etc/nginx/conf.d/default.conf 11 | COPY nginx/nginx.conf /etc/nginx/conf.d/nginx.conf 12 | USER nginx 13 | HEALTHCHECK CMD curl --fail http://localhost:3000/ || exit 1 14 | 15 | EXPOSE 3000 16 | ENTRYPOINT ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SnsHandler/NotSoSimpleEcommerce.SnsHandler.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.S3Handler/Abstractions/IObjectManager.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using NotSoSimpleEcommerce.S3Handler.Models; 3 | 4 | namespace NotSoSimpleEcommerce.S3Handler.Abstractions; 5 | 6 | public interface IObjectManager 7 | { 8 | Task GetPreSignedUrlAsync(string objectKey, CancellationToken cancellationToken); 9 | Task PutObjectAsync(ObjectRegister @object, CancellationToken cancellationToken); 10 | Task DeleteObjectAsync(string objectKey, CancellationToken cancellationToken); 11 | } 12 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SesHandler/NotSoSimpleEcommerce.SesHandler.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/frontend/src/pages/stock/ColumnDef.tsx: -------------------------------------------------------------------------------- 1 | import { GridColDef, GridValueGetterParams } from "@mui/x-data-grid"; 2 | 3 | const stockColumns: GridColDef[] = [ 4 | { 5 | field: 'product.name', 6 | headerName: 'Product', 7 | type: 'string', 8 | width: 250, 9 | valueGetter: (gridValue: GridValueGetterParams) => `${gridValue.row.product.name || ''}`, 10 | }, 11 | { 12 | field: 'quantity', 13 | headerName: 'Quantity', 14 | type: 'number', 15 | width: 250 16 | } 17 | ]; 18 | 19 | export default stockColumns; -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.S3Handler/NotSoSimpleEcommerce.S3Handler.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/Modules/DomainModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using NotSoSimpleEcommerce.IdentityServer.Domain.Services.Contracts; 3 | using NotSoSimpleEcommerce.IdentityServer.Domain.Services.Implementations; 4 | 5 | namespace NotSoSimpleEcommerce.IdentityServer.Modules 6 | { 7 | public class DomainModule : Module 8 | { 9 | protected override void Load(ContainerBuilder builder) 10 | { 11 | builder.RegisterType() 12 | .As() 13 | .InstancePerLifetimeScope(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Commands/CreateOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 3 | 4 | namespace NotSoSimpleEcommerce.Order.Domain.Commands; 5 | 6 | public class CreateOrderCommand: IRequest 7 | { 8 | public int Id { get; init; } 9 | public int ProductId { get; init; } 10 | public int Quantity { get; init; } 11 | public string BoughtBy { get; set; } 12 | 13 | public CreateOrderCommand WithBoughBy(string boughtBy) 14 | { 15 | BoughtBy = boughtBy; 16 | return this; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Commands/UpdateProductStockCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Commands 5 | { 6 | public sealed class UpdateProductStockCommand : IRequest 7 | { 8 | public int Id { get; private set; } 9 | public int ProductId { get; set; } 10 | public int Quantity { get; set; } 11 | 12 | public UpdateProductStockCommand WithId(int id) 13 | { 14 | Id = id; 15 | return this; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SqsHandler/Models/AwsSqsQueueMonitorParams.cs: -------------------------------------------------------------------------------- 1 | namespace NotSoSimpleEcommerce.SqsHandler.Models 2 | { 3 | public record AwsSqsQueueMonitorParams 4 | ( 5 | string QueueName, 6 | string QueueOwnerAwsAccountId, 7 | int MaxNumberOfMessages, 8 | int WaitTimeSeconds, 9 | string MsgProcessorServiceName, 10 | int MillisecondsDelay 11 | ) 12 | { 13 | public AwsSqsQueueMonitorParams() : this(string.Empty, string.Empty, int.MinValue, int.MinValue, string.Empty, 14 | int.MinValue) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/HttpHandlers/Contracts/IMainApi.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 2 | using Refit; 3 | 4 | namespace NotSoSimpleEcommerce.Shared.HttpHandlers.Contracts 5 | { 6 | public interface IMainApi 7 | { 8 | [Put("/api/product/{id}/stock")] 9 | [Headers("Authorization: Bearer")] 10 | Task> UpdateProductStockAsync(int id, [Body] object payload); 11 | 12 | [Get("/api/product/{id}/stock")] 13 | [Headers("Authorization: Bearer")] 14 | Task> GetStockByProductIdAsync(int id); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Events/OrderCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Order.Domain.Commands; 3 | 4 | namespace NotSoSimpleEcommerce.Order.Domain.Events; 5 | 6 | public class OrderCreatedEvent: INotification 7 | { 8 | public OrderCreatedEvent(CreateOrderCommand command) 9 | { 10 | Id = command.Id; 11 | ProductId = command.ProductId; 12 | Quantity = command.Quantity; 13 | BoughtBy = command.BoughtBy; 14 | } 15 | 16 | public int Id { get; set; } 17 | public int ProductId { get; init; } 18 | public int Quantity { get; init; } 19 | public string BoughtBy { get; init; } 20 | } 21 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Commands/UpdateProductCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 3 | 4 | namespace NotSoSimpleEcommerce.Order.Domain.Commands 5 | { 6 | public sealed class UpdateOrderCommand: IRequest 7 | { 8 | public int Id { get; init; } 9 | public int ProductId { get; init; } 10 | public int Quantity { get; init; } 11 | public string BoughtBy { get; set; } 12 | 13 | public UpdateOrderCommand WithBoughBy(string boughtBy) 14 | { 15 | BoughtBy = boughtBy; 16 | return this; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine as build 2 | WORKDIR /nsse-backend 3 | COPY . . 4 | RUN dotnet publish /nsse-backend/src/services/NotSoSimpleEcommerce.Main/NotSoSimpleEcommerce.Main.csproj \ 5 | -c Release \ 6 | -o publish 7 | 8 | FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine 9 | WORKDIR /nsse-backend 10 | COPY --from=build /nsse-backend/publish . 11 | EXPOSE 443 12 | 13 | RUN adduser dotnet --disabled-password 14 | RUN apk add curl 15 | 16 | USER dotnet 17 | HEALTHCHECK CMD curl -k --fail https://localhost:443/main/health 18 | ENTRYPOINT ["dotnet", "/nsse-backend/NotSoSimpleEcommerce.Main.dll"] 19 | 20 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Order/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine as build 2 | WORKDIR /nsse-backend 3 | COPY . . 4 | RUN dotnet publish /nsse-backend/src/services/NotSoSimpleEcommerce.Order/NotSoSimpleEcommerce.Order.csproj \ 5 | -c Release \ 6 | -o publish 7 | 8 | FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine 9 | WORKDIR /nsse-backend 10 | COPY --from=build /nsse-backend/publish . 11 | EXPOSE 443 12 | 13 | RUN adduser dotnet --disabled-password 14 | RUN apk add curl 15 | 16 | USER dotnet 17 | HEALTHCHECK CMD curl -k --fail https://localhost:443/order/health 18 | ENTRYPOINT ["dotnet", "/nsse-backend/NotSoSimpleEcommerce.Order.dll"] 19 | 20 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine as build 2 | WORKDIR /nsse-backend 3 | COPY . . 4 | RUN dotnet publish /nsse-backend/src/workers/NotSoSimpleEcommerce.Notificator/NotSoSimpleEcommerce.Notificator.csproj \ 5 | -c Release \ 6 | -o publish 7 | 8 | FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine 9 | WORKDIR /nsse-backend 10 | COPY --from=build /nsse-backend/publish . 11 | EXPOSE 443 12 | 13 | RUN adduser dotnet --disabled-password 14 | RUN apk add curl 15 | 16 | USER dotnet 17 | HEALTHCHECK CMD curl -k --fail https://localhost:443/notificator/health 18 | ENTRYPOINT ["dotnet", "/nsse-backend/NotSoSimpleEcommerce.Notificator.dll"] 19 | 20 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.HealthChecker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine as build 2 | WORKDIR /nsse-backend 3 | COPY . . 4 | RUN dotnet publish /nsse-backend/src/services/NotSoSimpleEcommerce.HealthChecker/NotSoSimpleEcommerce.HealthChecker.csproj \ 5 | -c Release \ 6 | -o publish 7 | 8 | FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine 9 | WORKDIR /nsse-backend 10 | COPY --from=build /nsse-backend/publish . 11 | EXPOSE 443 12 | 13 | RUN adduser dotnet --disabled-password 14 | RUN apk add curl 15 | 16 | USER dotnet 17 | HEALTHCHECK CMD curl -k --fail https://localhost:443/healthchecks/health 18 | ENTRYPOINT ["dotnet", "/nsse-backend/NotSoSimpleEcommerce.HealthChecker.dll"] 19 | 20 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine as build 2 | WORKDIR /nsse-backend 3 | COPY . . 4 | RUN dotnet publish /nsse-backend/src/services/NotSoSimpleEcommerce.IdentityServer/NotSoSimpleEcommerce.IdentityServer.csproj \ 5 | -c Release \ 6 | -o publish 7 | 8 | FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine 9 | WORKDIR /nsse-backend 10 | COPY --from=build /nsse-backend/publish . 11 | EXPOSE 443 12 | 13 | RUN adduser dotnet --disabled-password 14 | RUN apk add curl 15 | 16 | USER dotnet 17 | HEALTHCHECK CMD curl -k --fail https://localhost:443/identity/health 18 | ENTRYPOINT ["dotnet", "/nsse-backend/NotSoSimpleEcommerce.IdentityServer.dll"] 19 | 20 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SnsHandler/Exceptions/AwsSnsMessageSenderException.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace NotSoSimpleEcommerce.SnsHandler.Exceptions; 4 | 5 | [Serializable] 6 | public class AwsSnsMessageSenderException : Exception 7 | { 8 | public AwsSnsMessageSenderException() 9 | { 10 | } 11 | 12 | public AwsSnsMessageSenderException(string message) : base(message) 13 | { 14 | } 15 | 16 | public AwsSnsMessageSenderException(string message, Exception innerException) : base(message, innerException) 17 | { 18 | } 19 | 20 | protected AwsSnsMessageSenderException(SerializationInfo info, StreamingContext context) : base(info, context) 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine as build 2 | WORKDIR /nsse-backend 3 | COPY . . 4 | RUN dotnet publish /nsse-backend/src/workers/NotSoSimpleEcommerce.InvoiceGenerator/NotSoSimpleEcommerce.InvoiceGenerator.csproj \ 5 | -c Release \ 6 | -o publish 7 | 8 | FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine 9 | WORKDIR /nsse-backend 10 | COPY --from=build /nsse-backend/publish . 11 | EXPOSE 443 12 | 13 | RUN adduser dotnet --disabled-password 14 | RUN apk add curl 15 | 16 | USER dotnet 17 | HEALTHCHECK CMD curl -k --fail https://localhost:443/invoice/health 18 | ENTRYPOINT ["dotnet", "/nsse-backend/NotSoSimpleEcommerce.InvoiceGenerator.dll"] 19 | 20 | -------------------------------------------------------------------------------- /docker-compose.dcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.1 4 | Linux 5 | bc3be590-5519-4d12-bad0-b3a507592ae0 6 | LaunchBrowser 7 | {Scheme}://localhost:{ServicePort}/main/swagger 8 | simpleecommerce.main 9 | 6f7ee0e1-aa0c-4eb9-aec8-a0c28fef0813 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/Models/OrderEntity.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.Enums; 2 | 3 | namespace NotSoSimpleEcommerce.Shared.Models; 4 | 5 | public class OrderEntity 6 | { 7 | public OrderEntity(int productId, int quantity, string boughtBy, OrderStatus statusId) 8 | { 9 | ProductId = productId; 10 | Quantity = quantity; 11 | BoughtBy = boughtBy; 12 | StatusId = statusId; 13 | } 14 | 15 | public int Id { get; set; } 16 | public int ProductId { get; set; } 17 | public int Quantity { get; set; } 18 | public string BoughtBy{ get; set; } 19 | public OrderStatus StatusId{ get; set; } 20 | public ProductEntity Product { get; set; } 21 | public StatusEntity Status { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Utils/Encryption/Hasher.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace NotSoSimpleEcommerce.Utils.Encryption 5 | { 6 | public static class Hasher 7 | { 8 | public static string GetMd5(byte[] data) 9 | { 10 | using var hash = MD5.Create(); 11 | var md5 = hash.ComputeHash(data); 12 | return ParseToString(md5); 13 | } 14 | 15 | private static string ParseToString(byte[] md5) 16 | { 17 | var stringBuilder = new StringBuilder(); 18 | foreach (var @byte in md5) 19 | stringBuilder.Append(@byte.ToString("x2")); 20 | 21 | return stringBuilder.ToString(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Order/Modules/DomainModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using MediatR.Extensions.Autofac.DependencyInjection; 3 | using MediatR.Extensions.Autofac.DependencyInjection.Builder; 4 | using NotSoSimpleEcommerce.Order.Domain.Commands; 5 | 6 | namespace NotSoSimpleEcommerce.Order.Modules 7 | { 8 | public class DomainModule: Module 9 | { 10 | protected override void Load(ContainerBuilder builder) 11 | { 12 | var configuration = MediatRConfigurationBuilder 13 | .Create(typeof(CreateOrderCommand).Assembly) 14 | .WithAllOpenGenericHandlerTypesRegistered() 15 | .Build(); 16 | 17 | builder.RegisterMediatR(configuration); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/NotSoSimpleEcommerce.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | NotSoSimpleEcommerce.Shared 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SqsHandler/Exceptions/AwsSqsMessageSenderException.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace NotSoSimpleEcommerce.SqsHandler.Exceptions 4 | { 5 | [Serializable] 6 | public class AwsSqsMessageSenderException : Exception 7 | { 8 | public AwsSqsMessageSenderException() 9 | { 10 | } 11 | 12 | public AwsSqsMessageSenderException(string message) : base(message) 13 | { 14 | } 15 | 16 | public AwsSqsMessageSenderException(string message, Exception innerException) : base(message, innerException) 17 | { 18 | } 19 | 20 | protected AwsSqsMessageSenderException(SerializationInfo info, StreamingContext context) : base(info, context) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/frontend/src/types/Stock.type.ts: -------------------------------------------------------------------------------- 1 | export interface StockEntity { 2 | id: number; 3 | productId: number; 4 | product: ProductEntity; 5 | quantity: number; 6 | } 7 | 8 | export interface ReportEntity { 9 | id: number; 10 | totalOrders: number; 11 | totalOrdered: number; 12 | averagePrice: number; 13 | totalSold: number; 14 | productId: number; 15 | productName: string; 16 | } 17 | 18 | export interface ProductEntity { 19 | id: number; 20 | name: string; 21 | price: number; 22 | } 23 | 24 | 25 | export interface OrderEntity { 26 | id: number; 27 | productId: number; 28 | product: ProductEntity; 29 | quantity: number; 30 | statusId: number; 31 | status: string; 32 | boughtBy: string; 33 | } 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Models/ReportEntity.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Bson.Serialization.Attributes; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Models 5 | { 6 | public class ReportEntity 7 | { 8 | [BsonId] 9 | [BsonRepresentation(BsonType.ObjectId)] 10 | public string? Id { get; set; } 11 | 12 | [BsonElement("productId")] 13 | public int ProductId { get; set; } 14 | 15 | [BsonElement("productName")] 16 | public string ProductName { get; set; } 17 | 18 | [BsonElement("totalOrders")] 19 | public int TotalOrders { get; set; } 20 | 21 | [BsonElement("totalOrdered")] 22 | public int TotalOrdered{ get; set; } 23 | 24 | [BsonElement("totalSold")] 25 | public decimal TotalSold{ get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | paths: 7 | - 'src/frontend/**' 8 | 9 | jobs: 10 | frontend: 11 | name: CI 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - uses: actions/checkout@v4 16 | 17 | - name: Use Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20.x 21 | 22 | - name: NPM Install 23 | working-directory: ./src/frontend 24 | run: npm ci 25 | 26 | - name: NPM Tests 27 | working-directory: ./src/frontend 28 | run: npm run test --if-present 29 | 30 | - name: NPM Build 31 | working-directory: ./src/frontend 32 | run: npm run build 33 | 34 | - uses: actions/upload-artifact@v4 35 | with: 36 | name: ${{github.sha}} 37 | path: ./src/frontend/dist -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Repositories/Configurations/StatusEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using NotSoSimpleEcommerce.Shared.Enums; 4 | using NotSoSimpleEcommerce.Shared.Models; 5 | 6 | namespace NotSoSimpleEcommerce.Order.Domain.Repositories.Configurations; 7 | 8 | public class StatusEntityTypeConfiguration: IEntityTypeConfiguration 9 | { 10 | public void Configure(EntityTypeBuilder builder) 11 | { 12 | builder.ToTable("Status"); 13 | builder.HasKey(status => status.Id); 14 | builder 15 | .Property(status => status.Id) 16 | .HasColumnType("integer") 17 | .HasConversion(); 18 | 19 | builder 20 | .Property(status => status.Description) 21 | .HasColumnType("text") 22 | .IsRequired(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/Implementations/DeleteEntityRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NotSoSimpleEcommerce.Repositories.Contracts; 3 | 4 | namespace NotSoSimpleEcommerce.Repositories.Implementations 5 | { 6 | public class DeleteEntityRepository : IDeleteEntityRepository 7 | where TEntity : class 8 | { 9 | private readonly DbSet _dbSet; 10 | private readonly DbContext _databaseContext; 11 | 12 | public DeleteEntityRepository(DbContext databaseContext) 13 | { 14 | _dbSet = databaseContext.Set(); 15 | _databaseContext = databaseContext; 16 | } 17 | 18 | public async Task ExecuteAsync(TEntity entity, CancellationToken cancellationToken) 19 | { 20 | _dbSet.Remove(entity); 21 | await _databaseContext.SaveChangesAsync(cancellationToken); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/CommandHandlers/DeleteProductImageCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Main.Domain.Commands; 3 | using NotSoSimpleEcommerce.S3Handler.Abstractions; 4 | 5 | namespace NotSoSimpleEcommerce.Main.Domain.CommandHandlers; 6 | 7 | public class DeleteProductImageCommandHandler: IRequestHandler 8 | { 9 | private readonly IObjectManager _objectManager; 10 | 11 | public DeleteProductImageCommandHandler(IObjectManager objectManager) 12 | { 13 | _objectManager = objectManager ?? throw new ArgumentNullException(nameof(objectManager)); 14 | } 15 | 16 | public async Task Handle(DeleteProductImageCommand request, CancellationToken cancellationToken) 17 | { 18 | var httpStatusCode = await _objectManager.DeleteObjectAsync(request.ObjectKey, cancellationToken); 19 | return (int)httpStatusCode is >= 200 and <= 299; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/frontend/src/utils/HttpClient.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from "axios"; 2 | 3 | function createInstance(baseUrl: string) { 4 | const baseConfig = { baseURL: baseUrl }; 5 | const instance = axios.create(baseConfig); 6 | instance.interceptors.response.use((response) => { 7 | console.log('response', response) 8 | // if (response.status === 401) 9 | // window.location.assign('login'); 10 | 11 | return response; 12 | }); 13 | instance.interceptors.request.use(async (request) => { 14 | console.log('request') 15 | request.headers = request.headers || {}; 16 | request.headers["Content-type"] = "application/json"; 17 | request.headers["Authorization"] = `Bearer ${localStorage.getItem("jwtToken")}`; 18 | return request; 19 | }, error => { 20 | return Promise.reject(error); 21 | }); 22 | 23 | 24 | 25 | return instance; 26 | } 27 | 28 | 29 | export const http = createInstance(import.meta.env.VITE_APP_BASE_URL!); 30 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/NotSoSimpleEcommerce.IdentityServer.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Mappings/ProductRequestToCommandMapping.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Order.Domain.Commands; 2 | using NotSoSimpleEcommerce.Shared.InOut.Requests; 3 | 4 | namespace NotSoSimpleEcommerce.Order.Domain.Mappings 5 | { 6 | public static class ProductRequestToCommandMapping 7 | { 8 | public static CreateOrderCommand MapToRegisterOrderCommand(this OrderRequest order) 9 | { 10 | return new CreateOrderCommand 11 | { 12 | ProductId = order.ProductId, 13 | Quantity = order.Quantity 14 | }; 15 | } 16 | 17 | public static UpdateOrderCommand MapToUpdateCommand(this OrderRequest product, int id) 18 | { 19 | return new UpdateOrderCommand 20 | { 21 | Id = id, 22 | ProductId = product.ProductId, 23 | Quantity = product.Quantity 24 | }; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/Implementations/UpdateEntityRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NotSoSimpleEcommerce.Repositories.Contracts; 3 | 4 | namespace NotSoSimpleEcommerce.Repositories.Implementations 5 | { 6 | public class UpdateEntityRepository : IUpdateEntityRepository 7 | where TEntity : class 8 | { 9 | private readonly DbSet _dbSet; 10 | private readonly DbContext _databaseContext; 11 | 12 | public UpdateEntityRepository(DbContext databaseContext) 13 | { 14 | _dbSet = databaseContext.Set(); 15 | _databaseContext = databaseContext; 16 | } 17 | 18 | public async Task ExecuteAsync(TEntity entity, CancellationToken cancellationToken) 19 | { 20 | _dbSet.Update(entity); 21 | await _databaseContext.SaveChangesAsync(cancellationToken); 22 | return entity; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/Implementations/CreateEntityRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NotSoSimpleEcommerce.Repositories.Contracts; 3 | 4 | namespace NotSoSimpleEcommerce.Repositories.Implementations 5 | { 6 | public class CreateEntityRepository : ICreateEntityRepository 7 | where TEntity : class 8 | { 9 | private readonly DbSet _dbSet; 10 | private readonly DbContext _databaseContext; 11 | 12 | public CreateEntityRepository(DbContext databaseContext) 13 | { 14 | _dbSet = databaseContext.Set(); 15 | _databaseContext = databaseContext; 16 | } 17 | 18 | public async Task ExecuteAsync(TEntity entity, CancellationToken cancellationToken) 19 | { 20 | await _dbSet.AddAsync(entity, cancellationToken); 21 | await _databaseContext.SaveChangesAsync(cancellationToken); 22 | return entity; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SqsHandler/NotSoSimpleEcommerce.SqsHandler.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Shared/Repositories/Configurations/ProductEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using NotSoSimpleEcommerce.Shared.Models; 4 | 5 | namespace NotSoSimpleEcommerce.Shared.Repositories.Configurations; 6 | 7 | public class ProductEntityTypeConfiguration: IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.ToTable("Product"); 12 | builder.HasKey(product => product.Id); 13 | builder 14 | .Property(product => product.Id) 15 | .HasColumnType("integer") 16 | .ValueGeneratedOnAdd(); 17 | 18 | builder 19 | .Property(order => order.Name) 20 | .HasColumnType("text") 21 | .IsRequired(); 22 | 23 | builder 24 | .Property(product => product.Price) 25 | .HasColumnType("numeric") 26 | .IsRequired(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-end", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000 --host", 8 | "build": "vite build", 9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@mui/styled-engine": "^5.13.2", 14 | "@mui/x-data-grid": "^6.8.0", 15 | "@tanstack/react-query": "^4.29.15", 16 | "@vitejs/plugin-react": "^4.0.0", 17 | "axios": "^1.4.0", 18 | "cross-env": "^7.0.3", 19 | "env-cmd": "^10.1.0", 20 | "material-react-table": "^1.14.0", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0", 23 | "react-router-dom": "^6.13.0", 24 | "vite": "^4.3.9" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "^18.2.21", 28 | "@types/react-dom": "^18.0.11", 29 | "eslint": "^8.38.0", 30 | "eslint-plugin-react": "^7.32.2", 31 | "eslint-plugin-react-hooks": "^4.6.0", 32 | "eslint-plugin-react-refresh": "^0.3.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/continuous-deployment.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment 2 | on: 3 | workflow_run: 4 | workflows: ["Continuous Integration"] 5 | types: 6 | - completed 7 | 8 | jobs: 9 | staging-deployment: 10 | name: staging-deployment 11 | runs-on: ubuntu-latest 12 | permissions: 13 | id-token: write 14 | steps: 15 | - name: Configure AWS Credentials 16 | uses: aws-actions/configure-aws-credentials@v5.1.0 17 | with: 18 | aws-region: us-east-1 19 | role-to-assume: arn:aws:iam::968225077300:role/nsse-github-frontend-role 20 | 21 | - uses: actions/download-artifact@v5 22 | with: 23 | name: ${{ github.event.workflow_run.head_sha }} 24 | path: ./dist 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | run-id: ${{ github.event.workflow_run.id }} 27 | 28 | - name: Deploy to S3 29 | run: aws s3 sync ./dist s3://staging.devopsnanuvemweek.com --delete 30 | 31 | - name: Invalidate CloudFront Cache 32 | run: aws cloudfront create-invalidation --distribution-id E3AFL453T680TI --paths "/*" -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Repositories/Contexts/ProductContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NotSoSimpleEcommerce.Main.Domain.Repositories.Configurations; 3 | using NotSoSimpleEcommerce.Shared.Models; 4 | using NotSoSimpleEcommerce.Shared.Repositories.Configurations; 5 | 6 | namespace NotSoSimpleEcommerce.Main.Domain.Repositories.Contexts 7 | { 8 | public class ProductContext : DbContext 9 | { 10 | public ProductContext(DbContextOptions options): base(options) { } 11 | 12 | public DbSet Stock { get; set; } = null!; 13 | public DbSet Product { get; set; } = null!; 14 | 15 | protected override void OnModelCreating(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProductEntityTypeConfiguration).Assembly); 18 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(StockEntityTypeConfiguration).Assembly); 19 | base.OnModelCreating(modelBuilder); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docker-compose.workers.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | nsse-network: 3 | external: true 4 | 5 | services: 6 | 7 | nsse.invoice.internal: 8 | container_name: nsse.invoice.internal 9 | image: nsse/invoice 10 | ports: 11 | - 5004:443 12 | build: 13 | context: . 14 | dockerfile: src/workers/NotSoSimpleEcommerce.InvoiceGenerator/Dockerfile 15 | env_file: 16 | - ./.env 17 | volumes: 18 | - ${CERTIFICATES_HOST_PATH}:${CERTIFICATES_CONTAINER_PATH} 19 | - ${AWS_CREDENTIALS_HOST_PATH}:${AWS_CREDENTIALS_CONTAINER_PATH} 20 | networks: 21 | - nsse-network 22 | 23 | nsse.notificator.internal: 24 | container_name: nsse.notificator.internal 25 | image: nsse/notificator 26 | ports: 27 | - 5005:443 28 | build: 29 | context: . 30 | dockerfile: src/workers/NotSoSimpleEcommerce.Notificator/Dockerfile 31 | env_file: 32 | - ./.env 33 | volumes: 34 | - ${CERTIFICATES_HOST_PATH}:${CERTIFICATES_CONTAINER_PATH} 35 | - ${AWS_CREDENTIALS_HOST_PATH}:${AWS_CREDENTIALS_CONTAINER_PATH} 36 | networks: 37 | - nsse-network -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "NotSoSimpleEcommerce.IdentityServer": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": true, 10 | "applicationUrl": "https://localhost:7005;http://localhost:5033" 11 | }, 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "Docker": { 20 | "commandName": "Docker", 21 | "launchBrowser": true, 22 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", 23 | "publishAllPorts": true, 24 | "useSSL": true 25 | } 26 | }, 27 | "iisSettings": { 28 | "windowsAuthentication": false, 29 | "anonymousAuthentication": true, 30 | "iisExpress": { 31 | "applicationUrl": "http://localhost:36928", 32 | "sslPort": 44305 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/Modules/DomainModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Core; 3 | using Microsoft.Extensions.Options; 4 | using NotSoSimpleEcommerce.Notificator.Tasks; 5 | using NotSoSimpleEcommerce.SqsHandler.Abstractions; 6 | using NotSoSimpleEcommerce.SqsHandler.Implementations; 7 | using NotSoSimpleEcommerce.SqsHandler.Models; 8 | 9 | namespace NotSoSimpleEcommerce.Notificator.Modules; 10 | 11 | public class DomainModule : Module 12 | { 13 | protected override void Load(ContainerBuilder builder) 14 | { 15 | builder.RegisterType() 16 | .Named(nameof(AwsSesEmailSenderProcessor)); 17 | 18 | builder.RegisterType() 19 | .As() 20 | .WithParameter( 21 | new ResolvedParameter( 22 | (i, _) => i.ParameterType == typeof(AwsSqsQueueMonitorParams), 23 | (_, c) => c.Resolve>() 24 | .Get("AwsSqsQueueMonitorParams01") 25 | ) 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/Modules/DomainModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Core; 3 | using Microsoft.Extensions.Options; 4 | using NotSoSimpleEcommerce.InvoiceGenerator.Tasks; 5 | using NotSoSimpleEcommerce.SqsHandler.Abstractions; 6 | using NotSoSimpleEcommerce.SqsHandler.Implementations; 7 | using NotSoSimpleEcommerce.SqsHandler.Models; 8 | 9 | namespace NotSoSimpleEcommerce.InvoiceGenerator.Modules; 10 | 11 | public class DomainModule : Module 12 | { 13 | protected override void Load(ContainerBuilder builder) 14 | { 15 | builder.RegisterType() 16 | .Named(nameof(InvoiceProcessor)); 17 | 18 | builder.RegisterType() 19 | .As() 20 | .WithParameter( 21 | new ResolvedParameter( 22 | (i, _) => i.ParameterType == typeof(AwsSqsQueueMonitorParams), 23 | (_, c) => c.Resolve>() 24 | .Get("AwsSqsQueueMonitorParams01") 25 | ) 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Mappings/OrderCommandToEntityMapping.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Order.Domain.Commands; 2 | using NotSoSimpleEcommerce.Shared.Enums; 3 | using NotSoSimpleEcommerce.Shared.Models; 4 | 5 | namespace NotSoSimpleEcommerce.Order.Domain.Mappings 6 | { 7 | public static class ProductCommandToEntityMapping 8 | { 9 | public static OrderEntity MapToEntity(this CreateOrderCommand order) 10 | { 11 | return new OrderEntity 12 | ( 13 | productId: order.ProductId, 14 | quantity: order.Quantity, 15 | boughtBy:order.BoughtBy, 16 | statusId: OrderStatus.Pendente 17 | ); 18 | } 19 | 20 | public static OrderEntity MapToEntity(this UpdateOrderCommand order) 21 | { 22 | return new OrderEntity 23 | ( 24 | productId: order.ProductId, 25 | quantity: order.Quantity, 26 | boughtBy:order.BoughtBy, 27 | statusId: OrderStatus.Pendente 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "NotSoSimpleEcommerce.Product": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": true, 10 | "applicationUrl": "https://localhost:7150;http://localhost:5150" 11 | }, 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": false, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "Docker": { 20 | "commandName": "Docker", 21 | "launchBrowser": false, 22 | "launchUrl": "{Scheme}://{ServiceHost}:5000/main/swagger", 23 | "publishAllPorts": true, 24 | "useSSL": true 25 | } 26 | }, 27 | "$schema": "https://json.schemastore.org/launchsettings.json", 28 | "iisSettings": { 29 | "windowsAuthentication": false, 30 | "anonymousAuthentication": true, 31 | "iisExpress": { 32 | "applicationUrl": "http://localhost:33406", 33 | "sslPort": 44349 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Repositories/Implementations/StockReadRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NotSoSimpleEcommerce.Main.Domain.Repositories.Contexts; 3 | using NotSoSimpleEcommerce.Main.Domain.Repositories.Contracts; 4 | using NotSoSimpleEcommerce.Repositories.Implementations; 5 | using NotSoSimpleEcommerce.Shared.Models; 6 | 7 | namespace NotSoSimpleEcommerce.Main.Domain.Repositories.Implementations; 8 | 9 | public sealed class StockReadRepository: ReadEntityRepository, IStockReadRepository 10 | { 11 | private readonly ProductContext _context; 12 | public StockReadRepository(ProductContext context): 13 | base(context) 14 | { 15 | _context = context ?? throw new ArgumentNullException(nameof(context)); 16 | } 17 | 18 | public async Task GetByProductIdAsync(int productId, CancellationToken cancellationToken) 19 | { 20 | return await _context.Stock 21 | .AsNoTracking() 22 | .Include(stock => stock.Product) 23 | .FirstOrDefaultAsync(stock => stock.ProductId == productId, cancellationToken); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Mappings/EntityToResponseMapping.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 2 | using NotSoSimpleEcommerce.Shared.Models; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Mappings 5 | { 6 | public static class EntityToResponseMapping 7 | { 8 | public static StockResponse MapToResponse(this StockEntity stock) 9 | { 10 | return new StockResponse(stock.Id, stock.Product.MapToResponse(), stock.Quantity); 11 | } 12 | 13 | public static ProductResponse MapToResponse(this ProductEntity product) 14 | { 15 | return new ProductResponse(product.Id, product.Name, product.Price); 16 | } 17 | 18 | public static IEnumerable MapToResponse(this IEnumerable products) 19 | { 20 | return products.Select(product => product.MapToResponse()); 21 | } 22 | 23 | public static IEnumerable MapToResponse(this IEnumerable stocks) 24 | { 25 | return stocks.Select(stock => stock.MapToResponse()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Repositories/Implementations/OrderReadRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NotSoSimpleEcommerce.Order.Domain.Repositories.Contexts; 3 | using NotSoSimpleEcommerce.Order.Domain.Repositories.Contracts; 4 | using NotSoSimpleEcommerce.Repositories.Implementations; 5 | using NotSoSimpleEcommerce.Shared.Models; 6 | 7 | namespace NotSoSimpleEcommerce.Order.Domain.Repositories.Implementations; 8 | 9 | public sealed class OrderReadRepository : ReadEntityRepository, IOrderReadRepository 10 | { 11 | private readonly OrderContext _context; 12 | 13 | public OrderReadRepository(OrderContext context) : 14 | base(context) 15 | { 16 | _context = context ?? throw new ArgumentNullException(nameof(context)); 17 | } 18 | 19 | public async Task GetByProductIdAsync(int productId, CancellationToken cancellationToken) 20 | { 21 | return await _context.Order 22 | .AsNoTracking() 23 | .Include(order => order.Product) 24 | .FirstOrDefaultAsync(order => order.ProductId ==productId, cancellationToken: cancellationToken); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Repositories/Configurations/StockEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using NotSoSimpleEcommerce.Shared.Models; 4 | 5 | namespace NotSoSimpleEcommerce.Main.Domain.Repositories.Configurations; 6 | 7 | public class StockEntityTypeConfiguration: IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.ToTable("Stock"); 12 | builder.HasKey(stock => stock.Id); 13 | builder 14 | .Property(stock => stock.Id) 15 | .HasColumnType("integer") 16 | .ValueGeneratedOnAdd(); 17 | 18 | builder 19 | .Property(stock => stock.ProductId) 20 | .HasColumnType("integer") 21 | .IsRequired(); 22 | 23 | builder 24 | .Property(stock => stock.Quantity) 25 | .HasColumnType("integer") 26 | .IsRequired(); 27 | 28 | builder 29 | .HasOne(stock => stock.Product) 30 | .WithOne() 31 | .HasForeignKey(stock => stock.ProductId); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/Repositories/Configurations/UserEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 6 | using Microsoft.Extensions.Configuration; 7 | using NotSoSimpleEcommerce.IdentityServer.Domain.Models; 8 | 9 | namespace NotSoSimpleEcommerce.IdentityServer.Domain.Repositories.Configurations; 10 | 11 | public class UserEntityTypeConfiguration: IEntityTypeConfiguration 12 | { 13 | public void Configure(EntityTypeBuilder builder) 14 | { 15 | builder.ToTable("User"); 16 | builder.HasKey(user => user.Id); 17 | builder 18 | .Property(user => user.Id) 19 | // .HasColumnType("serial") 20 | .ValueGeneratedOnAdd(); 21 | 22 | builder 23 | .Property(user => user.Email) 24 | .HasColumnType("text") 25 | .IsRequired(); 26 | 27 | builder 28 | .Property(user => user.Password) 29 | .HasColumnType("text") 30 | .IsRequired(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Order/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "NotSoSimpleEcommerce.Order": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "swagger", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "dotnetRunMessages": true, 11 | "applicationUrl": "https://localhost:7100;http://localhost:5295" 12 | }, 13 | "IIS Express": { 14 | "commandName": "IISExpress", 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "Docker": { 22 | "commandName": "Docker", 23 | "launchBrowser": true, 24 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", 25 | "publishAllPorts": true, 26 | "useSSL": true 27 | } 28 | }, 29 | "$schema": "https://json.schemastore.org/launchsettings.json", 30 | "iisSettings": { 31 | "windowsAuthentication": false, 32 | "anonymousAuthentication": true, 33 | "iisExpress": { 34 | "applicationUrl": "http://localhost:3844", 35 | "sslPort": 44309 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.HealthChecker/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "NotSoSimpleEcommerce.HealthChecks": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "weatherforecast", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "dotnetRunMessages": true, 11 | "applicationUrl": "https://localhost:7174;http://localhost:5174" 12 | }, 13 | "IIS Express": { 14 | "commandName": "IISExpress", 15 | "launchBrowser": true, 16 | "launchUrl": "weatherforecast", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "Docker": { 22 | "commandName": "Docker", 23 | "launchBrowser": true, 24 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/weatherforecast", 25 | "publishAllPorts": true, 26 | "useSSL": true 27 | } 28 | }, 29 | "$schema": "https://json.schemastore.org/launchsettings.json", 30 | "iisSettings": { 31 | "windowsAuthentication": false, 32 | "anonymousAuthentication": true, 33 | "iisExpress": { 34 | "applicationUrl": "http://localhost:38636", 35 | "sslPort": 44357 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Mappings/OrderEntityToResponseMapping.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 2 | using NotSoSimpleEcommerce.Shared.Models; 3 | 4 | namespace NotSoSimpleEcommerce.Order.Domain.Mappings 5 | { 6 | public static class OrderEntityToResponseMapping 7 | { 8 | public static OrderResponse MapToResponse(this OrderEntity order) 9 | { 10 | return new OrderResponse 11 | ( 12 | order.Id, 13 | order.Product.MapToResponse(), 14 | order.Quantity, 15 | order.BoughtBy, 16 | order.StatusId, 17 | Enum.GetName(order.StatusId) 18 | ); 19 | } 20 | 21 | public static ProductResponse MapToResponse(this ProductEntity product) 22 | { 23 | if (product is null) 24 | return null; 25 | return new ProductResponse(product.Id, product.Name, product.Price); 26 | } 27 | 28 | public static IEnumerable MapToResponse(this IEnumerable orders) 29 | { 30 | return orders.Select(order => order.MapToResponse()); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "NotSoSimpleEcommerce.Notificator": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "swagger", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "dotnetRunMessages": true, 11 | "applicationUrl": "https://localhost:7017;http://localhost:5076" 12 | }, 13 | "IIS Express": { 14 | "commandName": "IISExpress", 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "Docker": { 22 | "commandName": "Docker", 23 | "launchBrowser": true, 24 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", 25 | "publishAllPorts": true, 26 | "useSSL": true 27 | } 28 | }, 29 | "$schema": "https://json.schemastore.org/launchsettings.json", 30 | "iisSettings": { 31 | "windowsAuthentication": false, 32 | "anonymousAuthentication": true, 33 | "iisExpress": { 34 | "applicationUrl": "http://localhost:23056", 35 | "sslPort": 44328 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "NotSoSimpleEcommerce.InvoiceGenerator": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "swagger", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "dotnetRunMessages": true, 11 | "applicationUrl": "https://localhost:7034;http://localhost:5270" 12 | }, 13 | "IIS Express": { 14 | "commandName": "IISExpress", 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "Docker": { 22 | "commandName": "Docker", 23 | "launchBrowser": true, 24 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", 25 | "publishAllPorts": true, 26 | "useSSL": true 27 | } 28 | }, 29 | "$schema": "https://json.schemastore.org/launchsettings.json", 30 | "iisSettings": { 31 | "windowsAuthentication": false, 32 | "anonymousAuthentication": true, 33 | "iisExpress": { 34 | "applicationUrl": "http://localhost:62557", 35 | "sslPort": 44337 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.Repositories/Implementations/GetEntityRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NotSoSimpleEcommerce.Repositories.Contracts; 3 | 4 | namespace NotSoSimpleEcommerce.Repositories.Implementations 5 | { 6 | public class ReadEntityRepository : IReadEntityRepository 7 | where TEntity : class 8 | { 9 | private readonly DbSet _dbSet; 10 | 11 | public ReadEntityRepository(DbContext databaseContext) 12 | { 13 | databaseContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; 14 | _dbSet = databaseContext.Set(); 15 | } 16 | 17 | public IQueryable GetAll() 18 | { 19 | return _dbSet.AsQueryable(); 20 | } 21 | 22 | public async Task> GetAllAsync(CancellationToken cancellationToken) 23 | { 24 | var entities = await _dbSet.ToListAsync(cancellationToken); 25 | return entities; 26 | } 27 | 28 | public async Task GetByIdAsync(int primaryKey, CancellationToken cancellationToken) 29 | { 30 | var entity = await _dbSet.FindAsync(primaryKey, cancellationToken); 31 | return entity; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/CommandHandlers/RegisterProductCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Main.Domain.Commands; 3 | using NotSoSimpleEcommerce.Main.Domain.Mappings; 4 | using NotSoSimpleEcommerce.Repositories.Contracts; 5 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 6 | using NotSoSimpleEcommerce.Shared.Models; 7 | 8 | namespace NotSoSimpleEcommerce.Main.Domain.CommandHandlers 9 | { 10 | public sealed class RegisterProductCommandHandler : IRequestHandler 11 | { 12 | private readonly ICreateEntityRepository _createEntityRepository; 13 | 14 | public RegisterProductCommandHandler 15 | ( 16 | ICreateEntityRepository createEntityRepository 17 | ) 18 | { 19 | _createEntityRepository = createEntityRepository ?? throw new ArgumentNullException(nameof(createEntityRepository)); 20 | } 21 | 22 | public async Task Handle(RegisterProductCommand request, CancellationToken cancellationToken) 23 | { 24 | var productEntity = await _createEntityRepository.ExecuteAsync(request.MapToEntity(), cancellationToken); 25 | return productEntity.MapToResponse(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/Tasks/AwsSesEmailSenderProcessor.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using NotSoSimpleEcommerce.SesHandler.Abstractions; 3 | using NotSoSimpleEcommerce.SesHandler.Models; 4 | using NotSoSimpleEcommerce.SqsHandler.Abstractions; 5 | using NotSoSimpleEcommerce.SqsHandler.Models; 6 | 7 | namespace NotSoSimpleEcommerce.Notificator.Tasks 8 | { 9 | public sealed class AwsSesEmailSenderProcessor : IMessageProcessor 10 | { 11 | private readonly IEmailSender _emailSender; 12 | public AwsSesEmailSenderProcessor 13 | ( 14 | IEmailSender emailSender 15 | ) 16 | { 17 | _emailSender = emailSender ?? throw new ArgumentNullException(nameof(emailSender)); 18 | } 19 | 20 | public async Task ProcessMessageAsync(AwsQueueMessageParams awsQueueMessage, CancellationToken cancellationToken) 21 | { 22 | if (awsQueueMessage is null) 23 | throw new ArgumentNullException(nameof(awsQueueMessage)); 24 | 25 | var emailParams = JsonConvert.DeserializeObject(awsQueueMessage.Body); 26 | if (emailParams is null) 27 | throw new ArgumentNullException(nameof(emailParams)); 28 | 29 | await _emailSender.SendAsync(emailParams, cancellationToken); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { createBrowserRouter, RouterProvider } from 'react-router-dom'; 4 | 5 | import App from './App'; 6 | import Stock from './pages/stock/Index'; 7 | import Product from './pages/product/Index'; 8 | import Login from './pages/login/Index'; 9 | import Order from './pages/order/Index'; 10 | import Report from './pages/report/Index'; 11 | 12 | import './index.css'; 13 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 14 | 15 | const queryClient = new QueryClient(); 16 | const router = createBrowserRouter([{ 17 | path: '/', 18 | element: , 19 | children: [ 20 | { 21 | path: 'stock', 22 | element: 23 | }, 24 | { 25 | path: 'product', 26 | element: 27 | }, 28 | { 29 | path: 'order', 30 | element: 31 | }, 32 | { 33 | path: 'login', 34 | element: 35 | }, 36 | { 37 | path: 'report', 38 | element: 39 | }, 40 | { 41 | path: '*', 42 | element: 43 | }] 44 | }]) 45 | 46 | ReactDOM.createRoot(document.getElementById('root')).render( 47 | 48 | 49 | 50 | 51 | , 52 | ) 53 | -------------------------------------------------------------------------------- /iac/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | access_key = "mock_access_key" 3 | secret_key = "mock_secret_key" 4 | region = "us-east-1" 5 | 6 | skip_credentials_validation = true 7 | skip_metadata_api_check = true 8 | skip_requesting_account_id = true 9 | 10 | endpoints { 11 | sqs = "http://nsse.localstack.internal:4566" 12 | } 13 | } 14 | 15 | resource "aws_sqs_queue" "emailNotificationQueue" { 16 | name = "EmailNotificationQueue" 17 | delay_seconds = 90 18 | max_message_size = 2048 19 | message_retention_seconds = 86400 20 | receive_wait_time_seconds = 10 21 | tags = { 22 | Environment = "Local" 23 | } 24 | } 25 | 26 | resource "aws_sqs_queue" "productStockQueue" { 27 | name = "ProductStockQueue" 28 | delay_seconds = 90 29 | max_message_size = 2048 30 | message_retention_seconds = 86400 31 | receive_wait_time_seconds = 10 32 | tags = { 33 | Environment = "Local" 34 | } 35 | } 36 | 37 | resource "aws_sqs_queue" "invoiceQueue" { 38 | name = "InvoiceQueue" 39 | delay_seconds = 90 40 | max_message_size = 2048 41 | message_retention_seconds = 86400 42 | receive_wait_time_seconds = 10 43 | tags = { 44 | Environment = "Local" 45 | } 46 | } -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/Services/Implementations/UserService.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | using Microsoft.EntityFrameworkCore; 4 | using NotSoSimpleEcommerce.IdentityServer.Domain.Models; 5 | using NotSoSimpleEcommerce.IdentityServer.Domain.Services.Contracts; 6 | using NotSoSimpleEcommerce.Repositories.Contracts; 7 | using NotSoSimpleEcommerce.Shared.InOut.Requests; 8 | 9 | namespace NotSoSimpleEcommerce.IdentityServer.Domain.Services.Implementations 10 | { 11 | public sealed class UserService : IUserService 12 | { 13 | private readonly IReadEntityRepository _readRepository; 14 | public UserService(IReadEntityRepository readRepository) 15 | { 16 | _readRepository = readRepository ?? throw new ArgumentNullException(nameof(readRepository)); 17 | } 18 | 19 | public async Task CheckPasswordAsync(AuthRequest userRequest) 20 | { 21 | var user = await _readRepository.GetAll() 22 | .FirstOrDefaultAsync(user => user.Email == userRequest.Email); 23 | 24 | if (user is null) 25 | throw new KeyNotFoundException("User not found."); 26 | 27 | var hashedPassword = Encoding.UTF8.GetString(SHA256.HashData(Encoding.UTF8.GetBytes(userRequest.Password))); 28 | return user.Password == hashedPassword; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/CommandHandlers/DeleteProductCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Main.Domain.Commands; 3 | using NotSoSimpleEcommerce.Repositories.Contracts; 4 | using NotSoSimpleEcommerce.Shared.Models; 5 | 6 | namespace NotSoSimpleEcommerce.Main.Domain.CommandHandlers 7 | { 8 | public sealed class DeleteProductCommandHandler : IRequestHandler 9 | { 10 | private readonly IDeleteEntityRepository _deleteEntityRepository; 11 | private readonly IReadEntityRepository _readRepository; 12 | 13 | public DeleteProductCommandHandler 14 | ( 15 | IDeleteEntityRepository deleteEntityRepository, 16 | IReadEntityRepository readRepository 17 | ) 18 | { 19 | _deleteEntityRepository = deleteEntityRepository ?? throw new ArgumentNullException(nameof(deleteEntityRepository)); 20 | _readRepository = readRepository ?? throw new ArgumentNullException(nameof(readRepository)); 21 | 22 | } 23 | 24 | public async Task Handle(DeleteProductCommand request, CancellationToken cancellationToken) 25 | { 26 | var entity = await _readRepository.GetByIdAsync(request.Id, cancellationToken); 27 | if (entity is not null) 28 | await _deleteEntityRepository.ExecuteAsync(entity, cancellationToken); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | /* color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; */ 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | a { 18 | font-weight: 500; 19 | color: #FFF; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #1976d2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #1976d2; 52 | 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | @media (prefers-color-scheme: light) { 60 | :root { 61 | color: #213547; 62 | background-color: #ffffff; 63 | } 64 | a:hover { 65 | color: #747bff; 66 | } 67 | button { 68 | background-color: #f9f9f9; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/Controllers/StockController.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.EntityFrameworkCore; 5 | using NotSoSimpleEcommerce.Main.Domain.Mappings; 6 | using NotSoSimpleEcommerce.Main.Domain.Repositories.Contracts; 7 | using NotSoSimpleEcommerce.Shared.Models; 8 | 9 | namespace NotSoSimpleEcommerce.Main.Controllers 10 | { 11 | [Authorize] 12 | [Route("api/stock")] 13 | [ApiController] 14 | public sealed class StockController : ControllerBase 15 | { 16 | private readonly IStockReadRepository _stockReadRepository; 17 | public StockController(IStockReadRepository stockReadRepository) 18 | { 19 | _stockReadRepository = stockReadRepository ?? throw new ArgumentNullException(nameof(stockReadRepository)); 20 | } 21 | 22 | [HttpGet] 23 | [ProducesResponseType((int)HttpStatusCode.OK)] 24 | [ProducesResponseType((int)HttpStatusCode.NoContent)] 25 | public async Task GetAllAsync(CancellationToken cancellationToken) 26 | { 27 | var stocks = await _stockReadRepository.GetAll() 28 | .Include(stock=> stock.Product) 29 | .ToListAsync(cancellationToken); 30 | 31 | if (!stocks.Any()) 32 | return NoContent(); 33 | 34 | return Ok(stocks.MapToResponse()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/frontend/src/hooks/useOrder.ts: -------------------------------------------------------------------------------- 1 | import { UseQueryResult, useMutation, useQuery } from "@tanstack/react-query"; 2 | import { OrderEntity } from "../types/Stock.type"; 3 | import { http } from "../utils/HttpClient"; 4 | 5 | export const getOrders = async () => { 6 | const { data } = await http.get("/order/api/request"); 7 | return data; 8 | }; 9 | 10 | export const useOrder = (): UseQueryResult => 11 | useQuery({ 12 | queryKey: ["order"], 13 | queryFn: getOrders, 14 | }); 15 | 16 | export type OrderRequest = { 17 | productId: number; 18 | quantity: number; 19 | }; 20 | 21 | export const useCreateOrder = ({onSuccess}) => 22 | useMutation({ 23 | mutationKey: ["createOrder"], 24 | mutationFn: (body: OrderRequest) => 25 | http.post(`/order/api/request`, body), 26 | onSuccess: () => onSuccess(), 27 | onError: () => {}, 28 | }); 29 | 30 | export const useUpdateOrder = ({onSuccess}) => 31 | useMutation({ 32 | mutationFn: (variables: { id: number; body: OrderRequest }) => 33 | http.put(`/order/api/request/${variables.id}`, variables.body), 34 | onSuccess: () => onSuccess(), 35 | onError: () => {}, 36 | }); 37 | 38 | export const useDeleteOrder = ({onSuccess}) => 39 | useMutation({ 40 | mutationKey: ["deleteOrder"], 41 | mutationFn: (variables: { id: number }) => 42 | http.delete(`/order/api/request/${variables.id}`), 43 | onSuccess: () => onSuccess(), 44 | onError: () => {}, 45 | }); 46 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/Modules/AwsModule.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SimpleEmail; 2 | using Amazon.SQS; 3 | using Autofac; 4 | using NotSoSimpleEcommerce.SesHandler.Abstractions; 5 | using NotSoSimpleEcommerce.SesHandler.Implementations; 6 | 7 | namespace NotSoSimpleEcommerce.Notificator.Modules; 8 | 9 | public class AwsModule: Module 10 | { 11 | protected override void Load(ContainerBuilder builder) 12 | { 13 | builder.Register(componentContext => 14 | { 15 | var configuration = componentContext.Resolve(); 16 | if (Convert.ToBoolean(configuration["LocalStack:IsEnabled"])){ 17 | var config = new AmazonSQSConfig 18 | { 19 | AuthenticationRegion = configuration["AWS_REGION"], 20 | ServiceURL = configuration["LocalStack:ServiceURL"] 21 | }; 22 | 23 | return new AmazonSQSClient(config); 24 | } 25 | 26 | var client = configuration 27 | .GetAWSOptions() 28 | .CreateServiceClient(); 29 | 30 | return client; 31 | }) 32 | .Named(nameof(IAmazonSQS)) 33 | .SingleInstance(); 34 | 35 | builder.RegisterType() 36 | .As(); 37 | 38 | builder.RegisterType() 39 | .As(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/frontend/src/hooks/useProduct.ts: -------------------------------------------------------------------------------- 1 | import { UseQueryResult, useMutation, useQuery } from "@tanstack/react-query"; 2 | import { ProductEntity } from "../types/Stock.type"; 3 | import { http } from "../utils/HttpClient"; 4 | 5 | export const getProducts = async () => { 6 | const { data } = await http.get("main/api/product"); 7 | return data; 8 | }; 9 | 10 | export const useProduct = (): UseQueryResult => 11 | useQuery({ 12 | queryKey: ["product"], 13 | queryFn: getProducts, 14 | }); 15 | 16 | export type ProductRequest = { 17 | name: string; 18 | price: number; 19 | }; 20 | 21 | export const useCreateProduct = ({onSuccess}) => 22 | useMutation({ 23 | mutationKey: ["createProduct"], 24 | mutationFn: (body: ProductRequest) => http.post(`/main/api/product`, body), 25 | onSuccess: () => onSuccess(), 26 | onError: () => {}, 27 | }); 28 | 29 | export const useUpdateProduct = ({onSuccess}) => 30 | useMutation({ 31 | mutationKey: ["updateProduct"], 32 | mutationFn: (variables: { id: number; body: ProductRequest }) => 33 | http.put(`/main/api/product/${variables.id}`, variables.body), 34 | onSuccess: () => onSuccess(), 35 | onError: () => {}, 36 | }); 37 | 38 | export const useDeleteProduct = ({onSuccess}) => 39 | useMutation({ 40 | mutationKey: ["deleteProduct"], 41 | mutationFn: (variables: { id: number }) => http.delete(`/main/api/product/${variables.id}`), 42 | onSuccess: () => onSuccess(), 43 | onError: () => {}, 44 | }); 45 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Mappings/CommandToEntityMapping.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Main.Domain.Commands; 2 | using NotSoSimpleEcommerce.Shared.Models; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Mappings 5 | { 6 | public static class CommandToEntityMapping 7 | { 8 | public static StockEntity MapToEntity(this RegisterProductStockCommand stock) 9 | { 10 | return new StockEntity 11 | { 12 | Quantity = stock.Quantity, 13 | ProductId = stock.ProductId 14 | }; 15 | } 16 | 17 | public static ProductEntity MapToEntity(this RegisterProductCommand product) 18 | { 19 | return new ProductEntity 20 | { 21 | Name = product.Name, 22 | Price = product.Price 23 | }; 24 | } 25 | 26 | public static ProductEntity MapToEntity(this UpdateProductCommand product) 27 | { 28 | return new ProductEntity 29 | { 30 | Id = product.Id, 31 | Name = product.Name, 32 | Price = product.Price 33 | }; 34 | } 35 | 36 | public static StockEntity MapToEntity(this UpdateProductStockCommand product) 37 | { 38 | return new StockEntity 39 | { 40 | Id = product.Id, 41 | ProductId = product.ProductId, 42 | Quantity = product.Quantity 43 | }; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Repositories/Contexts/OrderContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NotSoSimpleEcommerce.Order.Domain.Repositories.Configurations; 3 | using NotSoSimpleEcommerce.Shared.Enums; 4 | using NotSoSimpleEcommerce.Shared.Models; 5 | using NotSoSimpleEcommerce.Shared.Repositories.Configurations; 6 | 7 | namespace NotSoSimpleEcommerce.Order.Domain.Repositories.Contexts 8 | { 9 | public class OrderContext : DbContext 10 | { 11 | public OrderContext(DbContextOptions options) : base(options) { } 12 | public DbSet Order { get; set; } = null!; 13 | public DbSet Product { get; set; } = null!; 14 | protected override void OnModelCreating(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(OrderEntityTypeConfiguration).Assembly); 17 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProductEntityTypeConfiguration).Assembly); 18 | 19 | modelBuilder.Entity().HasData( 20 | new StatusEntity 21 | ( 22 | OrderStatus.Pendente, 23 | Enum.GetName(OrderStatus.Pendente)! 24 | ), 25 | new StatusEntity 26 | ( 27 | OrderStatus.Confirmada, 28 | Enum.GetName(OrderStatus.Confirmada)! 29 | ) 30 | ); 31 | 32 | base.OnModelCreating(modelBuilder); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/frontend/src/hooks/useStock.ts: -------------------------------------------------------------------------------- 1 | import { UseQueryResult, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 2 | import { StockEntity } from "../types/Stock.type"; 3 | import { http } from "../utils/HttpClient"; 4 | 5 | export const getStocks = async () => { 6 | const { data } = await http.get("/main/api/stock"); 7 | return data; 8 | }; 9 | 10 | export const useStock = (): UseQueryResult => 11 | useQuery({ 12 | queryKey: ["stock"], 13 | queryFn: getStocks, 14 | }); 15 | 16 | export type StockRequest = { 17 | quantity: number; 18 | }; 19 | 20 | export const useCreateStock = ({onSuccess}) => 21 | useMutation({ 22 | mutationKey: ["createStock"], 23 | mutationFn: (variables: { id: number; body: StockRequest }) => 24 | http.post(`/main/api/product/${variables.id}/stock`, variables.body), 25 | onSuccess: () => onSuccess(), 26 | onError: () => {}, 27 | }); 28 | 29 | export const useUpdateStock = ({onSuccess}) => 30 | useMutation({ 31 | mutationKey: ["updateStock"], 32 | mutationFn: (variables: { id: number; body: StockRequest }) => 33 | http.put(`/main/api/product/${variables.id}/stock`, variables.body), 34 | onSuccess: () => onSuccess(), 35 | onError: () => {}, 36 | }); 37 | 38 | export const useDeleteStock = ({onSuccess}) => 39 | useMutation({ 40 | mutationKey: ["deleteStock"], 41 | mutationFn: (variables: { id: number }) => 42 | http.delete(`/main/api/product/${variables.id}/stock`), 43 | onSuccess: () => onSuccess(), 44 | onError: () => {}, 45 | }); 46 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/CommandHandlers/DeleteOrderCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.EntityFrameworkCore; 3 | using NotSoSimpleEcommerce.Order.Domain.Commands; 4 | using NotSoSimpleEcommerce.Repositories.Contracts; 5 | using NotSoSimpleEcommerce.Shared.Models; 6 | 7 | namespace NotSoSimpleEcommerce.Order.Domain.CommandHandlers 8 | { 9 | public sealed class DeleteOrderCommandHandler : IRequestHandler 10 | { 11 | private readonly IDeleteEntityRepository _deleteEntityRepository; 12 | private readonly IReadEntityRepository _readRepository; 13 | 14 | public DeleteOrderCommandHandler 15 | ( 16 | IDeleteEntityRepository deleteEntityRepository, 17 | IReadEntityRepository readRepository 18 | ) 19 | { 20 | _deleteEntityRepository = deleteEntityRepository ?? throw new ArgumentNullException(nameof(deleteEntityRepository)); 21 | _readRepository = readRepository ?? throw new ArgumentNullException(nameof(readRepository)); 22 | 23 | } 24 | 25 | public async Task Handle(DeleteOrderCommand request, CancellationToken cancellationToken) 26 | { 27 | var entity = await _readRepository.GetAll() 28 | .FirstOrDefaultAsync(order => order.Id == request.Id, cancellationToken: cancellationToken); 29 | 30 | if (entity is not null) 31 | await _deleteEntityRepository.ExecuteAsync(entity, cancellationToken); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/CommandHandlers/DeleteProductStockCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.EntityFrameworkCore; 3 | using NotSoSimpleEcommerce.Main.Domain.Commands; 4 | using NotSoSimpleEcommerce.Repositories.Contracts; 5 | using NotSoSimpleEcommerce.Shared.Models; 6 | 7 | namespace NotSoSimpleEcommerce.Main.Domain.CommandHandlers; 8 | 9 | public sealed class DeleteProductStockCommandHandler: IRequestHandler 10 | { 11 | private readonly IDeleteEntityRepository _deleteEntityRepository; 12 | private readonly IReadEntityRepository _productReadRepository; 13 | 14 | public DeleteProductStockCommandHandler 15 | ( 16 | IDeleteEntityRepository deleteEntityRepository, 17 | IReadEntityRepository productReadRepository 18 | ) 19 | { 20 | _deleteEntityRepository = deleteEntityRepository ?? throw new ArgumentNullException(nameof(deleteEntityRepository)); 21 | _productReadRepository = productReadRepository ?? throw new ArgumentNullException(nameof(productReadRepository)); 22 | } 23 | 24 | public async Task Handle(DeleteProductStockCommand request, CancellationToken cancellationToken) 25 | { 26 | var entity = await _productReadRepository.GetAll() 27 | .Include(stock => stock.Product) 28 | .FirstOrDefaultAsync(product => product.Id == request.Id, cancellationToken); 29 | 30 | if (entity is not null) 31 | await _deleteEntityRepository.ExecuteAsync(entity, cancellationToken); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SnsHandler/Implementations/AwsSnsMessageSender.cs: -------------------------------------------------------------------------------- 1 | using Amazon.Runtime; 2 | using Amazon.SimpleNotificationService; 3 | using Amazon.SimpleNotificationService.Model; 4 | using Newtonsoft.Json; 5 | using NotSoSimpleEcommerce.SnsHandler.Abstractions; 6 | using NotSoSimpleEcommerce.SnsHandler.Exceptions; 7 | using NotSoSimpleEcommerce.SnsHandler.Models; 8 | 9 | namespace NotSoSimpleEcommerce.SnsHandler.Implementations; 10 | 11 | public sealed class AwsSnsMessageSender: IMessageSender 12 | { 13 | private readonly IAmazonSimpleNotificationService _snsClient; 14 | private readonly AwsSnsMessageParams _snsMessageParams; 15 | 16 | public AwsSnsMessageSender(IAmazonSimpleNotificationService snsClient, AwsSnsMessageParams snsMessageParams) 17 | { 18 | _snsClient = snsClient ?? throw new ArgumentNullException(nameof(snsClient)); 19 | _snsMessageParams = snsMessageParams?? throw new ArgumentNullException(nameof(snsMessageParams)); 20 | } 21 | 22 | public async Task EnqueueAsync(TObject messageBody, CancellationToken cancellationToken) 23 | { 24 | var request = new PublishRequest 25 | { 26 | TopicArn = _snsMessageParams.TopicArn, 27 | Message = JsonConvert.SerializeObject(messageBody) 28 | }; 29 | 30 | var response = await _snsClient.PublishAsync(request, cancellationToken); 31 | if (response.ResponseMetadata.ChecksumValidationStatus != ChecksumValidationStatus.SUCCESSFUL) 32 | throw new AwsSnsMessageSenderException("The message is corrupted."); 33 | 34 | return response.MessageId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Mappings/RequestToCommandMapping.cs: -------------------------------------------------------------------------------- 1 | using NotSoSimpleEcommerce.Main.Domain.Commands; 2 | using NotSoSimpleEcommerce.Main.Domain.InOut.Requests; 3 | 4 | namespace NotSoSimpleEcommerce.Main.Domain.Mappings 5 | { 6 | public static class RequestToCommandMapping 7 | { 8 | public static RegisterProductStockCommand MapToRegisterProductStockCommand(this StockRequest stock, int productId) 9 | { 10 | return new RegisterProductStockCommand 11 | { 12 | ProductId = productId, 13 | Quantity = stock.Quantity 14 | }; 15 | } 16 | 17 | public static UpdateProductStockCommand MapToUpdateProductStockCommand(this StockRequest stock, int productId) 18 | { 19 | return new UpdateProductStockCommand 20 | { 21 | ProductId = productId, 22 | Quantity = stock.Quantity 23 | }; 24 | } 25 | 26 | public static RegisterProductCommand MapToRegisterCommand(this ProductRequest product) 27 | { 28 | return new RegisterProductCommand 29 | { 30 | Name = product.Name, 31 | Price = product.Price 32 | }; 33 | } 34 | 35 | public static UpdateProductCommand MapToUpdateCommand(this ProductRequest product, int id) 36 | { 37 | return new UpdateProductCommand 38 | { 39 | Id = id, 40 | Name = product.Name, 41 | Price = product.Price 42 | }; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/Modules/DomainModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Core; 3 | using MediatR.Extensions.Autofac.DependencyInjection; 4 | using MediatR.Extensions.Autofac.DependencyInjection.Builder; 5 | using Microsoft.Extensions.Options; 6 | using NotSoSimpleEcommerce.Main.Domain.Commands; 7 | using NotSoSimpleEcommerce.Main.Domain.Tasks; 8 | using NotSoSimpleEcommerce.SqsHandler.Abstractions; 9 | using NotSoSimpleEcommerce.SqsHandler.Implementations; 10 | using NotSoSimpleEcommerce.SqsHandler.Models; 11 | 12 | namespace NotSoSimpleEcommerce.Main.Modules 13 | { 14 | public class DomainModule: Module 15 | { 16 | protected override void Load(ContainerBuilder builder) 17 | { 18 | builder.RegisterType() 19 | .Named(nameof(ProductStockProcessor)); 20 | 21 | builder.RegisterType() 22 | .As() 23 | .WithParameter( 24 | new ResolvedParameter( 25 | (i, _) => i.ParameterType == typeof(AwsSqsQueueMonitorParams), 26 | (_, c) => c.Resolve>() 27 | .Get("AwsSqsQueueMonitorParams01") 28 | ) 29 | ); 30 | 31 | var configuration = MediatRConfigurationBuilder 32 | .Create(typeof(RegisterProductCommand).Assembly) 33 | .WithAllOpenGenericHandlerTypesRegistered() 34 | .Build(); 35 | 36 | builder.RegisterMediatR(configuration); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /certificates/signing-request.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIEZjCCAk4CAQAwITEfMB0GA1UEAwwWZGV2b3BzbmFudXZlbS5pbnRlcm5hbDCC 3 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL9z+7Rb7qcwt3Y/LAkGtlbX 4 | 9ccZ0zIf+yRS/huG+Xu4c8Ox7KqQIFe6tOA2HdmxOGDv6SGcT5n6Rl2ZmUSKqkc8 5 | +5dHaLdwMceY3GdIhLsmvn7zzIwQe4ezuSd6wKSOJPPNiKsy5yk/iWVIfGT2OZPA 6 | UGhAJ++QU1qhXuzmE7+sLV8/EyiHTd1jg6fGvscv3stOptXdqmYB1R2AIyqiUuhu 7 | zq9S3+KZ6Tv2xtkyFWY6zqmyj2BMKWZnoxZkW2BUmh9uyPxf7zad//pn2u1KfVO+ 8 | QrcE8OVoj/nRIxyn7/9IErH3uxsL0AO5y5XicAYuEWR1oFuMBgIOl/kJcEKAWhoO 9 | vCZXXJDfeqqbOCfWvQ0B7L3sPwZCtTCZDY6LI2op/iP04lmKmeT2WxMrZ3xHOxfv 10 | BhLZzrNyDKsa2v5UI0uKaXKRqO2RvCOzLEODaTEuYl+3BtmnA4vyj2Unzn7xSyQ7 11 | YpzJigmGlILINyhTxXfLKRIbrTTnZoVfYJwW5z8Ee7UL2sFuDqoazhtc4VG+0nA4 12 | CARnOEI8HjA330VFOOAQKRshJcbTC6/ex8RfpsTOPX421n1FlZ+ZC0xeLcmXk49B 13 | 3vIUgIk0StEXVinbn+6fbRqNiIKp+PY+4mEAYS4P+PxFgaTHK1tu6kjBi32X4ORH 14 | BgTt89wCkDBqATQAppdPAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAgEAdZnF9GpS 15 | DiY3yGzMR6fux1ZRBu6InxMbxT2F9QHSQ2cVCr1MejUUpQf7onWvdFy6ysEDiI/c 16 | P5s3I7tqa2+f+OjYE6OTXpIZgTCmJ3NhyqlI2ii+2ufVmwZB1X9OE0GYjIvAcGxd 17 | 3Q2oM5C0gPQ8+6FJpj6U1dJi5gL72K1rBf1ssh2Wbl4cDL5Q69V+2zQK8OMufrwC 18 | 3PHHSAyUtcZnARKzr4snzVXqY89In43JXwBsykmoPIoZk/bKqFmlD8LMnbh5aAeU 19 | mrM9Syb+dXXFpZYCWWFb40GT4cq+tI/GagkwqRVrjZSta/DOvCUL2wWEqc6Ss2cV 20 | F2gGkgAHR3HlNThdKyNhBGQLW377cXL2oJDf5KD8Ein6bsKfIttGZTJtN0f3vsA7 21 | wEKAI8e4wi+8z7h6oGOd8p33hEHtIjhT7crPP6+a1vs3sq3jVVNcExVeKWB0NRjL 22 | L/+EubgXLUGGLynAs9YnognSgBPbxStxRJW8ksrcHdE2q0OvEWuEP8sCBjDyHb34 23 | VMO6BxcX34u4iqxyANtsud4hovwr0aZIyibVk85QzmR8r9+Kc3/xonzKvZZyuPsV 24 | dzm4Lw6gG7/r4ylPtr3ekd6Qtw2rfuurfbbF88Yy1O697OAXvuyRElhRAjOw6/ZU 25 | /1PlBplwHJNK+GTWnf/ND1VFaQZvc8qFa4E= 26 | -----END CERTIFICATE REQUEST----- 27 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/Migrations/20230720174257_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 3 | 4 | #nullable disable 5 | 6 | namespace NotSoSimpleEcommerce.IdentityServer.Domain.Migrations 7 | { 8 | /// 9 | public partial class InitialCreate : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.CreateTable( 15 | name: "User", 16 | columns: table => new 17 | { 18 | Id = table.Column(type: "integer", nullable: false) 19 | .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), 20 | Email = table.Column(type: "text", nullable: false), 21 | Password = table.Column(type: "text", nullable: false) 22 | }, 23 | constraints: table => 24 | { 25 | table.PrimaryKey("PK_User", x => x.Id); 26 | }); 27 | 28 | migrationBuilder.InsertData( 29 | table: "User", 30 | columns: new[] { "Id", "Email", "Password" }, 31 | values: new object[] { 1, "admin@nsse.com", "yJ�w�flVG6�@ 85F�Q�S�0�C�s'P��" }); 32 | } 33 | 34 | /// 35 | protected override void Down(MigrationBuilder migrationBuilder) 36 | { 37 | migrationBuilder.DropTable( 38 | name: "User"); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.HealthChecker/NotSoSimpleEcommerce.HealthChecker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | fc9b0bff-7045-48dc-9a67-98f28d507abd 8 | Linux 9 | ..\docker-compose.dcproj 10 | NotSoSimpleEcommerce.HealthChecker 11 | NotSoSimpleEcommerce.HealthChecker 12 | ..\..\.. 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/Repositories/Configurations/OrderEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using NotSoSimpleEcommerce.Shared.Models; 4 | 5 | namespace NotSoSimpleEcommerce.Order.Domain.Repositories.Configurations; 6 | 7 | public class OrderEntityTypeConfiguration: IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.ToTable("Order"); 12 | builder.HasKey(order => order.Id); 13 | builder 14 | .Property(order => order.Id) 15 | .HasColumnType("integer") 16 | .ValueGeneratedOnAdd(); 17 | 18 | builder 19 | .Property(order => order.StatusId) 20 | .HasColumnType("integer") 21 | .HasConversion() 22 | .IsRequired(); 23 | 24 | builder 25 | .Property(order => order.Quantity) 26 | .HasColumnType("integer") 27 | .IsRequired(); 28 | 29 | builder 30 | .Property(order => order.BoughtBy) 31 | .HasColumnType("text") 32 | .IsRequired(); 33 | 34 | builder 35 | .Property(order => order.ProductId) 36 | .HasColumnType("integer") 37 | .IsRequired(); 38 | 39 | builder 40 | .HasOne(order => order.Product) 41 | .WithOne() 42 | .HasForeignKey(order => order.ProductId); 43 | 44 | builder 45 | .HasOne(order => order.Status) 46 | .WithOne() 47 | .HasForeignKey(order => order.StatusId); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/Repositories/Contexts/IdentityServerContext.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.Configuration; 5 | using NotSoSimpleEcommerce.IdentityServer.Domain.Models; 6 | using NotSoSimpleEcommerce.IdentityServer.Domain.Repositories.Configurations; 7 | 8 | namespace NotSoSimpleEcommerce.IdentityServer.Domain.Repositories.Contexts 9 | { 10 | public sealed class IdentityServerContext : DbContext 11 | { 12 | private readonly IConfiguration _configuration; 13 | 14 | public IdentityServerContext(DbContextOptions options, IConfiguration configuration) : 15 | base(options) 16 | { 17 | _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 18 | } 19 | 20 | public DbSet User { get; set; } = null!; 21 | protected override void OnModelCreating(ModelBuilder modelBuilder) 22 | { 23 | var password =_configuration.GetValue("Identity:Admin:User:Password")!; 24 | var hashedPassword = SHA256.HashData(Encoding.UTF8.GetBytes(password)); 25 | 26 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(UserEntityTypeConfiguration).Assembly); 27 | 28 | modelBuilder.Entity().HasData( 29 | new UserEntity 30 | ( 31 | id:1, 32 | email: _configuration.GetValue("Identity:Admin:User")!, 33 | password:Encoding.UTF8.GetString(hashedPassword) 34 | ) 35 | ); 36 | 37 | base.OnModelCreating(modelBuilder); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/Modules/InfrastructureModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Core; 3 | using Microsoft.EntityFrameworkCore; 4 | using NotSoSimpleEcommerce.IdentityServer.Domain.Repositories.Contexts; 5 | using NotSoSimpleEcommerce.Repositories.Contracts; 6 | using NotSoSimpleEcommerce.Repositories.Implementations; 7 | 8 | namespace NotSoSimpleEcommerce.IdentityServer.Modules 9 | { 10 | public class InfrastructureModule : Module 11 | { 12 | private readonly IConfiguration _configuration; 13 | 14 | public InfrastructureModule(IConfiguration configuration) 15 | { 16 | _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 17 | } 18 | 19 | protected override void Load(ContainerBuilder builder) 20 | { 21 | builder.Register(_ => 22 | { 23 | var options = new DbContextOptionsBuilder(); 24 | options.UseNpgsql(_configuration.GetConnectionString("Default")); 25 | var dbContext = new IdentityServerContext(options.Options, _configuration); 26 | dbContext.Database.Migrate(); 27 | return dbContext; 28 | }) 29 | .InstancePerLifetimeScope(); 30 | 31 | builder 32 | .RegisterGeneric(typeof(ReadEntityRepository<>)) 33 | .As(typeof(IReadEntityRepository<>)) 34 | .WithParameter( 35 | new ResolvedParameter( 36 | (i, _) => i.ParameterType == typeof(DbContext), 37 | (_, c) => c.Resolve()) 38 | ) 39 | .InstancePerLifetimeScope(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/CommandHandlers/UpdateProductCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Main.Domain.Commands; 3 | using NotSoSimpleEcommerce.Main.Domain.Mappings; 4 | using NotSoSimpleEcommerce.Repositories.Contracts; 5 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 6 | using NotSoSimpleEcommerce.Shared.Models; 7 | 8 | namespace NotSoSimpleEcommerce.Main.Domain.CommandHandlers 9 | { 10 | internal sealed class UpdateProductCommandHandler : IRequestHandler 11 | { 12 | private readonly IUpdateEntityRepository _updateEntityRepository; 13 | private readonly IReadEntityRepository _readRepository; 14 | 15 | public UpdateProductCommandHandler 16 | ( 17 | IUpdateEntityRepository updateEntityRepository, 18 | IReadEntityRepository readRepository 19 | 20 | ) 21 | { 22 | _updateEntityRepository = updateEntityRepository ?? throw new ArgumentNullException(nameof(updateEntityRepository)); 23 | _readRepository = readRepository ?? throw new ArgumentNullException(nameof(readRepository)); 24 | } 25 | 26 | public async Task Handle(UpdateProductCommand request, CancellationToken cancellationToken) 27 | { 28 | var product = await _readRepository.GetByIdAsync(request.Id, cancellationToken); 29 | if (product is null) 30 | throw new KeyNotFoundException("The specified product doesn't exist."); 31 | 32 | var productEntity = await _updateEntityRepository.ExecuteAsync(request.MapToEntity(), cancellationToken); 33 | return productEntity.MapToResponse(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/Controllers/ReportController.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | using MongoDB.Bson; 5 | using MongoDB.Driver; 6 | using NotSoSimpleEcommerce.Main.Domain.Models; 7 | 8 | namespace NotSoSimpleEcommerce.Main.Controllers 9 | { 10 | [ApiController] 11 | //[Authorize] 12 | [Route("api/report")] 13 | public class ReportController : ControllerBase 14 | { 15 | 16 | [HttpGet("order")] 17 | [ProducesResponseType((int)HttpStatusCode.OK)] 18 | [ProducesResponseType((int)HttpStatusCode.NoContent)] 19 | public async Task GetAllAsync 20 | ( 21 | CancellationToken cancellationToken, 22 | [FromServices] IConfiguration configuration 23 | ) 24 | { 25 | var settings = MongoClientSettings.FromUrl(new MongoUrl(configuration.GetConnectionString("Mongo"))); 26 | settings.SslSettings = new SslSettings 27 | { 28 | ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true 29 | }; 30 | 31 | var client = new MongoClient(settings); 32 | 33 | var database = client.GetDatabase(configuration.GetValue("Mongo:Database")); 34 | 35 | //TDO testar entidade e tb allow hostname invalid na connection string e trech acima 36 | var collection = database.GetCollection(configuration.GetValue("Mongo:Report_Collection")); 37 | var report = await collection.Find(_ => true).ToListAsync(cancellationToken); 38 | 39 | if (!report.Any()) 40 | return NoContent(); 41 | 42 | return Ok(report); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/CommandHandlers/UpdateProductImageCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.Extensions.Configuration; 3 | using NotSoSimpleEcommerce.Main.Domain.Commands; 4 | using NotSoSimpleEcommerce.S3Handler.Abstractions; 5 | using NotSoSimpleEcommerce.S3Handler.Models; 6 | 7 | namespace NotSoSimpleEcommerce.Main.Domain.CommandHandlers; 8 | 9 | public sealed class UpdateProductImageCommandHandler : IRequestHandler 10 | { 11 | private readonly IObjectManager _objectManager; 12 | private readonly IConfiguration _configuration; 13 | 14 | public UpdateProductImageCommandHandler 15 | ( 16 | IObjectManager objectManager, 17 | IConfiguration configuration 18 | ) 19 | { 20 | _objectManager = objectManager ?? throw new ArgumentNullException(nameof(objectManager)); 21 | _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 22 | } 23 | 24 | public async Task Handle(UpdateProductImageCommand command, CancellationToken cancellationToken) 25 | { 26 | using var stream = new MemoryStream(); 27 | await command.Image.CopyToAsync(stream, cancellationToken); 28 | var imageBytes = stream.ToArray(); 29 | if (imageBytes.Length == 0) 30 | throw new ArgumentNullException(nameof(command.Image)); 31 | 32 | //TODO add content-type validation 33 | var objectRegister = new ObjectRegister 34 | ( 35 | _configuration.GetValue("BucketName")!, 36 | $"ProductId-{command.Id}-{Guid.NewGuid().ToString()}", 37 | imageBytes, 38 | command.Image.ContentType 39 | ); 40 | 41 | var objectUrl = await _objectManager.PutObjectAsync(objectRegister, cancellationToken); 42 | return objectUrl; 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /docker-compose.infra.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | nsse-network: 3 | driver: bridge 4 | name: nsse-network 5 | 6 | services: 7 | 8 | nsse.terraform-init.internal: 9 | container_name: nsse.terraform-init.internal 10 | depends_on: 11 | - nsse.localstack.internal 12 | image: hashicorp/terraform:light 13 | command: ["init"] 14 | working_dir: ${TERRAFORM_CONTAINER_PATH} 15 | networks: 16 | - nsse-network 17 | volumes: 18 | - ${TERRAFORM_HOST_PATH}:${TERRAFORM_CONTAINER_PATH} 19 | 20 | nsse.terraform-apply.internal: 21 | container_name: nsse.terraform-apply.internal 22 | depends_on: 23 | - nsse.terraform-init.internal 24 | image: hashicorp/terraform:light 25 | command: ["apply", "-auto-approve"] 26 | working_dir: ${TERRAFORM_CONTAINER_PATH} 27 | networks: 28 | - nsse-network 29 | volumes: 30 | - ${TERRAFORM_HOST_PATH}:${TERRAFORM_CONTAINER_PATH} 31 | 32 | nsse.localstack.internal: 33 | container_name: nsse.localstack.internal 34 | image: localstack/localstack 35 | ports: 36 | - 4566:4566 37 | - 4510-4559:4510-4559 38 | networks: 39 | - nsse-network 40 | 41 | nsse.nginx.internal: 42 | container_name: nsse.nginx.internal 43 | image: nginx:1.25.2-alpine 44 | ports: 45 | - 44300:44300 46 | volumes: 47 | - ${NGINX_CONF_HOST_PATH}:${NGINX_CONF_CONTAINER_PATH} 48 | - ${NGINX_CERT_HOST_PATH}:${NGINX_CERT_CONTAINER_PATH} 49 | - ${NGINX_KEY_HOST_PATH}:${NGINX_KEY_CONTAINER_PATH} 50 | networks: 51 | - nsse-network 52 | 53 | nsse.database.internal: 54 | container_name: nsse.database.internal 55 | image: postgres:16.0-alpine 56 | ports: 57 | - 5432:5432 58 | environment: 59 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 60 | - POSTGRES_DB=${POSTGRES_DB} 61 | networks: 62 | - nsse-network -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.HealthChecker/Program.cs: -------------------------------------------------------------------------------- 1 | using HealthChecks.UI.Client; 2 | using Microsoft.AspNetCore.Diagnostics.HealthChecks; 3 | using Serilog; 4 | using Serilog.Events; 5 | using Serilog.Exceptions; 6 | 7 | Log.Logger = new LoggerConfiguration() 8 | .WriteTo.Console(LogEventLevel.Information) 9 | .Enrich.WithExceptionDetails() 10 | .Enrich.WithMachineName() 11 | .Enrich.WithEnvironmentName() 12 | .CreateLogger(); 13 | try 14 | { 15 | Log.Information("Starting Health Checker Microservice"); 16 | var builder = WebApplication.CreateBuilder(args); 17 | builder.Services 18 | .AddHealthChecks() 19 | .AddNpgSql(builder.Configuration.GetConnectionString("Default")!, name: "Postgresql"); 20 | 21 | builder.Services 22 | .AddHealthChecksUI() 23 | .AddInMemoryStorage(); 24 | 25 | builder.Services.AddControllers(); 26 | builder.Host.UseSerilog(); 27 | 28 | var app = builder.Build(); 29 | app.Map("/healthchecks", applicationBuilder => 30 | { 31 | applicationBuilder.UseRouting(); 32 | applicationBuilder.UseEndpoints(endpoints => 33 | { 34 | endpoints.MapHealthChecks("/health", new HealthCheckOptions 35 | { 36 | Predicate = _ => true, 37 | ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse 38 | }); 39 | 40 | endpoints.MapHealthChecksUI(options => 41 | { 42 | options.UIPath = "/ui"; 43 | }); 44 | }); 45 | applicationBuilder.UseStaticFiles(); 46 | }); 47 | 48 | app.Run(); 49 | 50 | } 51 | catch (HostAbortedException exception) 52 | { 53 | Log.Warning(exception, "Executing migrations? All good."); 54 | } 55 | catch (Exception exception) 56 | { 57 | Log.Fatal(exception, "Application terminated unexpectedly"); 58 | } 59 | finally 60 | { 61 | Log.CloseAndFlush(); 62 | } 63 | -------------------------------------------------------------------------------- /certificates/root-ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFIzCCAwugAwIBAgIUXzcSfQJMFGERxyOuHVgLf/VBhvwwDQYJKoZIhvcNAQEL 3 | BQAwITEfMB0GA1UEAwwWZGV2b3BzbmFudXZlbS5pbnRlcm5hbDAeFw0yNTAxMDgx 4 | NDI5NDdaFw0yNjAxMDgxNDI5NDdaMCExHzAdBgNVBAMMFmRldm9wc25hbnV2ZW0u 5 | aW50ZXJuYWwwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCkd2Uj0Mi5 6 | 8AYrExMJ6ArLIMv8GJVdnXzspysf2B3tv76SqNuDo3BeSICLxVb+Ctt2jv2uCT7o 7 | JBVb7qcJikWB1ekc+ixjqTsXtqGQkbqRcEaNDEG/jBl9ofmjvPMh4dxZUNbRkFAW 8 | fN+UgEMbQjl87zbaxq0NiPHtcUaSFUFg2B6NH2auto59DjlTAFXHvenjFmiOHOda 9 | iE31rsw+WMDdQVP42R4XXHX95qFFXWpMIBEmaE/abKDfTRP6jhoH9AKL7pU9OD+d 10 | w/NSvj2x/HfsNjQaipuqKQjUoXuQeffR9KNyxI5Q9NxjvfqBNY79TssLU+3Bo93K 11 | VgBEoOd+oyf+oY0uYAQZy7CpYC/oZKmNPhqA9PVMNknr6787KEsc+8ZoUvof4QWf 12 | No9h5Bwd7tw1h+ZH21ACEHh7OWFXcew4moplmwFesOPtFPrzMKmvbA/3YfoJ+clh 13 | +ejG4ju7ZESkBDGOn2Vh3uPkcW8VFRAdPCma1c8U6AZhZyHCmCvfdXxRweBcIPSG 14 | Gs/IP3HaWvTZnknN4839KxS5s5ydNFk18l4C+VVZjkmDrmqSvVDJw7VEOtKv1av3 15 | S/jYhgD4K5Gk+HiRhoVIFLSEwVC2/0zxAEeTL6UlT+J0q2XCqDkBMcFxacvi57ux 16 | vJXbDxGaJdbKupN7Y2uNVMxWGgEWPHlFxwIDAQABo1MwUTAdBgNVHQ4EFgQUD6H2 17 | Bl2a6gOg0//1xV1pCpb9yYUwHwYDVR0jBBgwFoAUD6H2Bl2a6gOg0//1xV1pCpb9 18 | yYUwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAMAAS027APo6/ 19 | 77uFzJ8I6jDbKSpbdXNpM6LlT04EEWpLKofIytzNpuQxjyus4TRfqjz+t+pzTuyk 20 | ZWTPDwjg1xwO9Fc6OZT+/Ym5i9VggbxFmMQ/0pQkf30KfgKphT3qZGlXOyT3aze/ 21 | hLgOKzmc2C1tzRGuuo5IhhDztSuWd3pGDe+LUT7ulzxWgGuElGq9nPIHXAixoHy1 22 | U1ipMQoT2jOk5bnY7jxCH69z/S9FX1gykQv5f8DTpQxeqOt6ddcJMNQJ3SeitUI4 23 | wfwKdaa0UDICD/pTPNmpnaz2HQ767CkgOzxIbpVvDaxar0dFjSdti0ZtxJcc8RLr 24 | y/sTGIa4JBYqExO1sGyk/YWEZBjaRO0TPKDeJUoJyMn+1pNmk5uXE/bmBrUZStyk 25 | kVrZHnOHNYs1NZJz2WydYwVAgcY32eCQ3PuHtbdpSvIFTCCdTYHXYgp7wykZoDIR 26 | y8UjiDN4mUWiz6eEq+l5FypkYg3tpucA40IMDf3dz0tHD3DHOR88CcLShXoQQGKB 27 | +oevf5vML8RDKZNGa7tlqLTorByfVQGCMa5aTzouaP3gvzJYe7yaHLtKc9FoShXi 28 | ihh3Wwc/Qxk3l+Uc7tI99a0HmVlZN9CP5fG/WHT/YnxxnDy61wHPs4OoUrj+SOn3 29 | wJ45YYlQapOURO9ouX/t9KPCYyS8cdY= 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/NotSoSimpleEcommerce.Main.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\6.0.9\Microsoft.AspNetCore.Http.Features.dll 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /certificates/nginx-certificate.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFQDCCAyigAwIBAgIUYcFUlyjyedORsVGAGCXZWLEKr1EwDQYJKoZIhvcNAQEL 3 | BQAwITEfMB0GA1UEAwwWZGV2b3BzbmFudXZlbS5pbnRlcm5hbDAeFw0yNTAxMDgx 4 | NDI5NDlaFw0yNjAxMDgxNDI5NDlaMCExHzAdBgNVBAMMFmRldm9wc25hbnV2ZW0u 5 | aW50ZXJuYWwwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/c/u0W+6n 6 | MLd2PywJBrZW1/XHGdMyH/skUv4bhvl7uHPDseyqkCBXurTgNh3ZsThg7+khnE+Z 7 | +kZdmZlEiqpHPPuXR2i3cDHHmNxnSIS7Jr5+88yMEHuHs7knesCkjiTzzYirMucp 8 | P4llSHxk9jmTwFBoQCfvkFNaoV7s5hO/rC1fPxMoh03dY4Onxr7HL97LTqbV3apm 9 | AdUdgCMqolLobs6vUt/imek79sbZMhVmOs6pso9gTClmZ6MWZFtgVJofbsj8X+82 10 | nf/6Z9rtSn1TvkK3BPDlaI/50SMcp+//SBKx97sbC9ADucuV4nAGLhFkdaBbjAYC 11 | Dpf5CXBCgFoaDrwmV1yQ33qqmzgn1r0NAey97D8GQrUwmQ2OiyNqKf4j9OJZipnk 12 | 9lsTK2d8RzsX7wYS2c6zcgyrGtr+VCNLimlykajtkbwjsyxDg2kxLmJftwbZpwOL 13 | 8o9lJ85+8UskO2KcyYoJhpSCyDcoU8V3yykSG60052aFX2CcFuc/BHu1C9rBbg6q 14 | Gs4bXOFRvtJwOAgEZzhCPB4wN99FRTjgECkbISXG0wuv3sfEX6bEzj1+NtZ9RZWf 15 | mQtMXi3Jl5OPQd7yFICJNErRF1Yp25/un20ajYiCqfj2PuJhAGEuD/j8RYGkxytb 16 | bupIwYt9l+DkRwYE7fPcApAwagE0AKaXTwIDAQABo3AwbjAsBgNVHREEJTAjghZk 17 | ZXZvcHNuYW51dmVtLmludGVybmFsgglsb2NhbGhvc3QwHQYDVR0OBBYEFHk3vpTF 18 | 5IALyTCK7XrbGcQuuG1bMB8GA1UdIwQYMBaAFA+h9gZdmuoDoNP/9cVdaQqW/cmF 19 | MA0GCSqGSIb3DQEBCwUAA4ICAQArbyYc1ojYtlMS8cOyJUGp7ko474j3uY6Hc/ri 20 | 9X4LhyhdO0ctHOuKaIBYoMiza551ETMzeJT4Zi++VA5oiaJFgYx6+BcqktBwDcN4 21 | FHxF6Lcja2R498u10/eUmsRfjJyt2YeBFxabQeDA4r0DNXuLtmjynDKzfsktPQwr 22 | JvyqwaGAI8bRNjMQdXqJdVvVLi9GxqB6n3jHVNx+okXhJ+ijAVITOCKot+/+et+I 23 | UMMmALQ7cDJumWnfw2UWy1ImE9zWyE/Hg6By7WOi7jxf/brLwsE0N1Ngevr8xJ7a 24 | 8VBngf0ctA0ZP+h3cKM0quOyQVbkgo2Pfq0kZa1sBZAEKkIBf8eltAQaOqyKlnpv 25 | +ckC+mhafPuVbUbbdRpTSyV7Ne7YTV4HuFZ8ASquMM5BbTnbU5OaO1KF9JF8CpB+ 26 | E7/fC+hhIe4C3zwFzauuskw4jsnsFgsfcTaDMMTwwLUYg2AupGLSNdYzEQ3QqZG5 27 | pjNchenZyURdRaSXBtcQ2c6Hxo2DTX3nc/7rMgu5bYnnfttk6c83w/uL2p1DGpX3 28 | aUkx7u/mtoSOGczdxZsfPKhQRvVmRBfPeDICBmNTh24FIrwqdy1AY7XQK59VmUCJ 29 | hOMGE5Ey6FIxxWvVaS/69JsnSvXqeQTZGFZ4vzK0CTt8IibKjJZ4tdZq7ZcIxiaD 30 | LZw9Jg== 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/NotSoSimpleEcommerce.Order.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/Middlewares/GlobalErrorHandlerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Serialization; 4 | 5 | namespace NotSoSimpleEcommerce.Main.Middlewares 6 | { 7 | internal sealed class GlobalErrorHandlerMiddleware : IMiddleware 8 | { 9 | private readonly ILogger _logger; 10 | public GlobalErrorHandlerMiddleware 11 | ( 12 | ILogger logger 13 | ) 14 | { 15 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 16 | } 17 | 18 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 19 | { 20 | try 21 | { 22 | await next(context); 23 | } 24 | catch (Exception exception) 25 | { 26 | _logger.LogError("[Exception]: {exception}", exception); 27 | await HandleExceptionAsync(context, exception); 28 | } 29 | } 30 | 31 | private async Task HandleExceptionAsync(HttpContext context, Exception exception) 32 | { 33 | var exceptionObject = new { error = exception.Message }; 34 | var jsonSerializerSettings = new JsonSerializerSettings 35 | { 36 | ContractResolver = new CamelCasePropertyNamesContractResolver() 37 | }; 38 | 39 | var exceptionSerialized = JsonConvert.SerializeObject(new 40 | { 41 | success = false, 42 | response = exceptionObject 43 | }, jsonSerializerSettings); 44 | 45 | if (!context.Response.HasStarted) 46 | { 47 | context.Response.ContentType = "application/json"; 48 | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 49 | 50 | await context.Response.WriteAsync(exceptionSerialized); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Order/Middlewares/GlobalErrorHandlerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Serialization; 4 | 5 | namespace NotSoSimpleEcommerce.Order.Middlewares 6 | { 7 | internal sealed class GlobalErrorHandlerMiddleware : IMiddleware 8 | { 9 | private readonly ILogger _logger; 10 | public GlobalErrorHandlerMiddleware 11 | ( 12 | ILogger logger 13 | ) 14 | { 15 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 16 | } 17 | 18 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 19 | { 20 | try 21 | { 22 | await next(context); 23 | } 24 | catch (Exception exception) 25 | { 26 | _logger.LogError("[Exception]: {exception}", exception); 27 | await HandleExceptionAsync(context, exception); 28 | } 29 | } 30 | 31 | private async Task HandleExceptionAsync(HttpContext context, Exception exception) 32 | { 33 | var exceptionObject = new { error = exception.Message }; 34 | var jsonSerializerSettings = new JsonSerializerSettings 35 | { 36 | ContractResolver = new CamelCasePropertyNamesContractResolver() 37 | }; 38 | 39 | var exceptionSerialized = JsonConvert.SerializeObject(new 40 | { 41 | success = false, 42 | response = exceptionObject 43 | }, jsonSerializerSettings); 44 | 45 | if (!context.Response.HasStarted) 46 | { 47 | context.Response.ContentType = "application/json"; 48 | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 49 | 50 | await context.Response.WriteAsync(exceptionSerialized); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/Middlewares/GlobalErrorHandlerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Serialization; 4 | 5 | namespace NotSoSimpleEcommerce.IdentityServer.Middlewares 6 | { 7 | internal sealed class GlobalErrorHandlerMiddleware : IMiddleware 8 | { 9 | private readonly ILogger _logger; 10 | public GlobalErrorHandlerMiddleware 11 | ( 12 | ILogger logger 13 | ) 14 | { 15 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 16 | } 17 | 18 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 19 | { 20 | try 21 | { 22 | await next(context); 23 | } 24 | catch (Exception exception) 25 | { 26 | _logger.LogError("[Exception]: {exception}", exception); 27 | await HandleExceptionAsync(context, exception); 28 | } 29 | } 30 | 31 | private async Task HandleExceptionAsync(HttpContext context, Exception exception) 32 | { 33 | var exceptionObject = new { error = exception.Message }; 34 | var jsonSerializerSettings = new JsonSerializerSettings 35 | { 36 | ContractResolver = new CamelCasePropertyNamesContractResolver() 37 | }; 38 | 39 | var exceptionSerialized = JsonConvert.SerializeObject(new 40 | { 41 | success = false, 42 | response = exceptionObject 43 | }, jsonSerializerSettings); 44 | 45 | if (!context.Response.HasStarted) 46 | { 47 | context.Response.ContentType = "application/json"; 48 | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 49 | 50 | await context.Response.WriteAsync(exceptionSerialized); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/Modules/AwsModule.cs: -------------------------------------------------------------------------------- 1 | using Amazon.S3; 2 | using Amazon.SQS; 3 | using Autofac; 4 | using Autofac.Core; 5 | using Microsoft.Extensions.Options; 6 | using NotSoSimpleEcommerce.S3Handler.Abstractions; 7 | using NotSoSimpleEcommerce.S3Handler.Implementations; 8 | using NotSoSimpleEcommerce.S3Handler.Models; 9 | 10 | namespace NotSoSimpleEcommerce.Main.Modules 11 | { 12 | public class AwsModule: Module 13 | { 14 | protected override void Load(ContainerBuilder builder) 15 | { 16 | builder.Register(componentContext => 17 | { 18 | var configuration = componentContext.Resolve(); 19 | if (Convert.ToBoolean(configuration["LocalStack:IsEnabled"])){ 20 | var config = new AmazonSQSConfig 21 | { 22 | AuthenticationRegion = configuration["AWS_REGION"], 23 | ServiceURL = configuration["LocalStack:ServiceURL"] 24 | }; 25 | 26 | return new AmazonSQSClient(config); 27 | } 28 | 29 | var client = configuration 30 | .GetAWSOptions() 31 | .CreateServiceClient(); 32 | 33 | return client; 34 | }) 35 | .Named(nameof(IAmazonSQS)) 36 | .SingleInstance(); 37 | 38 | builder.RegisterType() 39 | .As(); 40 | 41 | builder.RegisterType() 42 | .As() 43 | .WithParameter( 44 | new ResolvedParameter( 45 | (i, _) => i.ParameterType == typeof(AwsS3BucketParams), 46 | (_, c) => c.Resolve>() 47 | .Get("AwsS3Params01")) 48 | ); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/EventHandlers/OrderCreatedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Logging; 4 | using Newtonsoft.Json; 5 | using NotSoSimpleEcommerce.Order.Domain.Events; 6 | using NotSoSimpleEcommerce.SesHandler.Models; 7 | using NotSoSimpleEcommerce.Shared.Consts; 8 | using NotSoSimpleEcommerce.SqsHandler.Abstractions; 9 | 10 | namespace NotSoSimpleEcommerce.Order.Domain.EventHandlers; 11 | 12 | public sealed class OrderCreatedEventHandler: INotificationHandler 13 | { 14 | private readonly ILogger _logger; 15 | private readonly IMessageSender _messageToEmailQueue; 16 | private readonly IConfiguration _configuration; 17 | 18 | public OrderCreatedEventHandler 19 | ( 20 | ILogger logger, 21 | IMessageSender messageEmailQueue, 22 | IConfiguration configuration 23 | ) 24 | { 25 | _logger = logger ?? throw new ArgumentNullException(nameof( logger)); 26 | _messageToEmailQueue = messageEmailQueue ?? throw new ArgumentNullException(nameof(messageEmailQueue)); 27 | _configuration = configuration?? throw new ArgumentNullException(nameof(configuration)); 28 | } 29 | 30 | public async Task Handle(OrderCreatedEvent @event, CancellationToken cancellationToken) 31 | { 32 | _logger.LogInformation($"OrderCreatedEventHandler: Order created by {@event.BoughtBy}"); 33 | // var emailParams = new EmailParams 34 | // ( 35 | // _configuration.GetValue("Notificator:EmailConfiguration:From")!, 36 | // new List { @event.BoughtBy }, 37 | // Email.Subjects.OrderCreated, 38 | // Email.Templates.OrderCreated, 39 | // JsonConvert.SerializeObject(new { Username = @event.BoughtBy }) 40 | // ); 41 | // //TODO ProductStock only on confirmed Order 42 | // await _messageToEmailQueue.EnqueueAsync(emailParams, cancellationToken); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", // Specify ECMAScript target version 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], // List of library files to be included in the compilation 9 | "allowJs": true, // Allow JavaScript files to be compiled 10 | "skipLibCheck": true, // Skip type checking of all declaration files 11 | "esModuleInterop": true, // Disables namespace imports (import * as fs from "fs") and enables CJS/AMD/UMD style imports (import fs from "fs") 12 | "allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export 13 | "strict": true, // Enable all strict type checking options 14 | "forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file. 15 | "module": "esnext", // Specify module code generation 16 | "moduleResolution": "node", // Resolve modules using Node.js style 17 | "isolatedModules": true, // Unconditionally emit imports for unresolved files 18 | "resolveJsonModule": true, // Include modules imported with .json extension 19 | "noEmit": true, // Do not emit output (meaning do not compile code, only perform type checking) 20 | "jsx": "react", // Support JSX in .tsx files 21 | "sourceMap": true, // Generate corrresponding .map file 22 | "declaration": true, // Generate corresponding .d.ts file 23 | "noUnusedLocals": true, // Report errors on unused locals 24 | "noUnusedParameters": true, // Report errors on unused parameters 25 | "incremental": true, // Enable incremental compilation by reading/writing information from prior compilations to a file on disk 26 | "noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statement 27 | "types": ["vite/client"], 28 | }, 29 | "include": [ 30 | "src/**/*" // *** The files TypeScript should type check *** 31 | ], 32 | "exclude": ["node_modules", "build"] // *** The files to not type check *** 33 | } -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/NotSoSimpleEcommerce.InvoiceGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | Linux 8 | b6a15a86-76d9-4b65-ba74-b11a88a7c784 9 | ..\..\.. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | .dockerignore 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/Tasks/InvoiceProcessor.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using NotSoSimpleEcommerce.SesHandler.Models; 3 | using NotSoSimpleEcommerce.Shared.Consts; 4 | using NotSoSimpleEcommerce.Shared.Events; 5 | using NotSoSimpleEcommerce.SqsHandler.Abstractions; 6 | using NotSoSimpleEcommerce.SqsHandler.Models; 7 | 8 | namespace NotSoSimpleEcommerce.InvoiceGenerator.Tasks; 9 | 10 | public class InvoiceProcessor : IMessageProcessor 11 | { 12 | private readonly IMessageSender _messageEmailQueue; 13 | private readonly IConfiguration _configuration; 14 | 15 | public InvoiceProcessor(IMessageSender messageEmailQueue, IConfiguration configuration) 16 | { 17 | _messageEmailQueue = messageEmailQueue; 18 | _configuration = configuration; 19 | } 20 | 21 | public async Task ProcessMessageAsync(AwsQueueMessageParams awsQueueMessage, CancellationToken cancellationToken) 22 | { 23 | Console.WriteLine($"Invoice received: {awsQueueMessage}"); 24 | var sqsEvent = JsonConvert.DeserializeObject(awsQueueMessage.Body); 25 | if (sqsEvent is null) 26 | throw new ArgumentNullException(nameof(sqsEvent)); 27 | 28 | var orderConfirmedEvent = JsonConvert.DeserializeObject(sqsEvent.Message.ToString()); 29 | if (orderConfirmedEvent is null) 30 | throw new ArgumentNullException(nameof(sqsEvent)); 31 | 32 | var emailParams = new EmailParams 33 | ( 34 | _configuration.GetValue("Notificator:EmailConfiguration:From")!, 35 | new List () {_configuration.GetValue("Notificator:EmailConfiguration:To")!}, 36 | Email.Subjects.OrderCreated, 37 | _configuration.GetValue("Notificator:EmailConfiguration:SesTemplate")!, 38 | JsonConvert.SerializeObject(new { OrderId = orderConfirmedEvent.Id, InvoiceNumber=new Random().Next(1000,2000) }) 39 | ); 40 | 41 | await _messageEmailQueue.EnqueueAsync(emailParams, cancellationToken); 42 | Console.WriteLine($"Invoice Generated for orderId {orderConfirmedEvent.Id}!"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/Modules/AwsModule.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SQS; 2 | using Autofac; 3 | using Autofac.Core; 4 | using Microsoft.Extensions.Options; 5 | using NotSoSimpleEcommerce.SqsHandler.Abstractions; 6 | using NotSoSimpleEcommerce.SqsHandler.Implementations; 7 | using NotSoSimpleEcommerce.SqsHandler.Models; 8 | 9 | namespace NotSoSimpleEcommerce.InvoiceGenerator.Modules; 10 | 11 | public class AwsModule: Module 12 | { 13 | protected override void Load(ContainerBuilder builder) 14 | { 15 | builder.Register(componentContext => 16 | { 17 | var configuration = componentContext.Resolve(); 18 | if (Convert.ToBoolean(configuration["LocalStack:IsEnabled"])){ 19 | var config = new AmazonSQSConfig 20 | { 21 | AuthenticationRegion = configuration["AWS_REGION"], 22 | ServiceURL = configuration["LocalStack:ServiceURL"] 23 | }; 24 | 25 | return new AmazonSQSClient(config); 26 | } 27 | 28 | var client = configuration 29 | .GetAWSOptions() 30 | .CreateServiceClient(); 31 | 32 | return client; 33 | }) 34 | .Named(nameof(IAmazonSQS)) 35 | .SingleInstance(); 36 | 37 | builder.RegisterType() 38 | .As() 39 | .WithParameter( 40 | new ResolvedParameter( 41 | (i, _) => i.ParameterType == typeof(IAmazonSQS), 42 | (_, c) => c.ResolveNamed(nameof(IAmazonSQS))) 43 | ) 44 | .WithParameter( 45 | new ResolvedParameter( 46 | (i, _) => i.ParameterType == typeof(AwsSqsMessageSenderParams), 47 | (_, c) => c.Resolve>() 48 | .Get("AwsSqsMessageSenderParams01")) 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Order/Modules/AwsModule.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SQS; 2 | using Autofac; 3 | using Autofac.Core; 4 | using Microsoft.Extensions.Options; 5 | using NotSoSimpleEcommerce.SqsHandler.Abstractions; 6 | using NotSoSimpleEcommerce.SqsHandler.Implementations; 7 | using NotSoSimpleEcommerce.SqsHandler.Models; 8 | 9 | namespace NotSoSimpleEcommerce.Order.Modules 10 | { 11 | public class AwsModule: Module 12 | { 13 | protected override void Load(ContainerBuilder builder) 14 | { 15 | builder.Register(componentContext => 16 | { 17 | var configuration = componentContext.Resolve(); 18 | if (Convert.ToBoolean(configuration["LocalStack:IsEnabled"])){ 19 | var config = new AmazonSQSConfig 20 | { 21 | AuthenticationRegion = configuration["AWS_REGION"], 22 | ServiceURL = configuration["LocalStack:ServiceURL"] 23 | }; 24 | 25 | return new AmazonSQSClient(config); 26 | } 27 | 28 | var client = configuration 29 | .GetAWSOptions() 30 | .CreateServiceClient(); 31 | 32 | return client; 33 | }) 34 | .Named(nameof(IAmazonSQS)) 35 | .SingleInstance(); 36 | 37 | builder.RegisterType() 38 | .As() 39 | .WithParameter( 40 | new ResolvedParameter( 41 | (i, _) => i.ParameterType == typeof(IAmazonSQS), 42 | (_, c) => c.ResolveNamed(nameof(IAmazonSQS))) 43 | ) 44 | .WithParameter( 45 | new ResolvedParameter( 46 | (i, _) => i.ParameterType == typeof(AwsSqsMessageSenderParams), 47 | (_, c) => c.Resolve>() 48 | .Get("AwsSqsMessageSenderParams01")) 49 | ); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/NotSoSimpleEcommerce.Notificator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | Linux 8 | 36e18649-8df8-432a-bf61-e249e94a37bd 9 | NotSoSimpleEcommerce.Notificator 10 | ..\..\.. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | .dockerignore 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/Migrations/IdentityServerContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 5 | using NotSoSimpleEcommerce.IdentityServer.Domain.Repositories.Contexts; 6 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 7 | 8 | #nullable disable 9 | 10 | namespace NotSoSimpleEcommerce.IdentityServer.Domain.Migrations 11 | { 12 | [DbContext(typeof(IdentityServerContext))] 13 | partial class IdentityServerContextModelSnapshot : ModelSnapshot 14 | { 15 | protected override void BuildModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder 19 | .HasAnnotation("ProductVersion", "7.0.5") 20 | .HasAnnotation("Relational:MaxIdentifierLength", 63); 21 | 22 | NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); 23 | 24 | modelBuilder.Entity("NotSoSimpleEcommerce.IdentityServer.Domain.Models.UserEntity", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasColumnType("integer"); 29 | 30 | NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); 31 | 32 | b.Property("Email") 33 | .IsRequired() 34 | .HasColumnType("text"); 35 | 36 | b.Property("Password") 37 | .IsRequired() 38 | .HasColumnType("text"); 39 | 40 | b.HasKey("Id"); 41 | 42 | b.ToTable("User", (string)null); 43 | 44 | b.HasData( 45 | new 46 | { 47 | Id = 1, 48 | Email = "admin@nsse.com", 49 | Password = "yJ�w�flVG6�@ 85F�Q�S�0�C�s'P��" 50 | }); 51 | }); 52 | #pragma warning restore 612, 618 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/CommandHandlers/UpdateProductStockCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Main.Domain.Commands; 3 | using NotSoSimpleEcommerce.Main.Domain.Mappings; 4 | using NotSoSimpleEcommerce.Main.Domain.Repositories.Contracts; 5 | using NotSoSimpleEcommerce.Repositories.Contracts; 6 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 7 | using NotSoSimpleEcommerce.Shared.Models; 8 | 9 | namespace NotSoSimpleEcommerce.Main.Domain.CommandHandlers 10 | { 11 | public sealed class UpdateProductStockCommandHandler : IRequestHandler 12 | { 13 | private readonly IUpdateEntityRepository _updateEntityRepository; 14 | private readonly IStockReadRepository _readRepository; 15 | 16 | public UpdateProductStockCommandHandler 17 | ( 18 | IUpdateEntityRepository updateEntityRepository, 19 | IStockReadRepository readRepository 20 | ) 21 | { 22 | _updateEntityRepository = updateEntityRepository ?? throw new ArgumentNullException(nameof(updateEntityRepository)); 23 | _readRepository = readRepository ?? throw new ArgumentNullException(nameof(readRepository)); 24 | } 25 | 26 | public async Task Handle(UpdateProductStockCommand request, CancellationToken cancellationToken) 27 | { 28 | var oldStock = await _readRepository.GetByProductIdAsync(request.ProductId, cancellationToken); 29 | if (oldStock is null) 30 | throw new KeyNotFoundException("The specified product doesn't have stock."); 31 | 32 | var stockToUpdate = request 33 | .WithId(oldStock.Id) 34 | .MapToEntity(); 35 | 36 | await _updateEntityRepository.ExecuteAsync(stockToUpdate, cancellationToken); 37 | 38 | var updatedStock = await _readRepository.GetByProductIdAsync(request.ProductId, cancellationToken); 39 | if (updatedStock == null) 40 | throw new ArgumentNullException(); 41 | 42 | return updatedStock.MapToResponse(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/frontend/src/pages/report/Index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState } from 'react'; 2 | import { 3 | MaterialReactTable, 4 | type MRT_ColumnDef, 5 | } from 'material-react-table'; 6 | import { ReportEntity } from '../../types/Stock.type'; 7 | import { useQueryClient } from '@tanstack/react-query'; 8 | import React, { useEffect, useMemo, useState } from 'react'; 9 | import { getReports, useReport } from '../../hooks/useReport'; 10 | 11 | const Report = () => { 12 | const {data: reports=[]} = useReport(); 13 | const [tableData, setTableData] = useState(() => reports); 14 | const [changed] = useState(false); 15 | 16 | const queryClient = useQueryClient(); 17 | 18 | useEffect(() => { 19 | queryClient.invalidateQueries({ queryKey: ['product'] }) 20 | getReports() 21 | .then((response) => { 22 | setTableData(response ?? []); 23 | }); 24 | }, [changed]); 25 | const columns = useMemo[]>( 26 | () => [ 27 | { 28 | accessorKey: 'productName', 29 | header: 'Produto', 30 | enableColumnOrdering: false, 31 | enableSorting: false, 32 | size: 80, 33 | }, 34 | { 35 | accessorKey: 'totalOrders', 36 | header: 'Qtde Pedidos', 37 | enableColumnOrdering: false, 38 | enableSorting: true, 39 | size: 80, 40 | }, 41 | { 42 | accessorKey: 'totalOrdered', 43 | header: 'Qtde Produtos Pedidos', 44 | enableColumnOrdering: false, 45 | enableSorting: true, 46 | size: 80, 47 | }, 48 | { 49 | accessorKey: 'totalSold', 50 | header: 'Total', 51 | enableColumnOrdering: false, 52 | enableSorting: true, 53 | size: 80, 54 | } 55 | ], 56 | [], 57 | ); 58 | 59 | return ( 60 | <> 61 | 74 | 75 | ); 76 | }; 77 | 78 | export default Report; -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/CommandHandlers/RegisterProductStockCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Main.Domain.Commands; 3 | using NotSoSimpleEcommerce.Main.Domain.Mappings; 4 | using NotSoSimpleEcommerce.Main.Domain.Repositories.Contracts; 5 | using NotSoSimpleEcommerce.Repositories.Contracts; 6 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 7 | using NotSoSimpleEcommerce.Shared.Models; 8 | 9 | namespace NotSoSimpleEcommerce.Main.Domain.CommandHandlers 10 | { 11 | public sealed class RegisterProductStockCommandHandler : IRequestHandler 12 | { 13 | private readonly ICreateEntityRepository _createEntityRepository; 14 | private readonly IStockReadRepository _stockReadRepository; 15 | 16 | public RegisterProductStockCommandHandler 17 | ( 18 | ICreateEntityRepository createEntityRepository, 19 | IStockReadRepository stockReadRepository 20 | ) 21 | { 22 | _createEntityRepository = createEntityRepository ?? throw new ArgumentNullException(nameof(createEntityRepository)); 23 | _stockReadRepository = stockReadRepository ?? throw new ArgumentNullException(nameof(stockReadRepository)); 24 | } 25 | 26 | public async Task Handle(RegisterProductStockCommand request, CancellationToken cancellationToken) 27 | { 28 | var oldStock = await _stockReadRepository.GetByProductIdAsync(request.ProductId, cancellationToken); 29 | if (oldStock is not null) 30 | throw new ArgumentException("The specified product already has a stock registered, please use" + 31 | "the UPDATE method."); 32 | 33 | var stockToCreate = request.MapToEntity(); 34 | await _createEntityRepository.ExecuteAsync(stockToCreate, cancellationToken); 35 | 36 | var createdStock = await _stockReadRepository.GetByProductIdAsync(request.ProductId, cancellationToken); 37 | if (createdStock == null) 38 | throw new ArgumentNullException(); 39 | 40 | return createdStock.MapToResponse(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/Controllers/AuthController.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using System.Security.Claims; 3 | using System.Text; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.IdentityModel.Tokens; 6 | using NotSoSimpleEcommerce.IdentityServer.Domain.Services.Contracts; 7 | using NotSoSimpleEcommerce.Shared.InOut.Requests; 8 | 9 | namespace NotSoSimpleEcommerce.IdentityServer.Controllers 10 | { 11 | [ApiController] 12 | [Route("api/auth")] 13 | public class AuthController: ControllerBase 14 | { 15 | [HttpPost] 16 | public async Task AuthenticateAsync 17 | ( 18 | [FromServices] IConfiguration configuration, 19 | [FromServices] IUserService userService, 20 | [FromBody] AuthRequest authRequest 21 | ) 22 | { 23 | var isAuthenticated = await userService.CheckPasswordAsync(authRequest); 24 | if (!isAuthenticated) 25 | return Unauthorized(); 26 | 27 | var tokenHandler = new JwtSecurityTokenHandler(); 28 | var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetValue("Identity:Key")!)); 29 | var claims = new List 30 | { 31 | new Claim(JwtRegisteredClaimNames.Email, authRequest.Email) 32 | }; 33 | 34 | var tokenDescriptor = new SecurityTokenDescriptor 35 | { 36 | Subject = new ClaimsIdentity(claims), 37 | Expires = DateTime.UtcNow.AddMinutes(configuration.GetValue("Identity:TokenLifetime")), 38 | Issuer = configuration.GetValue("Identity:Issuer"), 39 | Audience = configuration.GetValue("Identity:Audience"), 40 | SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256) 41 | }; 42 | 43 | var token = tokenHandler.CreateToken(tokenDescriptor); 44 | var jwt = tokenHandler.WriteToken(token); 45 | 46 | if (!authRequest.OnlyTokenBody) 47 | return Ok(new 48 | { 49 | token = jwt, 50 | expiration = token.ValidTo 51 | }); 52 | 53 | return Ok(jwt); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.IdentityServer.Domain/Migrations/20230720174257_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Migrations; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | using NotSoSimpleEcommerce.IdentityServer.Domain.Repositories.Contexts; 7 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 8 | 9 | #nullable disable 10 | 11 | namespace NotSoSimpleEcommerce.IdentityServer.Domain.Migrations 12 | { 13 | [DbContext(typeof(IdentityServerContext))] 14 | [Migration("20230720174257_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "7.0.5") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 63); 24 | 25 | NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); 26 | 27 | modelBuilder.Entity("NotSoSimpleEcommerce.IdentityServer.Domain.Models.UserEntity", b => 28 | { 29 | b.Property("Id") 30 | .ValueGeneratedOnAdd() 31 | .HasColumnType("integer"); 32 | 33 | NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); 34 | 35 | b.Property("Email") 36 | .IsRequired() 37 | .HasColumnType("text"); 38 | 39 | b.Property("Password") 40 | .IsRequired() 41 | .HasColumnType("text"); 42 | 43 | b.HasKey("Id"); 44 | 45 | b.ToTable("User", (string)null); 46 | 47 | b.HasData( 48 | new 49 | { 50 | Id = 1, 51 | Email = "admin@nsse.com", 52 | Password = "yJ�w�flVG6�@ 85F�Q�S�0�C�s'P��" 53 | }); 54 | }); 55 | #pragma warning restore 612, 618 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/NotSoSimpleEcommerce.IdentityServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | Linux 8 | e71bb9c8-7ded-42c6-bd2d-b45806e4d5da 9 | ..\..\.. 10 | 11 | 12 | 13 | 14 | .dockerignore 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | all 33 | runtime; build; native; contentfiles; analyzers; buildtransitive 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Main/NotSoSimpleEcommerce.Main.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 409ed138-fd8f-44d6-a2c2-8ba8f2c9faf4 8 | Linux 9 | ..\..\.. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.Notificator/Program.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Extensions.DependencyInjection; 3 | using HealthChecks.UI.Client; 4 | using Microsoft.AspNetCore.Diagnostics.HealthChecks; 5 | using NotSoSimpleEcommerce.Notificator.Modules; 6 | using NotSoSimpleEcommerce.SqsHandler.Models; 7 | using Serilog; 8 | using Serilog.Events; 9 | using Serilog.Exceptions; 10 | 11 | Log.Logger = new LoggerConfiguration() 12 | .WriteTo.Console(LogEventLevel.Information) 13 | .Enrich.WithExceptionDetails() 14 | .Enrich.WithMachineName() 15 | .Enrich.WithEnvironmentName() 16 | .CreateLogger(); 17 | 18 | try 19 | { 20 | Log.Information("Starting Notificator"); 21 | var builder = WebApplication.CreateBuilder(args); 22 | builder.Host.UseSerilog(); 23 | builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); 24 | builder.Host.ConfigureContainer(applicationBuilder => 25 | { 26 | applicationBuilder.RegisterModule(); 27 | applicationBuilder.RegisterModule(); 28 | }); 29 | 30 | builder.Services.Configure( 31 | "AwsSqsQueueMonitorParams01", 32 | builder.Configuration.GetSection("Notificator:AwsSqsQueueMonitorParams01") 33 | ); 34 | 35 | builder.Services.AddMemoryCache(); 36 | builder.Services.AddHealthChecks(); 37 | builder.Services.AddControllers(); 38 | builder.Services.AddEndpointsApiExplorer(); 39 | builder.Services.AddSwaggerGen(); 40 | var app = builder.Build(); 41 | 42 | app.Map("/notificator", applicationBuilder => 43 | { 44 | applicationBuilder.UseSwagger(); 45 | applicationBuilder.UseSwaggerUI(); 46 | applicationBuilder.UseRouting(); 47 | 48 | applicationBuilder.UseEndpoints(endpoints => 49 | { 50 | endpoints.MapControllers(); 51 | endpoints.MapHealthChecks("/health", 52 | new HealthCheckOptions 53 | { 54 | Predicate = _ => true, 55 | ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse 56 | }); 57 | }); 58 | }); 59 | 60 | app.Run(); 61 | } 62 | catch (Exception exception) 63 | { 64 | Log.Fatal(exception, "Application terminated unexpectedly"); 65 | } 66 | finally 67 | { 68 | Log.CloseAndFlush(); 69 | } 70 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | nsse-network: 3 | external: true 4 | 5 | services: 6 | 7 | nsse.site.internal: 8 | container_name: nsse.site.internal 9 | image: nsse/site 10 | ports: 11 | - 3000:3000 12 | build: 13 | context: src/frontend 14 | dockerfile: Dockerfile 15 | networks: 16 | - nsse-network 17 | 18 | nsse.main.internal: 19 | container_name: nsse.main.internal 20 | image: nsse/main 21 | ports: 22 | - 5000:443 23 | build: 24 | context: . 25 | dockerfile: src/services/NotSoSimpleEcommerce.Main/Dockerfile 26 | env_file: 27 | - ./.env 28 | volumes: 29 | - ${CERTIFICATES_HOST_PATH}:${CERTIFICATES_CONTAINER_PATH} 30 | - ${AWS_CREDENTIALS_HOST_PATH}:${AWS_CREDENTIALS_CONTAINER_PATH} 31 | - ${MONGO_CERTIFICATE_HOST_PATH}:${MONGO_CERTIFICATE_CONTAINER_PATH} 32 | networks: 33 | - nsse-network 34 | 35 | nsse.order.internal: 36 | container_name: nsse.order.internal 37 | image: nsse/order 38 | ports: 39 | - 5001:443 40 | build: 41 | context: . 42 | dockerfile: src/services/NotSoSimpleEcommerce.Order/Dockerfile 43 | env_file: 44 | - ./.env 45 | volumes: 46 | - ${CERTIFICATES_HOST_PATH}:${CERTIFICATES_CONTAINER_PATH} 47 | - ${AWS_CREDENTIALS_HOST_PATH}:${AWS_CREDENTIALS_CONTAINER_PATH} 48 | networks: 49 | - nsse-network 50 | 51 | nsse.identity.internal: 52 | container_name: nsse.identity.internal 53 | image: nsse/identity 54 | ports: 55 | - 5002:443 56 | build: 57 | context: . 58 | dockerfile: src/services/NotSoSimpleEcommerce.IdentityServer/Dockerfile 59 | env_file: 60 | - ./.env 61 | volumes: 62 | - ${CERTIFICATES_HOST_PATH}:${CERTIFICATES_CONTAINER_PATH} 63 | - ${AWS_CREDENTIALS_HOST_PATH}:${AWS_CREDENTIALS_CONTAINER_PATH} 64 | networks: 65 | - nsse-network 66 | 67 | nsse.health-checker.internal: 68 | container_name: nsse.health-checker.internal 69 | image: nsse/health-checker 70 | ports: 71 | - 5003:443 72 | build: 73 | context: . 74 | dockerfile: src/services/NotSoSimpleEcommerce.HealthChecker/Dockerfile 75 | env_file: 76 | - ./.env 77 | volumes: 78 | - ${CERTIFICATES_HOST_PATH}:${CERTIFICATES_CONTAINER_PATH} 79 | - ${AWS_CREDENTIALS_HOST_PATH}:${AWS_CREDENTIALS_CONTAINER_PATH} 80 | networks: 81 | - nsse-network -------------------------------------------------------------------------------- /src/infrastructure/NotSoSimpleEcommerce.SesHandler/Implementations/AwsSesEmailSender.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SimpleEmail; 2 | using Amazon.SimpleEmail.Model; 3 | using Microsoft.Extensions.Logging; 4 | using NotSoSimpleEcommerce.SesHandler.Abstractions; 5 | using NotSoSimpleEcommerce.SesHandler.Models; 6 | 7 | namespace NotSoSimpleEcommerce.SesHandler.Implementations; 8 | public sealed class AwsSesEmailSender : IEmailSender 9 | { 10 | private readonly ILogger _logger; 11 | private readonly IAmazonSimpleEmailService _sesClient; 12 | 13 | public AwsSesEmailSender 14 | ( 15 | ILogger logger, 16 | IAmazonSimpleEmailService sesClient 17 | ) 18 | { 19 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 20 | _sesClient = sesClient ?? throw new ArgumentNullException(nameof(sesClient)); 21 | } 22 | 23 | public async Task SendAsync 24 | ( 25 | EmailParams @params, 26 | CancellationToken cancellationToken 27 | ) 28 | { 29 | var sendRequest = new SendTemplatedEmailRequest 30 | { 31 | Source = @params.FromAddress, 32 | Destination = new Destination 33 | { 34 | ToAddresses = @params.ToAddresses.ToList(), 35 | CcAddresses = @params.CcAddresses?.ToList() ?? new (), 36 | BccAddresses = @params.BccAddresses?.ToList()?? new () 37 | }, 38 | Template = @params.TemplateName, 39 | TemplateData = @params.TemplateData 40 | }; 41 | 42 | try 43 | { 44 | var response = await _sesClient.SendTemplatedEmailAsync(sendRequest, cancellationToken); 45 | 46 | _logger.LogInformation 47 | ( 48 | "Email sent: {StatusCode}, Response {MailMoreInfo}", 49 | response.HttpStatusCode, 50 | response.ToString() 51 | ); 52 | } 53 | catch (Exception exception) 54 | { 55 | _logger.LogError 56 | ( 57 | exception, 58 | "An error happened while sending an email: {MailMoreInfo}", 59 | exception.ToString() 60 | ); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/frontend/src/components/navbar/Index.jsx: -------------------------------------------------------------------------------- 1 | import AppBar from '@mui/material/AppBar'; 2 | import Box from '@mui/material/Box'; 3 | import Toolbar from '@mui/material/Toolbar'; 4 | import Typography from '@mui/material/Typography'; 5 | import Button from '@mui/material/Button'; 6 | import { Component } from 'react' 7 | import { IconButton, Stack } from '@mui/material'; 8 | import { Link } from 'react-router-dom'; 9 | import CategoryOutlinedIcon from '@mui/icons-material/CategoryOutlined'; 10 | import AddShoppingCartOutlinedIcon from '@mui/icons-material/AddShoppingCartOutlined'; 11 | import CreditCardOutlinedIcon from '@mui/icons-material/CreditCardOutlined'; 12 | import InventoryOutlinedIcon from '@mui/icons-material/InventoryOutlined'; 13 | import LoginOutlinedIcon from '@mui/icons-material/LoginOutlined'; 14 | 15 | export class NavBar extends Component { 16 | render() { 17 | return ( 18 | 19 | 20 | 21 | 22 | Not So Simple Ecommerce 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Order.Domain/CommandHandlers/UpdateOrderCommanHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using NotSoSimpleEcommerce.Order.Domain.Commands; 3 | using NotSoSimpleEcommerce.Order.Domain.Mappings; 4 | using NotSoSimpleEcommerce.Order.Domain.Repositories.Contracts; 5 | using NotSoSimpleEcommerce.Repositories.Contracts; 6 | using NotSoSimpleEcommerce.Shared.HttpHandlers.Contracts; 7 | using NotSoSimpleEcommerce.Shared.InOut.Responses; 8 | using NotSoSimpleEcommerce.Shared.Models; 9 | 10 | namespace NotSoSimpleEcommerce.Order.Domain.CommandHandlers; 11 | 12 | public sealed class UpdateOrderCommanHandler: IRequestHandler 13 | { 14 | private readonly IUpdateEntityRepository _updateEntityRepository; 15 | private readonly IMainApi _mainApiClient; 16 | private readonly IOrderReadRepository _readRepository; 17 | 18 | public UpdateOrderCommanHandler 19 | ( 20 | IUpdateEntityRepository updateEntityRepository, 21 | IMainApi mainApiClient, 22 | IOrderReadRepository readRepository 23 | ) 24 | { 25 | _updateEntityRepository = updateEntityRepository ?? throw new ArgumentNullException(nameof(updateEntityRepository)); 26 | _mainApiClient = mainApiClient?? throw new ArgumentNullException(nameof(mainApiClient)); 27 | _readRepository = readRepository ?? throw new ArgumentNullException(nameof(readRepository)); 28 | } 29 | 30 | public async Task Handle(UpdateOrderCommand request, CancellationToken cancellationToken) 31 | { 32 | var requestedOrderEntity = request.MapToEntity(); 33 | var stockApiResponse = await _mainApiClient.GetStockByProductIdAsync(request.ProductId); 34 | if (!stockApiResponse.IsSuccessStatusCode) 35 | throw new KeyNotFoundException("Sorry, something went wrong while consulting the product stock."); 36 | 37 | var stockResponse = stockApiResponse.Content; 38 | if (stockResponse.Quantity < request.Quantity) 39 | throw new Exception("There is not enough stock for the selected Product."); 40 | 41 | var order = await _readRepository.GetByProductIdAsync(request.ProductId, cancellationToken); 42 | if (order == null) 43 | throw new ArgumentNullException(); 44 | 45 | order.Quantity = requestedOrderEntity.Quantity; 46 | 47 | await _updateEntityRepository.ExecuteAsync(order, cancellationToken); 48 | return order.MapToResponse(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/workers/NotSoSimpleEcommerce.InvoiceGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Extensions.DependencyInjection; 3 | using HealthChecks.UI.Client; 4 | using Microsoft.AspNetCore.Diagnostics.HealthChecks; 5 | using NotSoSimpleEcommerce.InvoiceGenerator.Modules; 6 | using NotSoSimpleEcommerce.SqsHandler.Models; 7 | using Serilog; 8 | using Serilog.Events; 9 | using Serilog.Exceptions; 10 | 11 | Log.Logger = new LoggerConfiguration() 12 | .WriteTo.Console(LogEventLevel.Information) 13 | .Enrich.WithExceptionDetails() 14 | .Enrich.WithMachineName() 15 | .Enrich.WithEnvironmentName() 16 | .CreateLogger(); 17 | 18 | try 19 | { 20 | Log.Information("Starting Invoice Generator Microservice"); 21 | var builder = WebApplication.CreateBuilder(args); 22 | builder.Host.UseSerilog(); 23 | builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); 24 | builder.Host.ConfigureContainer(applicationBuilder => 25 | { 26 | applicationBuilder.RegisterModule(); 27 | applicationBuilder.RegisterModule(); 28 | }); 29 | 30 | builder.Services.Configure( 31 | "AwsSqsQueueMonitorParams01", 32 | builder.Configuration.GetSection("InvoiceGenerator:AwsSqsQueueMonitorParams01") 33 | ); 34 | 35 | builder.Services.Configure( 36 | "AwsSqsMessageSenderParams01", 37 | builder.Configuration.GetSection("InvoiceGenerator:AwsSqsMessageSenderParams01") 38 | ); 39 | 40 | 41 | builder.Services.AddMemoryCache(); 42 | builder.Services.AddHealthChecks(); 43 | builder.Services.AddControllers(); 44 | builder.Services.AddEndpointsApiExplorer(); 45 | builder.Services.AddSwaggerGen(); 46 | var app = builder.Build(); 47 | 48 | app.Map("/invoice", applicationBuilder => 49 | { 50 | applicationBuilder.UseSwagger(); 51 | applicationBuilder.UseSwaggerUI(); 52 | applicationBuilder.UseRouting(); 53 | 54 | applicationBuilder.UseEndpoints(endpoints => 55 | { 56 | endpoints.MapControllers(); 57 | endpoints.MapHealthChecks("/health", 58 | new HealthCheckOptions 59 | { 60 | Predicate = _ => true, 61 | ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse 62 | }); 63 | }); 64 | }); 65 | 66 | app.Run(); 67 | } 68 | catch (Exception exception) 69 | { 70 | Log.Fatal(exception, "Application terminated unexpectedly"); 71 | } 72 | finally 73 | { 74 | Log.CloseAndFlush(); 75 | } 76 | -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.IdentityServer/Program.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Extensions.DependencyInjection; 3 | using HealthChecks.UI.Client; 4 | using Microsoft.AspNetCore.Diagnostics.HealthChecks; 5 | using NotSoSimpleEcommerce.IdentityServer.Middlewares; 6 | using NotSoSimpleEcommerce.IdentityServer.Modules; 7 | using Serilog; 8 | using Serilog.Events; 9 | using Serilog.Exceptions; 10 | 11 | Log.Logger = new LoggerConfiguration() 12 | .WriteTo.Console(LogEventLevel.Information) 13 | .Enrich.WithExceptionDetails() 14 | .Enrich.WithMachineName() 15 | .Enrich.WithEnvironmentName() 16 | .CreateLogger(); 17 | try 18 | { 19 | Log.Information("Starting Identity Server Microservice"); 20 | var builder = WebApplication.CreateBuilder(args); 21 | builder.Host.UseSerilog(); 22 | builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); 23 | builder.Host.ConfigureContainer(applicationBuilder => 24 | { 25 | applicationBuilder.RegisterModule(); 26 | applicationBuilder.RegisterModule(new InfrastructureModule(builder.Configuration)); 27 | 28 | applicationBuilder 29 | .RegisterType() 30 | .SingleInstance(); 31 | }); 32 | builder.Services.AddHealthChecks(); 33 | builder.Services.AddControllers(); 34 | builder.Services.AddEndpointsApiExplorer(); 35 | builder.Services.AddSwaggerGen(); 36 | 37 | var app = builder.Build(); 38 | app.Map("/identity", applicationBuilder => 39 | { 40 | applicationBuilder.UseSwagger(); 41 | applicationBuilder.UseSwaggerUI(); 42 | applicationBuilder.UseRouting(); 43 | applicationBuilder.UseMiddleware(); 44 | applicationBuilder.UseAuthentication(); 45 | applicationBuilder.UseAuthorization(); 46 | applicationBuilder.UseEndpoints(endpoints => 47 | { 48 | endpoints.MapControllers(); 49 | endpoints.MapHealthChecks("/health", 50 | new HealthCheckOptions 51 | { 52 | Predicate = _ => true, 53 | ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse 54 | }); 55 | }); 56 | }); 57 | 58 | app.Run(); 59 | } 60 | catch (HostAbortedException exception) 61 | { 62 | Log.Warning(exception, "Executing migrations? All good."); 63 | } 64 | catch (Exception exception) 65 | { 66 | Log.Fatal(exception, "Application terminated unexpectedly"); 67 | } 68 | finally 69 | { 70 | Log.CloseAndFlush(); 71 | } 72 | -------------------------------------------------------------------------------- /src/domains/NotSoSimpleEcommerce.Main.Domain/Migrations/20250108182910_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 3 | 4 | #nullable disable 5 | 6 | namespace NotSoSimpleEcommerce.Main.Domain.Migrations 7 | { 8 | /// 9 | public partial class InitialCreate : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.CreateTable( 15 | name: "Product", 16 | columns: table => new 17 | { 18 | Id = table.Column(type: "integer", nullable: false) 19 | .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), 20 | Name = table.Column(type: "text", nullable: false), 21 | Price = table.Column(type: "numeric", nullable: false) 22 | }, 23 | constraints: table => 24 | { 25 | table.PrimaryKey("PK_Product", x => x.Id); 26 | }); 27 | 28 | migrationBuilder.CreateTable( 29 | name: "Stock", 30 | columns: table => new 31 | { 32 | Id = table.Column(type: "integer", nullable: false) 33 | .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), 34 | ProductId = table.Column(type: "integer", nullable: false), 35 | Quantity = table.Column(type: "integer", nullable: false) 36 | }, 37 | constraints: table => 38 | { 39 | table.PrimaryKey("PK_Stock", x => x.Id); 40 | table.ForeignKey( 41 | name: "FK_Stock_Product_ProductId", 42 | column: x => x.ProductId, 43 | principalTable: "Product", 44 | principalColumn: "Id", 45 | onDelete: ReferentialAction.Cascade); 46 | }); 47 | 48 | migrationBuilder.CreateIndex( 49 | name: "IX_Stock_ProductId", 50 | table: "Stock", 51 | column: "ProductId", 52 | unique: true); 53 | } 54 | 55 | /// 56 | protected override void Down(MigrationBuilder migrationBuilder) 57 | { 58 | migrationBuilder.DropTable( 59 | name: "Stock"); 60 | 61 | migrationBuilder.DropTable( 62 | name: "Product"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | events{ 2 | worker_connections 512; 3 | } 4 | 5 | http { 6 | server { 7 | listen *:44300 ssl; 8 | server_name devopsnanuvem.internal; 9 | 10 | ssl_certificate /etc/nginx/certificates/nginx-certificate.crt; 11 | ssl_certificate_key /etc/nginx/certificates/signing-request.key; 12 | 13 | location ^~/main/{ 14 | resolver 127.0.0.11; 15 | set $upstream_host nsse.main.internal; 16 | proxy_pass https://$upstream_host; 17 | 18 | proxy_set_header Host $host; 19 | proxy_set_header X-Real-IP $proxy_protocol_addr; 20 | proxy_set_header X-Forwarded-For $proxy_protocol_addr; 21 | } 22 | 23 | location ^~/order/{ 24 | resolver 127.0.0.11; 25 | set $upstream_host nsse.order.internal; 26 | proxy_pass https://$upstream_host; 27 | 28 | proxy_set_header Host $host; 29 | proxy_set_header X-Real-IP $proxy_protocol_addr; 30 | proxy_set_header X-Forwarded-For $proxy_protocol_addr; 31 | } 32 | 33 | location ^~/identity/{ 34 | resolver 127.0.0.11; 35 | set $upstream_host nsse.identity.internal; 36 | proxy_pass https://$upstream_host; 37 | 38 | proxy_set_header Host $host; 39 | proxy_set_header X-Real-IP $proxy_protocol_addr; 40 | proxy_set_header X-Forwarded-For $proxy_protocol_addr; 41 | } 42 | 43 | location ^~/healthchecks/{ 44 | resolver 127.0.0.11; 45 | set $upstream_host nsse.health-checker.internal; 46 | proxy_pass https://$upstream_host; 47 | 48 | proxy_set_header Host $host; 49 | proxy_set_header X-Real-IP $proxy_protocol_addr; 50 | proxy_set_header X-Forwarded-For $proxy_protocol_addr; 51 | } 52 | 53 | location ^~/invoice/{ 54 | resolver 127.0.0.11; 55 | set $upstream_host nsse.invoice.internal; 56 | proxy_pass https://$upstream_host; 57 | 58 | proxy_set_header Host $host; 59 | proxy_set_header X-Real-IP $proxy_protocol_addr; 60 | proxy_set_header X-Forwarded-For $proxy_protocol_addr; 61 | } 62 | 63 | location ^~/notificator/{ 64 | resolver 127.0.0.11; 65 | set $upstream_host nsse.notificator.internal; 66 | proxy_pass https://$upstream_host; 67 | 68 | proxy_set_header Host $host; 69 | proxy_set_header X-Real-IP $proxy_protocol_addr; 70 | proxy_set_header X-Forwarded-For $proxy_protocol_addr; 71 | } 72 | 73 | location /{ 74 | resolver 127.0.0.11; 75 | set $upstream_host nsse.site.internal; 76 | proxy_pass http://$upstream_host:3000; 77 | 78 | proxy_set_header Host $host; 79 | proxy_set_header X-Real-IP $proxy_protocol_addr; 80 | proxy_set_header X-Forwarded-For $proxy_protocol_addr; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/services/NotSoSimpleEcommerce.Order/NotSoSimpleEcommerce.Order.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | Linux 8 | 4dc356eb-a7a9-4a4a-ae2a-288980cb3bfc 9 | ..\..\.. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | all 33 | runtime; build; native; contentfiles; analyzers; buildtransitive 34 | 35 | 36 | 37 | 38 | 39 | 40 | .dockerignore 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | --------------------------------------------------------------------------------