├── 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 | }>Produto
27 |
28 |
29 | }>Estoque
30 |
31 |
32 | }>Ordem
33 |
34 |
35 | }>Relatório
36 |
37 |
38 | }>Login
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