├── .env ├── .gitignore ├── MyMicroservicesApp.png ├── MyMicroservicesApplication.sln ├── MyMicroservicesApplication.sln.DotSettings.user ├── README.md ├── dockerstack-application ├── Clients │ └── Web │ │ └── spa │ │ ├── .angular-cli.json │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── e2e │ │ ├── app.e2e-spec.ts │ │ ├── app.po.ts │ │ └── tsconfig.e2e.json │ │ ├── karma.conf.js │ │ ├── nginx │ │ └── default.conf │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── protractor.conf.js │ │ ├── proxy.config.docker.json │ │ ├── proxy.config.local.json │ │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── app.routes.ts │ │ │ ├── basket │ │ │ │ ├── basket-data.ts │ │ │ │ ├── basket.component.html │ │ │ │ ├── basket.component.ts │ │ │ │ ├── basket.module.ts │ │ │ │ └── basket.service.ts │ │ │ ├── core │ │ │ │ ├── core.module.ts │ │ │ │ ├── security │ │ │ │ │ ├── auth.guard.service.ts │ │ │ │ │ ├── authentication.service.ts │ │ │ │ │ └── jwt-integration-http.interceptor.ts │ │ │ │ ├── shared │ │ │ │ │ └── product-data.ts │ │ │ │ └── utils.service.ts │ │ │ ├── login │ │ │ │ ├── login.component.css │ │ │ │ ├── login.component.html │ │ │ │ ├── login.component.ts │ │ │ │ ├── login.module.ts │ │ │ │ └── user-data.ts │ │ │ ├── orders │ │ │ │ ├── order-command.ts │ │ │ │ ├── order-confirmation.component.html │ │ │ │ ├── order-confirmation.component.ts │ │ │ │ ├── orders.component.html │ │ │ │ ├── orders.component.ts │ │ │ │ ├── orders.module.ts │ │ │ │ └── orders.service.ts │ │ │ └── shop │ │ │ │ ├── product-shop-data.ts │ │ │ │ ├── shop.component.css │ │ │ │ ├── shop.component.html │ │ │ │ ├── shop.component.ts │ │ │ │ ├── shop.module.ts │ │ │ │ └── shop.service.ts │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ ├── css │ │ │ │ ├── bootstrap.css │ │ │ │ ├── font-awesome.css │ │ │ │ └── images │ │ │ │ │ ├── meteorshower2.jpg │ │ │ │ │ └── pattern.jpg │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ └── images │ │ │ │ └── shopping.png │ │ ├── environments │ │ │ ├── environment.deploy.ts │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ ├── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ └── typings.d.ts │ │ ├── tsconfig.json │ │ └── tslint.json ├── EcosystemBlocks │ ├── EventBus │ │ ├── EventBus │ │ │ ├── .gitignore │ │ │ ├── Abstractions │ │ │ │ ├── IEventBus.cs │ │ │ │ ├── IIntegrationEventHandler.cs │ │ │ │ └── ISubscriptionsManager.cs │ │ │ ├── EventBus.csproj │ │ │ ├── Events │ │ │ │ └── IntegrationEvent.cs │ │ │ ├── IntegrationEventReceivedNotificationDto.cs │ │ │ ├── ResilientTransaction.cs │ │ │ ├── Responses │ │ │ │ ├── EbPublishResponse.cs │ │ │ │ └── EventBusResponse.cs │ │ │ ├── SnsMessage.cs │ │ │ └── SuscriptionManager.cs │ │ └── EventBusAwsSns │ │ │ ├── .gitignore │ │ │ ├── EventBus.cs │ │ │ ├── EventBusAwsSns.csproj │ │ │ └── Shared │ │ │ └── IntegrationEvents │ │ │ └── OrderStartedIntegrationEvent.cs │ ├── IntegrationEventsContext │ │ └── IntegrationEventsContext │ │ │ ├── .gitignore │ │ │ ├── IIntegrationEventsRespository.cs │ │ │ ├── IntegrationEventsContext.csproj │ │ │ ├── IntegrationEventsRespository.cs │ │ │ └── Models │ │ │ ├── IntegrationEvent.cs │ │ │ └── IntegrationEventInstance.cs │ └── WebHostCustomization │ │ └── WebHost.Customization │ │ ├── .gitignore │ │ ├── WebHost.Customization.csproj │ │ └── WebHostExtensions.cs ├── Services │ ├── AuthService │ │ ├── .gitignore │ │ ├── AuthService.csproj │ │ ├── AuthService.csproj.user │ │ ├── DbModels │ │ │ ├── AppUser.cs │ │ │ ├── IdentityContext.cs │ │ │ └── IdentityContextSeed.cs │ │ ├── Dockerfile │ │ ├── Dockerfile.debug │ │ ├── Filters │ │ │ ├── RequestLoggingMiddleware.cs │ │ │ └── ResponseLoggingMiddleware.cs │ │ ├── Migrations │ │ │ ├── 20180328114302_InitialCreate.Designer.cs │ │ │ ├── 20180328114302_InitialCreate.cs │ │ │ └── IdentityContextModelSnapshot.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Repository │ │ │ └── Users │ │ │ │ ├── IUserRepository.cs │ │ │ │ └── UserRepository.cs │ │ ├── Security │ │ │ ├── ITokenMiddleware.cs │ │ │ ├── TokenMiddleware.cs │ │ │ ├── TokenOptions.cs │ │ │ ├── TokenProviderMiddleware.cs │ │ │ ├── TokenProviderOptions.cs │ │ │ ├── TokenRenewMiddleware.cs │ │ │ └── TokenRenewOptions.cs │ │ ├── Startup.Auth.cs │ │ ├── Startup.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── Basket │ │ └── Basket.Application │ │ │ ├── .gitignore │ │ │ ├── Basket.Application.csproj │ │ │ ├── Controllers │ │ │ └── BasketController.cs │ │ │ ├── Dockerfile │ │ │ ├── Dockerfile.debug │ │ │ ├── Filters │ │ │ ├── HttpGlobalExceptionHandlingFilter.cs │ │ │ ├── RequestLoggingMiddleware.cs │ │ │ └── ResponseLoggingMiddleware.cs │ │ │ ├── Infrastructure │ │ │ └── Repositories │ │ │ │ ├── BasketRepository.cs │ │ │ │ └── IBasketRepository.cs │ │ │ ├── IntegrationEvents │ │ │ └── DeleteBasketOnOrderStartedIntegrationEventHandler.cs │ │ │ ├── Models │ │ │ ├── BasketItem.cs │ │ │ └── CustomerBasket.cs │ │ │ ├── Program.cs │ │ │ ├── Services │ │ │ ├── IIdentityService.cs │ │ │ └── IdentityService.cs │ │ │ ├── Startup.cs │ │ │ ├── appsettings.Development.json │ │ │ └── appsettings.json │ ├── Catalog │ │ └── Catalog.Application │ │ │ ├── .gitignore │ │ │ ├── Catalog.Application.csproj │ │ │ ├── Controllers │ │ │ └── ProductsController.cs │ │ │ ├── DbModels │ │ │ └── Product.cs │ │ │ ├── Dockerfile │ │ │ ├── Filters │ │ │ ├── HttpGlobalExceptionHandlingFilter.cs │ │ │ ├── RequestLoggingMiddleware.cs │ │ │ └── ResponseLoggingMiddleware.cs │ │ │ ├── Infrastructure │ │ │ ├── CatalogContext.cs │ │ │ ├── CatalogContextSeed.cs │ │ │ └── Repositories │ │ │ │ ├── CatalogRepository.cs │ │ │ │ └── ICatalogRepository.cs │ │ │ ├── Migrations │ │ │ ├── 20180327201902_InitialCreate.Designer.cs │ │ │ ├── 20180327201902_InitialCreate.cs │ │ │ └── CatalogContextModelSnapshot.cs │ │ │ ├── Program.cs │ │ │ ├── Startup.cs │ │ │ ├── appsettings.Development.json │ │ │ └── appsettings.json │ ├── Gateway │ │ └── Api.Gateway │ │ │ ├── .gitignore │ │ │ ├── Api.Gateway.csproj │ │ │ ├── Dockerfile │ │ │ ├── Program.cs │ │ │ ├── Properties │ │ │ └── launchSettings.json │ │ │ ├── Startup.cs │ │ │ ├── appsettings.Development.json │ │ │ └── appsettings.json │ └── Orders │ │ ├── Orders.Application │ │ ├── .gitignore │ │ ├── Commands │ │ │ ├── CreateOrderCommand.cs │ │ │ └── CreateOrderCommandHandler.cs │ │ ├── Controllers │ │ │ ├── OrderItemDto.cs │ │ │ └── OrdersController.cs │ │ ├── Dockerfile │ │ ├── DomainEventsHandler │ │ │ ├── UpdateOrderOnPaymentMethodVerifiedEventHandler.cs │ │ │ └── ValidateOrAddBuyerOnOrderStartedEventHandler.cs │ │ ├── Filters │ │ │ ├── ErrorResponse.cs │ │ │ ├── HttpGlobalExceptionHandlingFilter.cs │ │ │ ├── RequestLoggingMiddleware.cs │ │ │ └── ResponseLoggingMiddleware.cs │ │ ├── Migrations │ │ │ ├── 20180327205005_InitialCreate.Designer.cs │ │ │ ├── 20180327205005_InitialCreate.cs │ │ │ └── OrdersContextModelSnapshot.cs │ │ ├── Orders.Application.csproj │ │ ├── Program.cs │ │ ├── Services │ │ │ ├── IIdentityService.cs │ │ │ ├── IOrderingIntegrationEventService.cs │ │ │ ├── IdentityService.cs │ │ │ └── OrderingIntegrationService.cs │ │ ├── Startup.cs │ │ ├── Validation │ │ │ ├── CreateOrderCommandValidator.cs │ │ │ └── ValidatorPipeline.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ │ ├── Orders.Domain │ │ ├── .gitignore │ │ ├── AggregatesModel │ │ │ ├── BuyerAggregate │ │ │ │ ├── Buyer.cs │ │ │ │ ├── IBuyerRepository.cs │ │ │ │ └── PaymentMethod.cs │ │ │ ├── Events │ │ │ │ ├── OrderStartedDomainEvent.cs │ │ │ │ └── PaymentMethodVerifiedDomainEvent.cs │ │ │ └── OrderAggregate │ │ │ │ ├── IOrderRepository.cs │ │ │ │ ├── Order.cs │ │ │ │ └── OrderItem.cs │ │ ├── Exceptions │ │ │ └── OrderingDomainException.cs │ │ ├── Orders.Domain.csproj │ │ └── SeedWork │ │ │ ├── Entity.cs │ │ │ ├── IAggregateRoot.cs │ │ │ ├── IRepository.cs │ │ │ └── IUnitOfWork.cs │ │ └── Orders.Infrastructure │ │ ├── .gitignore │ │ ├── MediatorExtension.cs │ │ ├── Orders.Infrastructure.csproj │ │ ├── OrdersContext.cs │ │ ├── OrdersDbContextSeed.cs │ │ └── Repositories │ │ ├── BuyerRepository.cs │ │ └── OrderRepository.cs ├── docker-compose.yml └── docker-stack.yml ├── dockerstack-system ├── docker-stack.yml └── fluentd │ ├── Dockerfile │ └── fluent.conf └── scripts └── copy-ssh-id-to-host.sh /.env: -------------------------------------------------------------------------------- 1 | COMPOSE_CONVERT_WINDOWS_PATHS=1 2 | DEV_WEB_API_DNS_OR_IP=localhost 3 | PROD_WEB_API_DNS_OR_IP= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .vs/ 4 | Dev-notes.txt 5 | swarmprom/ 6 | dockerstack-application/certificates/ -------------------------------------------------------------------------------- /MyMicroservicesApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/MyMicroservicesApp.png -------------------------------------------------------------------------------- /MyMicroservicesApplication.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | ForceIncluded 3 | ForceIncluded 4 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "spa" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "styles.css" 23 | ], 24 | "scripts": [], 25 | "environmentSource": "environments/environment.ts", 26 | "environments": { 27 | "dev": "environments/environment.ts", 28 | "prod": "environments/environment.prod.ts", 29 | "deploy": "environments/environment.deploy.ts" 30 | } 31 | } 32 | ], 33 | "e2e": { 34 | "protractor": { 35 | "config": "./protractor.conf.js" 36 | } 37 | }, 38 | "lint": [ 39 | { 40 | "project": "src/tsconfig.app.json", 41 | "exclude": "**/node_modules/**" 42 | }, 43 | { 44 | "project": "src/tsconfig.spec.json", 45 | "exclude": "**/node_modules/**" 46 | }, 47 | { 48 | "project": "e2e/tsconfig.e2e.json", 49 | "exclude": "**/node_modules/**" 50 | } 51 | ], 52 | "test": { 53 | "karma": { 54 | "config": "./karma.conf.js" 55 | } 56 | }, 57 | "defaults": { 58 | "styleExt": "css", 59 | "component": {} 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | yarn-error.log 36 | 37 | # e2e 38 | /e2e/*.js 39 | /e2e/*.map 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | 45 | # Certificates 46 | /certificate 47 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.13-alpine 2 | 3 | ENV APP_PATH /app 4 | ENV PATH $APP_PATH/node_modules/@angular/cli/bin/:$PATH 5 | ARG ENV 6 | ARG CERT_NAME 7 | ARG CERT_PWD_NAME 8 | ARG CERT_KEY_NAME 9 | 10 | RUN apk add --update --no-cache nodejs && mkdir $APP_PATH && rm -rf /etc/nginx/conf.d/* 11 | WORKDIR $APP_PATH 12 | 13 | COPY . . 14 | 15 | COPY nginx/default.conf /etc/nginx/conf.d/ 16 | COPY certificate/* /etc/nginx/conf.d/certificate/ 17 | 18 | RUN /bin/sed -i "s|CERT_NAME|$CERT_NAME|" /etc/nginx/conf.d/default.conf \ 19 | && /bin/sed -i "s|CERT_KEY_NAME|$CERT_KEY_NAME|" /etc/nginx/conf.d/default.conf \ 20 | && /bin/sed -i "s|CERT_PWD_NAME|$CERT_PWD_NAME|" /etc/nginx/conf.d/default.conf 21 | 22 | RUN npm install \ 23 | && ng build --aot --env=${ENV} \ 24 | && rm -rf /usr/share/nginx/html/* \ 25 | && mv ./dist/* /usr/share/nginx/html/ \ 26 | && apk del nodejs libstdc++ libgcc libuv http-parser ca-certificates \ 27 | && rm -rf ./* 28 | 29 | CMD ["nginx", "-g", "daemon off;"] 30 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/README.md: -------------------------------------------------------------------------------- 1 | # Spa 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.7. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | Before running the tests make sure you are serving the app via `ng serve`. 25 | 26 | ## Further help 27 | 28 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 29 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { SpaPage } from './app.po'; 2 | 3 | describe('spa App', () => { 4 | let page: SpaPage; 5 | 6 | beforeEach(() => { 7 | page = new SpaPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class SpaPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 443 ssl; 4 | server_name localhost; 5 | ssl_certificate /etc/nginx/conf.d/certificate/CERT_NAME; 6 | ssl_certificate_key /etc/nginx/conf.d/certificate/CERT_KEY_NAME; 7 | ssl_password_file /etc/nginx/conf.d/certificate/CERT_PWD_NAME; 8 | 9 | sendfile on; 10 | 11 | default_type application/octet-stream; 12 | 13 | 14 | gzip on; 15 | gzip_http_version 1.1; 16 | gzip_disable "MSIE [1-6]\."; 17 | gzip_min_length 256; 18 | gzip_vary on; 19 | gzip_proxied expired no-cache no-store private auth; 20 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; 21 | gzip_comp_level 9; 22 | 23 | 24 | root /usr/share/nginx/html; 25 | 26 | location / { 27 | try_files $uri $uri/ /index.html =404; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spa", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve --proxy-config proxy.config.local.json", 8 | "start-docker": "ng serve --proxy-config proxy.config.docker.json", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^5.2.0", 17 | "@angular/common": "^5.2.0", 18 | "@angular/compiler": "^5.2.0", 19 | "@angular/core": "^5.2.0", 20 | "@angular/forms": "^5.2.0", 21 | "@angular/http": "^5.2.0", 22 | "@angular/platform-browser": "^5.2.0", 23 | "@angular/platform-browser-dynamic": "^5.2.0", 24 | "@angular/platform-server": "^5.2.0", 25 | "@angular/router": "^5.2.0", 26 | "@ng-bootstrap/ng-bootstrap": "^1.1.1", 27 | "core-js": "^2.4.1", 28 | "rxjs": "^5.5.2", 29 | "zone.js": "^0.8.14" 30 | }, 31 | "devDependencies": { 32 | "@angular/cli": "1.2.7", 33 | "@angular/compiler-cli": "^5.2.0", 34 | "@angular/language-service": "^4.0.0", 35 | "@types/jasmine": "~2.5.53", 36 | "@types/jasminewd2": "~2.0.2", 37 | "@types/node": "~6.0.60", 38 | "codelyzer": "~3.0.1", 39 | "jasmine-core": "~2.6.2", 40 | "jasmine-spec-reporter": "~4.1.0", 41 | "karma": "~1.7.0", 42 | "karma-chrome-launcher": "~2.1.1", 43 | "karma-cli": "~1.0.1", 44 | "karma-coverage-istanbul-reporter": "^1.2.1", 45 | "karma-jasmine": "~1.1.0", 46 | "karma-jasmine-html-reporter": "^0.2.2", 47 | "protractor": "~5.1.2", 48 | "ts-node": "~3.0.4", 49 | "tslint": "~5.3.2", 50 | "typescript": "^2.4.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/proxy.config.docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/token": { 3 | "target": "https://docker-host:5005", 4 | "secure": false, 5 | "changeOrigin": true, 6 | "logLevel": "debug" 7 | }, 8 | "/api/*": { 9 | "target": "https://docker-host:3000", 10 | "secure": false, 11 | "changeOrigin": true, 12 | "logLevel": "debug" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/proxy.config.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/token": { 3 | "target": "https://localhost:443", 4 | "secure": false, 5 | "changeOrigin": true, 6 | "logLevel": "debug" 7 | }, 8 | "/api/basket/*": { 9 | "target": "https://localhost:443", 10 | "secure": false, 11 | "changeOrigin": true, 12 | "logLevel": "debug" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/app/app.component.css -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

MyMicroservices - Tiny eCommerce demo application

5 |
6 |
7 |
8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | } 10 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 4 | import { AppComponent } from './app.component'; 5 | import {AppRoutingModule} from "./app.routes"; 6 | import {LoginModule} from "./login/login.module"; 7 | import {ShopModule} from "./shop/shop.module"; 8 | import {CoreModule} from "./core/core.module"; 9 | import {OrdersModule} from "./orders/orders.module"; 10 | import {BasketModule} from "./basket/basket.module"; 11 | import {HTTP_INTERCEPTORS, HttpClientModule} from "@angular/common/http"; 12 | import {JwtIntegrationHttpInterceptor} from "./core/security/jwt-integration-http.interceptor"; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | AppComponent 17 | ], 18 | imports: [ 19 | HttpClientModule, 20 | BrowserModule, 21 | NgbModule.forRoot(), 22 | AppRoutingModule, 23 | CoreModule, 24 | LoginModule, 25 | ShopModule, 26 | OrdersModule, 27 | BasketModule 28 | ], 29 | providers: [ 30 | { 31 | provide:HTTP_INTERCEPTORS, 32 | useClass: JwtIntegrationHttpInterceptor, 33 | multi: true 34 | } 35 | ], 36 | bootstrap: [AppComponent] 37 | }) 38 | export class AppModule { } 39 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import {RouterModule, Routes} from "@angular/router"; 2 | import {LoginComponent} from "./login/login.component"; 3 | import {ShopComponent} from "./shop/shop.component"; 4 | import {AuthGuardService} from "./core/security/auth.guard.service"; 5 | import {NgModule} from "@angular/core"; 6 | import {OrdersComponent} from "./orders/orders.component"; 7 | import {BasketComponent} from "./basket/basket.component"; 8 | 9 | export const appRoutes: Routes = [ 10 | { 11 | path: 'login', 12 | component: LoginComponent 13 | }, 14 | { 15 | path: 'shop', 16 | component: ShopComponent, 17 | canActivate: [AuthGuardService] 18 | }, 19 | { 20 | path: 'checkout', 21 | component: OrdersComponent, 22 | canActivate: [AuthGuardService] 23 | }, 24 | { 25 | path: 'basket', 26 | component: BasketComponent, 27 | canActivate: [AuthGuardService] 28 | }, 29 | { 30 | path: '', 31 | component: LoginComponent 32 | }, 33 | { 34 | path: '**', 35 | redirectTo: '' 36 | } 37 | ]; 38 | 39 | @NgModule({ 40 | imports: [ RouterModule.forRoot(appRoutes) ], 41 | exports: [ RouterModule ] 42 | }) 43 | export class AppRoutingModule {} 44 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/basket/basket-data.ts: -------------------------------------------------------------------------------- 1 | import {ProductData} from "../core/shared/product-data"; 2 | 3 | export class BasketData { 4 | basketItems: ProductData[]; 5 | 6 | constructor() { 7 | this.basketItems = []; 8 | } 9 | 10 | addItem(productId: number, productName: string, unitPrice: number, units: number) { 11 | if(this.basketItems.filter((item) => item.productId === productId).length > 0) { 12 | this.basketItems.filter((item) => item.productId === productId)[0].units += units; 13 | } 14 | else { 15 | this.basketItems.push({ 16 | productId: productId, 17 | productName: productName, 18 | units: units, 19 | unitPrice: unitPrice 20 | }); 21 | } 22 | } 23 | 24 | getTotalPrice() : number { 25 | let total = 0; 26 | this.basketItems.forEach(item => { 27 | total += item.units * item.unitPrice; 28 | }); 29 | return total; 30 | } 31 | 32 | copyFrom(data: any) { 33 | this.basketItems = []; // Clear current 34 | data.basketItems.forEach(item => { 35 | this.basketItems.push( 36 | { 37 | productId: parseInt(item.productId), 38 | productName: item.productName, 39 | units: parseInt(item.units), 40 | unitPrice: parseFloat(item.unitPrice) 41 | } 42 | ); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/basket/basket.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 | 10 |
11 |
12 | 15 |
16 |
17 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 48 | 49 | 50 |
Product nameUnit PriceQuantityTotalRemove
{{item.productName}}{{item.unitPrice | currency}} 39 | 40 | {{item.units}} 41 | {{item.unitPrice*item.units | currency}} 44 | 47 |
51 |
52 |
53 |
54 |
55 |
56 | Total: {{basket.getTotalPrice() | currency}} 57 |
58 |
59 |
60 |
61 |
62 |
63 | 68 |
69 |
70 |
71 |
72 |
73 |
74 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/basket/basket.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {BasketService} from "./basket.service"; 3 | import {BasketData} from "./basket-data"; 4 | import {AuthenticationService} from "../core/security/authentication.service"; 5 | import {Router} from "@angular/router"; 6 | import {OrdersService} from "../orders/orders.service"; 7 | 8 | @Component({ 9 | selector: 'basket', 10 | templateUrl: 'basket.component.html' 11 | }) 12 | 13 | export class BasketComponent implements OnInit { 14 | 15 | basket: BasketData; 16 | 17 | private readonly basketService: BasketService; 18 | private readonly ordersService: OrdersService; 19 | private authService: AuthenticationService; 20 | private router: Router; 21 | 22 | constructor( 23 | basketService: BasketService, 24 | authService: AuthenticationService, 25 | ordersService: OrdersService, 26 | router: Router) { 27 | this.basketService = basketService; 28 | this.authService = authService; 29 | this.ordersService = ordersService; 30 | this.router = router; 31 | this.basket = new BasketData(); 32 | } 33 | 34 | ngOnInit() { 35 | this.basketService.getBasket().subscribe((data) => { 36 | if(data.result !== null) { 37 | this.basket.copyFrom(data.result); 38 | } 39 | }, 40 | (error) => { 41 | console.error(error); 42 | }); 43 | } 44 | 45 | deleteCurrentBasket() { 46 | this.basketService.deleteBasket().subscribe((data) => { 47 | if(data) { 48 | if(data.result) { 49 | this.basket.basketItems = []; 50 | } 51 | } 52 | }, 53 | (error) => { 54 | console.error(error); 55 | }); 56 | } 57 | 58 | logout() { 59 | this.authService.logout(); 60 | this.router.navigate(['/login']); 61 | } 62 | 63 | checkoutOrder() { 64 | this.router.navigate(['/checkout']); 65 | } 66 | 67 | backToShop() { 68 | this.router.navigate(['/shop']); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/basket/basket.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {BasketComponent} from './basket.component'; 3 | import {BasketService} from "./basket.service"; 4 | import {CoreModule} from "../core/core.module"; 5 | import {CommonModule} from "@angular/common"; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CoreModule, 10 | CommonModule 11 | ], 12 | exports: [], 13 | declarations: [BasketComponent], 14 | providers: [BasketService], 15 | }) 16 | export class BasketModule { } 17 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/basket/basket.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import 'rxjs/add/operator/map' 4 | import {BasketData} from "./basket-data"; 5 | import {environment} from "../../environments/environment"; 6 | import {HttpClient} from "@angular/common/http"; 7 | 8 | @Injectable() 9 | export class BasketService { 10 | 11 | constructor(private http: HttpClient) { } 12 | 13 | getBasket() : Observable { 14 | return this.http.get(environment.settings.basket_gateway + `/api/basket`); 15 | } 16 | 17 | updateBasket(basket: BasketData) : Observable { 18 | return this.http.post(environment.settings.basket_gateway + '/api/basket', basket); 19 | } 20 | 21 | deleteBasket() : Observable { 22 | return this.http.delete(environment.settings.basket_gateway + `/api/basket`); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from "@angular/core"; 2 | import {HttpModule} from "@angular/http"; 3 | import {AuthGuardService} from "./security/auth.guard.service"; 4 | import {AuthenticationService} from "./security/authentication.service"; 5 | import {UtilityService} from "./utils.service"; 6 | 7 | @NgModule({ 8 | imports: [ 9 | HttpModule 10 | ], 11 | declarations: [ 12 | ], 13 | exports: [ 14 | ], 15 | providers: [ 16 | AuthenticationService, 17 | AuthGuardService, 18 | UtilityService 19 | ] 20 | }) 21 | export class CoreModule {} 22 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/core/security/auth.guard.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router"; 3 | import {Observable} from "rxjs/Observable"; 4 | import {AuthenticationService} from "./authentication.service"; 5 | 6 | @Injectable() 7 | export class AuthGuardService implements CanActivate { 8 | 9 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) 10 | : boolean | Observable | Promise { 11 | 12 | if(this.authService.tokenCheck()) { 13 | return true; 14 | } 15 | 16 | this.router.navigate(['/login']); 17 | return false; 18 | } 19 | 20 | private router: Router; 21 | private authService; 22 | 23 | constructor(router: Router, authService: AuthenticationService) { 24 | this.router = router; 25 | this.authService = authService; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/core/security/jwt-integration-http.interceptor.ts: -------------------------------------------------------------------------------- 1 | import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http"; 2 | import {Injectable, Injector} from "@angular/core"; 3 | import {Observable} from "rxjs/Observable"; 4 | import {AuthenticationService} from "./authentication.service"; 5 | import {UtilityService} from "../utils.service"; 6 | 7 | /** 8 | * Add jwt in every http request 9 | */ 10 | @Injectable() 11 | export class JwtIntegrationHttpInterceptor implements HttpInterceptor { 12 | 13 | constructor(private inj: Injector) { } 14 | 15 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 16 | 17 | const auth = this.inj.get(AuthenticationService); 18 | 19 | if(auth.token) { 20 | req = req.clone({ 21 | setHeaders: { 22 | Authorization: `Bearer ${auth.token}`, 23 | RequestId: UtilityService.newGuid() 24 | } 25 | }); 26 | } 27 | 28 | return next.handle(req) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/core/shared/product-data.ts: -------------------------------------------------------------------------------- 1 | export class ProductData { 2 | productId: number; 3 | productName: string; 4 | unitPrice: number; 5 | units: number; 6 | } 7 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/core/utils.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | 3 | @Injectable() 4 | export class UtilityService { 5 | 6 | /** 7 | * Decode Json web token content 8 | * @param {string} token 9 | * @returns {JSON} 10 | */ 11 | static decodeToken(token: string) : any { 12 | 13 | let base64 = token.split('.')[1] 14 | .replace('-', '+') 15 | .replace('_', '/'); 16 | 17 | return JSON.parse(window.atob(base64)); 18 | } 19 | 20 | /** 21 | * Generate a random guid 22 | * @returns {string} 23 | */ 24 | static newGuid() { 25 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 26 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 27 | return v.toString(16); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | .well{ 2 | background-color:#fff!important; 3 | border-radius:0!important; 4 | border:black solid 1px; 5 | } 6 | 7 | .well.login-box { 8 | /*width:400px; */ 9 | border:#d1d1d1 solid 1px; 10 | margin:0 auto; 11 | margin-top:30px; 12 | } 13 | .well.login-box legend { 14 | font-size:26px; 15 | text-align:center; 16 | font-weight:300; 17 | } 18 | .well.login-box label { 19 | font-weight:300; 20 | font-size:13px; 21 | 22 | } 23 | .well.login-box input[type="text"] { 24 | box-shadow:none; 25 | border-color:#ddd; 26 | border-radius:0; 27 | } 28 | 29 | .well.welcome-text{ 30 | font-size:21px; 31 | } 32 | 33 | /* Notifications */ 34 | 35 | .notification{ 36 | position:fixed; 37 | top: 20px; 38 | right:0; 39 | background-color:#FF4136; 40 | padding: 20px; 41 | color: #fff; 42 | font-size:21px; 43 | display:none; 44 | } 45 | .notification-success{ 46 | background-color:#3D9970; 47 | } 48 | 49 | .notification-show{ 50 | display:block!important; 51 | } 52 | 53 | /*Loged in*/ 54 | .btn-default { 55 | color: #333; 56 | background-color: #f9f9f9; 57 | border-color: #ccc; 58 | border: 1px solid; 59 | text-align: center; 60 | cursor: pointer; 61 | color: #5e5e5e; 62 | -moz-border-radius: 3px; 63 | -webkit-border-radius: 3px; 64 | border-radius: 3px; 65 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fefefe), color-stop(100%, #f9f9f9)), #f9f9f9; 66 | background: -moz-linear-gradient(#fefefe, #f9f9f9), #f9f9f9; 67 | background: -webkit-linear-gradient(#fefefe, #f9f9f9), #f9f9f9; 68 | background: linear-gradient(#fefefe, #f9f9f9), #f9f9f9; 69 | border-color: #c3c3c3 #c3c3c3 #bebebe; 70 | -moz-box-shadow: rgba(0, 0, 0, 0.06) 0 1px 0, rgba(255, 255, 255, 0.1) 0 1px 0 0 inset; 71 | -webkit-box-shadow: rgba(0, 0, 0, 0.06) 0 1px 0, rgba(255, 255, 255, 0.1) 0 1px 0 0 inset; 72 | box-shadow: rgba(0, 0, 0, 0.06) 0 1px 0, rgba(255, 255, 255, 0.1) 0 1px 0 0 inset; 73 | } 74 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Component, OnInit} from "@angular/core"; 3 | import {FormBuilder, FormGroup, Validators} from "@angular/forms"; 4 | import {Router} from "@angular/router"; 5 | import {UserData} from "./user-data"; 6 | import {AuthenticationService} from "../core/security/authentication.service"; 7 | 8 | @Component({ 9 | templateUrl: 'login.component.html', 10 | styleUrls: ['login.component.css'] 11 | }) 12 | export class LoginComponent implements OnInit { 13 | form: FormGroup; 14 | private formBuilder: FormBuilder; 15 | private userDto: UserData; 16 | private router: Router; 17 | private authService: AuthenticationService; 18 | 19 | formErrors = { 20 | 'username': '', 21 | 'password': '' 22 | }; 23 | 24 | validationMessages = { 25 | 'username': { 26 | 'required': 'Required' 27 | }, 28 | 'password': { 29 | 'required': 'Required' 30 | } 31 | }; 32 | 33 | loginError = ''; 34 | 35 | constructor(formBuilder: FormBuilder, router: Router, authService: AuthenticationService) { 36 | this.formBuilder = formBuilder; 37 | this.userDto = new UserData(); 38 | this.router = router; 39 | this.authService = authService; 40 | 41 | this.buildForm(); 42 | } 43 | 44 | ngOnInit() { 45 | if(this.authService.tokenCheck()) { 46 | this.router.navigate(['/shop']); 47 | return; 48 | } 49 | } 50 | 51 | /** 52 | * Login action 53 | */ 54 | onNgSubmit() { 55 | this.userDto = this.form.value; 56 | this.authService.login(this.userDto.username, this.userDto.password).subscribe( 57 | (result: any) => { 58 | if(result) { 59 | this.router.navigate(['/shop']); 60 | } 61 | else { 62 | this.loginError = 'Username/password not valid'; 63 | } 64 | } 65 | ); 66 | } 67 | 68 | /** 69 | * Setup the login form 70 | */ 71 | private buildForm() { 72 | this.form = this.formBuilder.group({ 73 | 'username': [ 74 | this.userDto.username, 75 | Validators.required 76 | ], 77 | 'password': [ 78 | this.userDto.password, 79 | Validators.required 80 | ] 81 | }); 82 | 83 | this.form.valueChanges.subscribe((data) => { 84 | this.onDataChanged(data); 85 | }); 86 | this.onDataChanged(); 87 | } 88 | 89 | /** 90 | * Setup the error messages 91 | * @param data 92 | */ 93 | private onDataChanged(data?: any) { 94 | if (!this.form) 95 | return; 96 | 97 | const _form = this.form; 98 | 99 | for (const field in this.formErrors) { 100 | const control = _form.get(field); 101 | this.formErrors[field] = ''; 102 | 103 | if (control && control.dirty && !control.valid) { 104 | const messages = this.validationMessages[field]; 105 | for (const key in control.errors) { 106 | this.formErrors[field] += messages[key] + ' '; 107 | } 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/login/login.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from "@angular/common"; 3 | import {ReactiveFormsModule} from "@angular/forms"; 4 | import {LoginComponent} from "./login.component"; 5 | import {CoreModule} from "../core/core.module"; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | ReactiveFormsModule, 11 | CoreModule 12 | ], 13 | declarations: [ 14 | LoginComponent 15 | ], 16 | exports: [ 17 | LoginComponent 18 | ], 19 | providers: [ 20 | 21 | ] 22 | }) 23 | export class LoginModule {} 24 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/login/user-data.ts: -------------------------------------------------------------------------------- 1 | export class UserData { 2 | username: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/orders/order-command.ts: -------------------------------------------------------------------------------- 1 | import {ProductData} from "../core/shared/product-data"; 2 | 3 | export class CreateOrderCommand { 4 | orderItems: Array; 5 | cardNumber: string; 6 | cardHolderName: string; 7 | cardSecurityNumber: number; 8 | cardExpiration: Date; 9 | 10 | constructor() { 11 | this.orderItems = []; 12 | } 13 | 14 | addItem(productId: number, 15 | productName: string, 16 | unitPrice: number, 17 | units: number) { 18 | 19 | let item = new ProductData(); 20 | 21 | item.unitPrice = unitPrice; 22 | item.units = units; 23 | item.productName = productName; 24 | item.productId = productId; 25 | 26 | this.orderItems.push(item); 27 | } 28 | 29 | setCardExpiration(month: number, year: number) { 30 | this.cardExpiration = new Date(); 31 | this.cardExpiration.setMonth(month); 32 | this.cardExpiration.setFullYear(year); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/orders/order-confirmation.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Order submitted successfully

4 |
5 | 8 |
9 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/orders/order-confirmation.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'order-confirmation', 5 | templateUrl: 'order-confirmation.component.html' 6 | }) 7 | 8 | export class OrderConfirmationComponent {} 9 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/orders/orders.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from "@angular/core"; 2 | import {CoreModule} from "../core/core.module"; 3 | import {OrdersComponent} from "./orders.component"; 4 | import {OrdersService} from "./orders.service"; 5 | import {CommonModule} from "@angular/common"; 6 | import {FormsModule, ReactiveFormsModule} from "@angular/forms"; 7 | import {OrderConfirmationComponent} from "./order-confirmation.component"; 8 | import {RouterModule} from "@angular/router"; 9 | 10 | @NgModule({ 11 | imports: [ 12 | CoreModule, 13 | CommonModule, 14 | FormsModule, 15 | ReactiveFormsModule, 16 | RouterModule 17 | ], 18 | exports: [], 19 | declarations: [ 20 | OrdersComponent, 21 | OrderConfirmationComponent 22 | ], 23 | providers: [OrdersService] 24 | }) 25 | export class OrdersModule {} 26 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/orders/orders.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import 'rxjs/add/operator/map' 4 | import {CreateOrderCommand} from "./order-command"; 5 | import {environment} from "../../environments/environment"; 6 | import {HttpClient} from "@angular/common/http"; 7 | 8 | @Injectable() 9 | export class OrdersService { 10 | 11 | constructor(private http: HttpClient) { } 12 | 13 | placeOrder(createOrderCommand: CreateOrderCommand) : Observable { 14 | return this.http.post( 15 | environment.settings.orders_gateway + '/api/orders/new', 16 | createOrderCommand 17 | ) 18 | .map((response: Response) => response); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/shop/product-shop-data.ts: -------------------------------------------------------------------------------- 1 | export class ProductShopData { 2 | 3 | constructor(id: number, productName: string, unitPrice: number, _package: string, assets: number) { 4 | this.UnitPrice = unitPrice; 5 | this.ProductName = productName; 6 | this.Assets = assets; 7 | this.Id = id; 8 | this.Package = _package; 9 | } 10 | 11 | Id: number; 12 | ProductName: string; 13 | UnitPrice: number; 14 | Package: string; 15 | Assets: number; 16 | } 17 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/shop/shop.component.css: -------------------------------------------------------------------------------- 1 | .glyphicon { margin-right:5px; } 2 | .thumbnail 3 | { 4 | margin-bottom: 20px; 5 | padding: 0px; 6 | -webkit-border-radius: 0px; 7 | -moz-border-radius: 0px; 8 | border-radius: 0px; 9 | } 10 | 11 | .item.list-group-item 12 | { 13 | float: none; 14 | width: 100%; 15 | background-color: #fff; 16 | margin-bottom: 10px; 17 | } 18 | .item.list-group-item:nth-of-type(odd):hover,.item.list-group-item:hover 19 | { 20 | background: #428bca; 21 | } 22 | 23 | .item.list-group-item .list-group-image 24 | { 25 | margin-right: 10px; 26 | } 27 | .item.list-group-item .thumbnail 28 | { 29 | margin-bottom: 0px; 30 | } 31 | .item.list-group-item .caption 32 | { 33 | padding: 9px 9px 0px 9px; 34 | } 35 | .item.list-group-item:nth-of-type(odd) 36 | { 37 | background: #eeeeee; 38 | } 39 | 40 | .item.list-group-item:before, .item.list-group-item:after 41 | { 42 | display: table; 43 | content: " "; 44 | } 45 | 46 | .item.list-group-item img 47 | { 48 | float: left; 49 | } 50 | .item.list-group-item:after 51 | { 52 | clear: both; 53 | } 54 | .list-group-item-text 55 | { 56 | margin: 0 0 11px; 57 | } 58 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/shop/shop.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Display 4 |
5 | 8 | 11 |
12 |
13 | 23 | 26 |
27 |
28 |
29 |
32 |
33 | demo-product-image 34 |
35 |

36 | {{product.ProductName}}

37 | Available quantity: {{product.Assets}} 38 |

39 | {{product.Package}}

40 |
41 |
42 |

43 | ${{product.UnitPrice}}

44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/shop/shop.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {ShopService} from "./shop.service"; 3 | import {AuthenticationService} from "../core/security/authentication.service"; 4 | import {Router} from "@angular/router"; 5 | import {BasketService} from "../basket/basket.service"; 6 | import {BasketData} from "../basket/basket-data"; 7 | import {ProductShopData} from "./product-shop-data"; 8 | 9 | @Component({ 10 | selector: 'shop-component', 11 | templateUrl: 'shop.component.html', 12 | styleUrls: ['shop.component.css'] 13 | }) 14 | 15 | export class ShopComponent implements OnInit { 16 | 17 | products: ProductShopData[]; 18 | basket: BasketData; 19 | 20 | private shopService: ShopService; 21 | private viewType: string = "grid"; 22 | private authService: AuthenticationService; 23 | private router: Router; 24 | private basketService: BasketService; 25 | 26 | constructor( 27 | shopService: ShopService, 28 | authService: AuthenticationService, 29 | router: Router, 30 | basketService: BasketService) { 31 | this.shopService = shopService; 32 | this.authService = authService; 33 | this.router = router; 34 | this.basketService = basketService; 35 | 36 | this.basket = new BasketData(); 37 | this.products = []; 38 | } 39 | 40 | ngOnInit() { 41 | this.shopService.getProducts().subscribe((data) => { 42 | data.forEach(item => { 43 | this.products.push(new ProductShopData( 44 | item.id, 45 | item.productName, 46 | item.unitPrice, 47 | item.package, 48 | item.assets)); 49 | }); 50 | }, 51 | (error) => { 52 | console.error(error); 53 | }); 54 | 55 | this.basketService.getBasket().subscribe((data) => { 56 | if(data.result !== null) { 57 | this.basket.copyFrom(data.result); 58 | } 59 | }, 60 | (error) => { 61 | console.error(error); 62 | }); 63 | } 64 | 65 | viewByList(event) { 66 | event.preventDefault(); 67 | this.viewType = 'list'; 68 | } 69 | 70 | viewByGrid(event) { 71 | event.preventDefault(); 72 | this.viewType = 'grid'; 73 | } 74 | 75 | addToBasket(product: ProductShopData) { 76 | 77 | this.basket.addItem(product.Id,product.ProductName,product.UnitPrice,1); 78 | 79 | this.basketService.updateBasket(this.basket).subscribe((data) => { 80 | if(data.result !== null) { 81 | this.basket.copyFrom(data.result); 82 | } 83 | },(error) => { 84 | console.error(error); 85 | }); 86 | } 87 | 88 | goToBasket() { 89 | this.router.navigate(['/basket']); 90 | } 91 | 92 | logout() { 93 | this.authService.logout(); 94 | this.router.navigate(['/login']); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/shop/shop.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {ShopComponent} from './shop.component'; 3 | import {CoreModule} from "../core/core.module"; 4 | import {ShopService} from "./shop.service"; 5 | import {CommonModule} from "@angular/common"; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CoreModule, 10 | CommonModule 11 | ], 12 | exports: [], 13 | declarations: [ShopComponent], 14 | providers: [ShopService], 15 | }) 16 | export class ShopModule { 17 | } 18 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/app/shop/shop.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {AuthenticationService} from "../core/security/authentication.service"; 3 | import { Observable } from 'rxjs'; 4 | import 'rxjs/add/operator/map' 5 | import {environment} from "../../environments/environment"; 6 | import {HttpClient} from "@angular/common/http"; 7 | 8 | @Injectable() 9 | export class ShopService { 10 | 11 | private http: HttpClient; 12 | private authService: AuthenticationService; 13 | 14 | constructor(http: HttpClient, authService: AuthenticationService) { 15 | this.http = http; 16 | this.authService = authService; 17 | } 18 | 19 | getProducts() : Observable { 20 | return this.http.get(environment.settings.catalog_gateway+ '/api/products'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/.gitkeep -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/css/images/meteorshower2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/css/images/meteorshower2.jpg -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/css/images/pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/css/images/pattern.jpg -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/assets/images/shopping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/assets/images/shopping.png -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/environments/environment.deploy.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | settings: { 4 | auth_gateway: "https://docker-host-3:5005", 5 | orders_gateway: "https://docker-host-3:3000", 6 | catalog_gateway: "https://docker-host-3:3000", 7 | basket_gateway: "https://docker-host-3:3000" 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | settings: { 4 | auth_gateway: "https://docker-host:5005", 5 | orders_gateway: "https://docker-host:3000", 6 | catalog_gateway: "https://docker-host:3000", 7 | basket_gateway: "https://docker-host:3000" 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | settings: { 9 | auth_gateway: "", 10 | orders_gateway: "", 11 | catalog_gateway: "", 12 | basket_gateway: "" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandhaka/MyMicroservicesApplication/6f072febb933632d85f852a23f1cf724fbb01961/dockerstack-application/Clients/Web/spa/src/favicon.ico -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spa 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** Evergreen browsers require these. **/ 41 | import 'core-js/es6/reflect'; 42 | import 'core-js/es7/reflect'; 43 | 44 | 45 | /** 46 | * Required to support Web Animations `@angular/animation`. 47 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 48 | **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | /** 70 | * Need to import at least one locale-data with intl. 71 | */ 72 | // import 'intl/locale-data/jsonp/en'; 73 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import url("./assets/css/bootstrap.css"); 3 | @import url("./assets/css/font-awesome.css"); 4 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /dockerstack-application/Clients/Web/spa/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2016", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/Abstractions/IEventBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using EventBus.Events; 5 | using EventBus.Responses; 6 | 7 | namespace EventBus.Abstractions 8 | { 9 | public interface IEventBus 10 | { 11 | void Init(); 12 | 13 | Task PublishAsync(string message, CancellationToken cancellationToken) 14 | where T : IntegrationEvent; 15 | 16 | string Subscribe(Func handler) 17 | where T : IntegrationEvent 18 | where TH : IIntegrationEventHandler; 19 | 20 | bool Unsubscribe(string guid) 21 | where T : IntegrationEvent 22 | where TH : IIntegrationEventHandler; 23 | } 24 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/Abstractions/IIntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EventBus.Events; 3 | 4 | namespace EventBus.Abstractions 5 | { 6 | public interface IIntegrationEventHandler 7 | where TIntegrationEvent : IntegrationEvent 8 | { 9 | Task Handle(TIntegrationEvent @event); 10 | } 11 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/Abstractions/ISubscriptionsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EventBus.Events; 3 | 4 | namespace EventBus.Abstractions 5 | { 6 | public interface ISubscriptionsManager 7 | { 8 | event EventHandler OnIntegrationEventReceived; 9 | event EventHandler OnIntegrationEventReadyToDelete; 10 | 11 | string AddSubscription(Func handler) 12 | where T : IntegrationEvent 13 | where TH : IIntegrationEventHandler; 14 | 15 | void RemoveSubscription(string guid) 16 | where T : IntegrationEvent; 17 | 18 | bool HasSubscription(string guid) 19 | where T : IntegrationEvent; 20 | 21 | string GetEventTopicArn() where T : IntegrationEvent; 22 | 23 | bool HasEventTopic() where T : IntegrationEvent; 24 | 25 | string GetEventKey() where T : IntegrationEvent; 26 | 27 | void ProcessEvent(string typeName, IntegrationEventReceivedNotificationDto eventReceived); 28 | } 29 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/EventBus.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/Events/IntegrationEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace EventBus.Events 5 | { 6 | /// 7 | /// Integration Events notes: 8 | // An Event is “something that has happened in the past”, therefore its name has to be 9 | // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. 10 | /// 11 | [DataContract] 12 | public class IntegrationEvent 13 | { 14 | [DataMember] 15 | public Guid Id { get; set; } 16 | [DataMember] 17 | public DateTime CreationDate { get; set; } 18 | 19 | public IntegrationEvent(Guid eventGuid, DateTime creationDateTime) 20 | { 21 | Id = eventGuid; 22 | CreationDate = creationDateTime; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/IntegrationEventReceivedNotificationDto.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SQS.Model; 2 | 3 | namespace EventBus 4 | { 5 | public class IntegrationEventReceivedNotificationDto 6 | { 7 | public IntegrationEventReceivedNotificationDto(Message message, string queueUrl) 8 | { 9 | Message = message; 10 | QueueUrl = queueUrl; 11 | } 12 | 13 | public Message Message { get; } 14 | public string QueueUrl { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/ResilientTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace EventBus 6 | { 7 | public class ResilientTransaction 8 | { 9 | private DbContext _context; 10 | private ResilientTransaction(DbContext context) => 11 | _context = context ?? throw new ArgumentNullException(nameof(context)); 12 | 13 | public static ResilientTransaction New (DbContext context) => 14 | new ResilientTransaction(context); 15 | 16 | public async Task ExecuteAsync(Func action) 17 | { 18 | //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): 19 | //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency 20 | var strategy = _context.Database.CreateExecutionStrategy(); 21 | await strategy.ExecuteAsync(async () => 22 | { 23 | using (var transaction = _context.Database.BeginTransaction()) 24 | { 25 | await action(); 26 | transaction.Commit(); 27 | } 28 | }); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/Responses/EbPublishResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace EventBus.Responses 4 | { 5 | public class EbPublishResponse : EventBusResponse 6 | { 7 | public EbPublishResponse(HttpStatusCode httpStatusCode) : base(httpStatusCode) { } 8 | 9 | public string MessageId { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/Responses/EventBusResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace EventBus.Responses 4 | { 5 | public class EventBusResponse 6 | { 7 | public EventBusResponse(HttpStatusCode httpStatusCode) 8 | { 9 | StatusCode = httpStatusCode; 10 | } 11 | 12 | public HttpStatusCode StatusCode { get; } 13 | public string Arn { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBus/SnsMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus 4 | { 5 | /// 6 | /// Aws SNS message DTO 7 | /// 8 | public class SnsMessage 9 | { 10 | public string Type { get; set; } 11 | public string MessageId { get; set; } 12 | public string TopicArn { get; set; } 13 | public string Message { get; set; } 14 | public DateTime Timestamp { get; set; } 15 | public string Signature { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBusAwsSns/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBusAwsSns/EventBusAwsSns.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | {A04922E2-BBD8-47D9-BA55-C36CE5B15753} 8 | EventBus 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/EventBus/EventBusAwsSns/Shared/IntegrationEvents/OrderStartedIntegrationEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using EventBus.Events; 5 | 6 | namespace EventBusAwsSns.Shared.IntegrationEvents 7 | { 8 | [DataContract] 9 | public class OrderStartedIntegrationEvent : IntegrationEvent 10 | { 11 | [DataMember] 12 | public List OrderItems { get; set; } 13 | 14 | [DataMember] 15 | public string UserId { get; } 16 | 17 | public OrderStartedIntegrationEvent(Guid guid, string userId, DateTime creation, List orderItems) 18 | : base(guid, creation) 19 | { 20 | OrderItems = orderItems; 21 | UserId = userId; 22 | } 23 | } 24 | 25 | public class OrderItemInfo 26 | { 27 | public int ProductId { get; set; } 28 | public int Assets { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/IntegrationEventsContext/IntegrationEventsContext/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/IntegrationEventsContext/IntegrationEventsContext/IIntegrationEventsRespository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using IntegrationEventsContext.Models; 4 | 5 | namespace IntegrationEventsContext 6 | { 7 | public interface IIntegrationEventsRespository 8 | { 9 | Task GetModelAsync(); 10 | Task SetModelAsync(bool subscription = true); 11 | 12 | Task CreateInstanceAsync(); 13 | Task GetInstanceAsync(string guid, string eventType); 14 | Task DeleteInstanceAsync(string guid, string eventType); 15 | 16 | Task MarkSubscriberHandlerAsProcessed( 17 | string guid, 18 | string eventTypeName, 19 | string subscriberName); 20 | 21 | Task> GetIntegrationEventRegisteredInstances(); 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/IntegrationEventsContext/IntegrationEventsContext/IntegrationEventsContext.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/IntegrationEventsContext/IntegrationEventsContext/Models/IntegrationEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace IntegrationEventsContext.Models 6 | { 7 | public class IntegrationEvent 8 | { 9 | public string EventType { get; } 10 | 11 | /// 12 | /// Set of subscriber tuples, instance name - handled status 13 | /// 14 | public HashSet> Subscribers { get; set; } 15 | 16 | public IntegrationEvent(string eventType) 17 | { 18 | EventType = eventType; 19 | Subscribers = new HashSet>(); 20 | } 21 | 22 | protected string SubscribersToString() 23 | { 24 | StringBuilder sb = new StringBuilder(); 25 | sb.Append("{"); 26 | foreach (var subscriber in Subscribers) 27 | { 28 | sb.Append($"[Instance name: {subscriber.Item1}, Handled: {subscriber.Item2}]"); 29 | } 30 | sb.Append("}"); 31 | return sb.ToString(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/IntegrationEventsContext/IntegrationEventsContext/Models/IntegrationEventInstance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IntegrationEventsContext.Models 4 | { 5 | public class IntegrationEventInstance : IntegrationEvent 6 | { 7 | public string Id { get; } 8 | 9 | public IntegrationEventInstance(string id, string eventType) : 10 | base(eventType) 11 | { 12 | Id = id ?? throw new ArgumentNullException(nameof(id)); 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return $"[EventType: {EventType}, Id: {Id}, Subscribers: {SubscribersToString()}]"; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/WebHostCustomization/WebHost.Customization/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /dockerstack-application/EcosystemBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using Polly; 5 | using System; 6 | using System.Data.SqlClient; 7 | 8 | namespace Microsoft.AspNetCore.Hosting 9 | { 10 | public static class IWebHostExtensions 11 | { 12 | public static IWebHost MigrateDbContext( 13 | this IWebHost webHost, Action seeder) where TContext : DbContext 14 | { 15 | using (var scope = webHost.Services.CreateScope()) 16 | { 17 | var services = scope.ServiceProvider; 18 | 19 | var logger = services.GetRequiredService>(); 20 | 21 | var context = services.GetService(); 22 | 23 | try 24 | { 25 | logger.LogInformation($"Migrating database associated with context {typeof(TContext).Name}"); 26 | 27 | var retry = Policy.Handle() 28 | .WaitAndRetry(new TimeSpan[] 29 | { 30 | TimeSpan.FromSeconds(5), 31 | TimeSpan.FromSeconds(10), 32 | TimeSpan.FromSeconds(15), 33 | }); 34 | 35 | retry.Execute(() => 36 | { 37 | //if the sql server container is not created on run docker compose this 38 | //migration can't fail for network related exception. The retry options for DbContext only 39 | //apply to transient exceptions. 40 | 41 | context.Database 42 | .Migrate(); 43 | 44 | seeder(context, services); 45 | }); 46 | 47 | 48 | logger.LogInformation($"Migrated database associated with context {typeof(TContext).Name}"); 49 | } 50 | catch (Exception ex) 51 | { 52 | logger.LogError(ex, $"An error occurred while migrating the database used on context {typeof(TContext).Name}"); 53 | } 54 | } 55 | 56 | return webHost; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | keys/ 4 | certificate/ -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/AuthService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/AuthService.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | IIS Express 5 | 6 | -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/DbModels/AppUser.cs: -------------------------------------------------------------------------------- 1 | namespace AuthService.DbModels 2 | { 3 | public class AppUser 4 | { 5 | public string id { get; set; } 6 | public string username { get; set; } 7 | public string password { get; set; } 8 | public string name_full { get; set; } 9 | public int level { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/DbModels/IdentityContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace AuthService.DbModels 5 | { 6 | /// 7 | /// Database context 8 | /// 9 | public class IdentityContext : DbContext 10 | { 11 | private const string DefaultSchema = "mymicsapp.Services.identityDb"; 12 | 13 | public IdentityContext(DbContextOptions options) : base(options) {} 14 | 15 | public DbSet ApplicationUsers {get; set; } 16 | 17 | protected override void OnModelCreating(ModelBuilder modelBuilder) 18 | { 19 | modelBuilder.Entity(ConfigureAppUser); 20 | } 21 | 22 | private void ConfigureAppUser(EntityTypeBuilder appUserConfiguration) 23 | { 24 | appUserConfiguration.ToTable("applicationusers", DefaultSchema); 25 | appUserConfiguration.HasKey(cr => cr.id); 26 | appUserConfiguration.Property("username").IsRequired(); 27 | appUserConfiguration.Property("password").IsRequired(); 28 | appUserConfiguration.Property("name_full"); 29 | appUserConfiguration.Property("level"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/DbModels/IdentityContextSeed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace AuthService.DbModels 10 | { 11 | /// 12 | /// Init database with default values 13 | /// 14 | public class UserDbContextSeed 15 | { 16 | public async Task SeedAsync(IdentityContext context, ILoggerFactory loggerFactory) 17 | { 18 | try 19 | { 20 | using(context) 21 | { 22 | if (!context.ApplicationUsers.Any()) 23 | { 24 | context.ApplicationUsers.AddRange(GetDefaultsUser()); 25 | await context.SaveChangesAsync(); 26 | } 27 | } 28 | } 29 | catch (Exception exception) 30 | { 31 | var log = loggerFactory.CreateLogger("users seed"); 32 | log.LogError(exception.Message); 33 | } 34 | } 35 | 36 | private AppUser GetDefaultsUser() 37 | { 38 | var md5Hascher = MD5.Create(); 39 | var passwordHash = md5Hascher.ComputeHash(Encoding.UTF8.GetBytes("admin")); 40 | var sBuilder = new StringBuilder(); 41 | 42 | foreach (var t in passwordHash) 43 | { 44 | sBuilder.Append(t.ToString("x2")); 45 | } 46 | 47 | return new AppUser 48 | { 49 | id = Guid.NewGuid().ToString(), 50 | level = 0, // Admin permissions as default 51 | name_full = "Developer", 52 | username = "admin", 53 | password = sBuilder.ToString() 54 | }; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:2.0.3 2 | 3 | # Copy application files 4 | WORKDIR /app 5 | COPY bin/Debug/netcoreapp2.0/publish /app 6 | 7 | EXPOSE 443 8 | 9 | ENTRYPOINT /bin/bash -c "dotnet AuthService.dll" -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Dockerfile.debug: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:1.1 2 | 3 | # Remote debugger 4 | RUN apt-get update \ 5 | && apt-get install unzip -y \ 6 | && apt-get install -y openssh-server \ 7 | && curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg 8 | 9 | # Enable root login 10 | RUN mkdir /var/run/sshd 11 | RUN echo 'root:password' | chpasswd 12 | RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config 13 | 14 | # SSH login fix. Otherwise user is kicked off after login 15 | RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 16 | 17 | ENV NOTVISIBLE "in users profile" 18 | RUN echo "export VISIBLE=now" >> /etc/profile 19 | 20 | # Copy application files 21 | WORKDIR /app 22 | COPY bin/Debug/netcoreapp1.1/publish /app 23 | 24 | EXPOSE 5000 22 25 | 26 | ENTRYPOINT /bin/bash -c "dotnet AuthService.dll" -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Filters/RequestLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace AuthService.Filters 8 | { 9 | public class RequestLoggingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ILogger _logger; 13 | 14 | public RequestLoggingMiddleware(RequestDelegate next, ILogger logger) 15 | { 16 | _next = next; 17 | _logger = logger; 18 | } 19 | 20 | public async Task Invoke(HttpContext context) 21 | { 22 | var injectedRequestStream = new MemoryStream(); 23 | 24 | try 25 | { 26 | var requestLog = $"REQUEST HttpMethod: {context.Request.Method}, Path: {context.Request.Path}"; 27 | 28 | using (var bodyReader = new StreamReader(context.Request.Body)) 29 | { 30 | var bodyAsText = bodyReader.ReadToEnd(); 31 | if (string.IsNullOrWhiteSpace(bodyAsText) == false) 32 | { 33 | requestLog += $", Body : {bodyAsText}"; 34 | } 35 | 36 | var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText); 37 | injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length); 38 | injectedRequestStream.Seek(0, SeekOrigin.Begin); 39 | context.Request.Body = injectedRequestStream; 40 | } 41 | 42 | _logger.LogTrace(requestLog); 43 | 44 | await _next.Invoke(context); 45 | } 46 | finally 47 | { 48 | injectedRequestStream.Dispose(); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Filters/ResponseLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace AuthService.Filters 8 | { 9 | public class ResponseLoggingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ILogger _logger; 13 | private readonly Func _defaultFormatter = (state, exception) => state; 14 | 15 | public ResponseLoggingMiddleware(RequestDelegate next, ILogger logger) 16 | { 17 | _next = next; 18 | _logger = logger; 19 | } 20 | 21 | public async Task Invoke(HttpContext context) 22 | { 23 | var bodyStream = context.Response.Body; 24 | 25 | var responseBodyStream = new MemoryStream(); 26 | context.Response.Body = responseBodyStream; 27 | 28 | await _next(context); 29 | 30 | responseBodyStream.Seek(0, SeekOrigin.Begin); 31 | var responseBody = new StreamReader(responseBodyStream).ReadToEnd(); 32 | _logger.Log(LogLevel.Trace, 1, $"RESPONSE LOG: {responseBody}", null, _defaultFormatter); 33 | responseBodyStream.Seek(0, SeekOrigin.Begin); 34 | await responseBodyStream.CopyToAsync(bodyStream); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Migrations/20180328114302_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using AuthService.DbModels; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace AuthService.Migrations 12 | { 13 | [DbContext(typeof(IdentityContext))] 14 | [Migration("20180328114302_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011") 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("AuthService.DbModels.AppUser", b => 25 | { 26 | b.Property("id") 27 | .ValueGeneratedOnAdd(); 28 | 29 | b.Property("level"); 30 | 31 | b.Property("name_full"); 32 | 33 | b.Property("password") 34 | .IsRequired(); 35 | 36 | b.Property("username") 37 | .IsRequired(); 38 | 39 | b.HasKey("id"); 40 | 41 | b.ToTable("applicationusers","mymicsapp.Services.identityDb"); 42 | }); 43 | #pragma warning restore 612, 618 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Migrations/20180328114302_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace AuthService.Migrations 6 | { 7 | public partial class InitialCreate : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.EnsureSchema( 12 | name: "mymicsapp.Services.identityDb"); 13 | 14 | migrationBuilder.CreateTable( 15 | name: "applicationusers", 16 | schema: "mymicsapp.Services.identityDb", 17 | columns: table => new 18 | { 19 | id = table.Column(nullable: false), 20 | level = table.Column(nullable: false), 21 | name_full = table.Column(nullable: true), 22 | password = table.Column(nullable: false), 23 | username = table.Column(nullable: false) 24 | }, 25 | constraints: table => 26 | { 27 | table.PrimaryKey("PK_applicationusers", x => x.id); 28 | }); 29 | } 30 | 31 | protected override void Down(MigrationBuilder migrationBuilder) 32 | { 33 | migrationBuilder.DropTable( 34 | name: "applicationusers", 35 | schema: "mymicsapp.Services.identityDb"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Migrations/IdentityContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using AuthService.DbModels; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace AuthService.Migrations 12 | { 13 | [DbContext(typeof(IdentityContext))] 14 | partial class IdentityContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011") 21 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 22 | 23 | modelBuilder.Entity("AuthService.DbModels.AppUser", b => 24 | { 25 | b.Property("id") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("level"); 29 | 30 | b.Property("name_full"); 31 | 32 | b.Property("password") 33 | .IsRequired(); 34 | 35 | b.Property("username") 36 | .IsRequired(); 37 | 38 | b.HasKey("id"); 39 | 40 | b.ToTable("applicationusers","mymicsapp.Services.identityDb"); 41 | }); 42 | #pragma warning restore 612, 618 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Mime; 5 | using AuthService.DbModels; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.EntityFrameworkCore; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace AuthService 13 | { 14 | public class Program 15 | { 16 | public static void Main(string[] args) 17 | { 18 | BuildWebHost(args).MigrateDbContext((context, services) => 19 | { 20 | var loggerFactory = services.GetRequiredService(); 21 | var logger = loggerFactory.CreateLogger("BuildWebHost"); 22 | 23 | try 24 | { 25 | new UserDbContextSeed().SeedAsync(context, loggerFactory).Wait(); 26 | } 27 | catch (Exception exception) 28 | { 29 | logger.LogError(exception, "An error occurred seeding the DB."); 30 | } 31 | }).Run(); 32 | } 33 | 34 | public static IWebHost BuildWebHost(string[] args) => 35 | WebHost.CreateDefaultBuilder(args) 36 | .UseStartup() 37 | .UseContentRoot(Directory.GetCurrentDirectory()) 38 | .UseKestrel(options => 39 | { 40 | var certName = Environment.GetEnvironmentVariable("CERT_NAME"); 41 | var certPwdName = Environment.GetEnvironmentVariable("CERT_PWD_NAME"); 42 | 43 | var certPath = File.Exists(certName) 44 | ? certName 45 | : "/run/secrets/cert"; 46 | 47 | var certPwdPath = File.Exists(certPwdName) 48 | ? certPwdName 49 | : "/run/secrets/cert_pwd"; 50 | 51 | options.Listen(IPAddress.Any, 443, listenOptions => 52 | { 53 | listenOptions.UseHttps(certPath, File.ReadAllText(certPwdPath)); 54 | }); 55 | }).Build(); 56 | } 57 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52146/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "AuthService": { 19 | "commandName": "Project" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Repository/Users/IUserRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using System.Threading.Tasks; 3 | 4 | namespace AuthService.Repository.Users 5 | { 6 | public interface IUserRepository 7 | { 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Repository/Users/UserRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using System.Security.Cryptography; 3 | using System.Security.Principal; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using AuthService.DbModels; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | namespace AuthService.Repository.Users 10 | { 11 | public class UserRepository : IUserRepository 12 | { 13 | private readonly IdentityContext _identityContext; 14 | 15 | public UserRepository(IdentityContext context) 16 | { 17 | _identityContext = context; 18 | } 19 | 20 | /// 21 | /// Check the user credentials 22 | /// 23 | /// 24 | /// 25 | /// User claims 26 | public static async Task GetIdentityAsync(IdentityContext context, string username, string password) 27 | { 28 | ClaimsIdentity cIdentity = null; 29 | { 30 | var user = await context.ApplicationUsers.FirstOrDefaultAsync(i => i.username == username); 31 | 32 | if (user != null) 33 | { 34 | using (var md5Hash = MD5.Create()) 35 | { 36 | var hash = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(password)); 37 | var sBuilder = new StringBuilder(); 38 | 39 | foreach (var t in hash) 40 | { 41 | sBuilder.Append(t.ToString("x2")); 42 | } 43 | 44 | if (user.password.Equals(sBuilder.ToString())) 45 | { 46 | cIdentity = new ClaimsIdentity( 47 | new GenericIdentity(username, "Token"), 48 | new Claim[] 49 | { 50 | new Claim("isAdmin", user.level == 0 ? "true" : "false"), 51 | new Claim("userId", user.id), 52 | new Claim("username", user.username) 53 | }); 54 | } 55 | } 56 | } 57 | 58 | return cIdentity; 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Security/ITokenMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AuthService.DbModels; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace AuthService.Security 6 | { 7 | public interface ITokenMiddleware 8 | { 9 | Task Invoke(HttpContext httpContext, IdentityContext context); 10 | } 11 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Security/TokenMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AuthService.DbModels; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Options; 6 | using Newtonsoft.Json; 7 | 8 | namespace AuthService.Security 9 | { 10 | /// 11 | /// Abstract class Token Middleware - Setting up the middleware 12 | /// 13 | public abstract class TokenMiddleware : ITokenMiddleware 14 | { 15 | /// 16 | /// Delegate to the next middleware 17 | /// 18 | protected readonly RequestDelegate Next; 19 | /// 20 | /// Token options 21 | /// 22 | protected readonly TokenOptions Options; 23 | /// 24 | /// Json serialization settings 25 | /// 26 | protected readonly JsonSerializerSettings SerializerSettings; 27 | 28 | /// 29 | /// Base ctor 30 | /// 31 | /// next middleware delegate 32 | /// Token options 33 | protected TokenMiddleware(RequestDelegate next, IOptions options) 34 | { 35 | Next = next; 36 | Options = options.Value; 37 | SerializerSettings = new JsonSerializerSettings 38 | { 39 | Formatting = Formatting.Indented 40 | }; 41 | } 42 | 43 | /// 44 | /// Analyze a request - Return a new access token or delegate the request to the next middleware 45 | /// 46 | /// Http context 47 | /// Db context 48 | /// Return the next task 49 | public virtual Task Invoke(HttpContext httpContext, IdentityContext context) 50 | { 51 | if (!httpContext.Request.Path.Equals(Options.Path, StringComparison.Ordinal)) 52 | { 53 | return Next(httpContext); 54 | } 55 | 56 | if (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType) 57 | { 58 | httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; 59 | return httpContext.Response.WriteAsync("Bad request."); 60 | } 61 | 62 | return GenerateTokenAsync(httpContext, context); 63 | } 64 | 65 | /// 66 | /// Implementation specific 67 | /// 68 | /// Http context 69 | /// Db context 70 | /// Task 71 | protected abstract Task GenerateTokenAsync(HttpContext httpContext, IdentityContext context); 72 | } 73 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Security/TokenOptions.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | 4 | namespace AuthService.Security 5 | { 6 | public class TokenOptions 7 | { 8 | /// 9 | /// The relative request path to listen on. 10 | /// 11 | /// The default path is /token. 12 | public string Path { get; set; } 13 | 14 | /// 15 | /// The expiration time for the generated tokens. 16 | /// 17 | /// The default is 1 week. 18 | public TimeSpan Expiration { get; set; } = TimeSpan.FromDays(7); 19 | } 20 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Security/TokenProviderOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | using AuthService.DbModels; 5 | using Microsoft.IdentityModel.Tokens; 6 | 7 | namespace AuthService.Security 8 | { 9 | public class TokenProviderOptions : TokenOptions 10 | { 11 | /// 12 | /// The Issuer (iss) claim for generated tokens. 13 | /// 14 | public string Issuer { get; set; } 15 | 16 | /// 17 | /// The Audience (aud) claim for the generated tokens. 18 | /// 19 | public string Audience { get; set; } 20 | 21 | /// 22 | /// The signing key to use when generating tokens. 23 | /// 24 | public SigningCredentials SigningCredentials { get; set; } 25 | 26 | /// 27 | /// Generates a random value (nonce) for each generated token. 28 | /// 29 | /// The default nonce is a random GUID. 30 | public Func> NonceGenerator { get; set; } 31 | = () => Task.FromResult(Guid.NewGuid().ToString()); 32 | 33 | /// 34 | /// Resolves a user identity given a username and password. 35 | /// 36 | public Func> IdentityResolver { get; set; } 37 | } 38 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Security/TokenRenewOptions.cs: -------------------------------------------------------------------------------- 1 | namespace AuthService.Security 2 | { 3 | public class TokenRenewOptions : TokenOptions 4 | { } 5 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/Startup.Auth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography.X509Certificates; 4 | using AuthService.Repository.Users; 5 | using AuthService.Security; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Options; 9 | using Microsoft.IdentityModel.Tokens; 10 | 11 | namespace AuthService 12 | { 13 | public partial class Startup 14 | { 15 | private void ConfigureTokenProviderMiddleware(IApplicationBuilder app, IServiceProvider services) 16 | { 17 | var userRepository = services.GetRequiredService(); 18 | 19 | var certName = Environment.GetEnvironmentVariable("CERT_NAME"); 20 | var certPwdName = Environment.GetEnvironmentVariable("CERT_PWD_NAME"); 21 | 22 | var certPath = File.Exists(certName) 23 | ? certName 24 | : "/run/secrets/cert"; 25 | 26 | var certPwdPath = File.Exists(certPwdName) 27 | ? certPwdName 28 | : "/run/secrets/cert_pwd"; 29 | 30 | var prvtKeyPassphrase = File.ReadAllText(certPwdPath); 31 | var privateKey = new X509Certificate2(certPath, prvtKeyPassphrase).GetRSAPrivateKey(); 32 | 33 | // Setup Token provider 34 | var tokenProviderOptions = new TokenProviderOptions() 35 | { 36 | Path = "/api/token", 37 | Audience = Configuration.GetSection("TokenAuthentication:Audience").Value, 38 | Issuer = Configuration.GetSection("TokenAuthentication:Issuer").Value, 39 | SigningCredentials = new SigningCredentials(new RsaSecurityKey(privateKey), SecurityAlgorithms.RsaSha256), 40 | IdentityResolver = UserRepository.GetIdentityAsync 41 | }; 42 | app.UseMiddleware(Options.Create(tokenProviderOptions)); 43 | 44 | // Setup Token renew options 45 | var tokenRenewOptions = new TokenRenewOptions() 46 | { 47 | Path = "/api/tokenrenew" 48 | }; 49 | app.UseMiddleware(Options.Create(tokenRenewOptions)); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=192.168.104.100; Port=3306; Database=mymicsapp.Services.identityDb; Uid=sandhaka; Password=pwd" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "LogLevel": { 8 | "Default": "Trace", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | }, 13 | "TokenAuthentication": { 14 | "Issuer": "DemoIssuer", 15 | "Audience": "DemoAudience" 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /dockerstack-application/Services/AuthService/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=192.168.104.100; Port=3306; Database=mymicsapp.Services.identityDb; Uid=sandhaka; Password=pwd" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "LogLevel": { 8 | "Default": "Debug", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | }, 13 | "TokenAuthentication": { 14 | "Issuer": "DemoIssuer", 15 | "Audience": "DemoAudience" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | keys/ 4 | certificate/ 5 | aws.dev/ -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Basket.Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | aws.dev\%(RecursiveDir)%(Filename)%(Extension) 27 | PreserveNewest 28 | 29 | 30 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Controllers/BasketController.cs: -------------------------------------------------------------------------------- 1 | using Basket.Application.Infrastructure.Repositories; 2 | using Basket.Application.Models; 3 | using Basket.Application.Services; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Basket.Application.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [Authorize] 11 | public class BasketController : Controller 12 | { 13 | private readonly IBasketRepository _basketRepository; 14 | private readonly IIdentityService _identityService; 15 | 16 | public BasketController(IBasketRepository basketRepository, 17 | IIdentityService identityService) 18 | { 19 | _basketRepository = basketRepository; 20 | _identityService = identityService; 21 | } 22 | 23 | [HttpGet] 24 | public IActionResult Get() 25 | { 26 | return Ok(_basketRepository.GetBasketAsync(_identityService.GetUserIdentity())); 27 | } 28 | 29 | [HttpDelete] 30 | public IActionResult Delete() 31 | { 32 | return Ok(_basketRepository.DeleteBasketAsync(_identityService.GetUserIdentity())); 33 | } 34 | 35 | [HttpPost] 36 | public IActionResult Post([FromBody] CustomerBasket customerBasket) 37 | { 38 | var basket = _basketRepository.UpdateBasketAsync(customerBasket); 39 | return Ok(basket); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:2.0.3 2 | 3 | # Copy application files 4 | WORKDIR /app 5 | COPY bin/Debug/netcoreapp2.0/publish /app 6 | 7 | EXPOSE 80 8 | 9 | ENTRYPOINT /bin/bash -c "dotnet Basket.Application.dll" -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Dockerfile.debug: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:1.1 2 | 3 | # Remote debugger 4 | RUN apt-get update \ 5 | && apt-get install unzip -y \ 6 | && apt-get install -y openssh-server \ 7 | && curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg 8 | 9 | # Enable root login 10 | RUN mkdir /var/run/sshd 11 | RUN echo 'root:password' | chpasswd 12 | RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config 13 | 14 | # SSH login fix. Otherwise user is kicked off after login 15 | RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 16 | 17 | ENV NOTVISIBLE "in users profile" 18 | RUN echo "export VISIBLE=now" >> /etc/profile 19 | 20 | # Copy application files 21 | WORKDIR /app 22 | COPY bin/Debug/netcoreapp1.1/publish /app 23 | 24 | EXPOSE 5000 22 25 | 26 | ENTRYPOINT /bin/bash -c "dotnet Basket.Application.dll" -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Filters/HttpGlobalExceptionHandlingFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Filters; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Basket.Application.Filters 5 | { 6 | public class HttpGlobalExceptionHandlingFilter : IExceptionFilter 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public HttpGlobalExceptionHandlingFilter(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnException(ExceptionContext context) 16 | { 17 | _logger.LogError( 18 | new EventId(context.Exception.HResult), 19 | context.Exception, 20 | context.Exception.Message 21 | ); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Filters/RequestLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Basket.Application.Filters 8 | { 9 | public class RequestLoggingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ILogger _logger; 13 | 14 | public RequestLoggingMiddleware(RequestDelegate next, ILogger logger) 15 | { 16 | _next = next; 17 | _logger = logger; 18 | } 19 | 20 | public async Task Invoke(HttpContext context) 21 | { 22 | var injectedRequestStream = new MemoryStream(); 23 | 24 | try 25 | { 26 | var requestLog = $"REQUEST HttpMethod: {context.Request.Method}, Path: {context.Request.Path}"; 27 | 28 | using (var bodyReader = new StreamReader(context.Request.Body)) 29 | { 30 | var bodyAsText = bodyReader.ReadToEnd(); 31 | if (string.IsNullOrWhiteSpace(bodyAsText) == false) 32 | { 33 | requestLog += $", Body : {bodyAsText}"; 34 | } 35 | 36 | var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText); 37 | injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length); 38 | injectedRequestStream.Seek(0, SeekOrigin.Begin); 39 | context.Request.Body = injectedRequestStream; 40 | } 41 | 42 | _logger.LogTrace(requestLog); 43 | 44 | await _next.Invoke(context); 45 | } 46 | finally 47 | { 48 | injectedRequestStream.Dispose(); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Filters/ResponseLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Basket.Application.Filters 8 | { 9 | public class ResponseLoggingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ILogger _logger; 13 | private readonly Func _defaultFormatter = (state, exception) => state; 14 | 15 | public ResponseLoggingMiddleware(RequestDelegate next, ILogger logger) 16 | { 17 | _next = next; 18 | _logger = logger; 19 | } 20 | 21 | public async Task Invoke(HttpContext context) 22 | { 23 | var bodyStream = context.Response.Body; 24 | 25 | var responseBodyStream = new MemoryStream(); 26 | context.Response.Body = responseBodyStream; 27 | 28 | await _next(context); 29 | 30 | responseBodyStream.Seek(0, SeekOrigin.Begin); 31 | var responseBody = new StreamReader(responseBodyStream).ReadToEnd(); 32 | _logger.Log(LogLevel.Trace, 1, $"RESPONSE LOG: {responseBody}", null, _defaultFormatter); 33 | responseBodyStream.Seek(0, SeekOrigin.Begin); 34 | await responseBodyStream.CopyToAsync(bodyStream); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Infrastructure/Repositories/BasketRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Basket.Application.Models; 3 | using Basket.Application.Services; 4 | using Microsoft.Extensions.Logging; 5 | using Newtonsoft.Json; 6 | using StackExchange.Redis; 7 | 8 | namespace Basket.Application.Infrastructure.Repositories 9 | { 10 | public class BasketRepository : IBasketRepository 11 | { 12 | private readonly ConnectionMultiplexer _redisConnection; 13 | private ILogger _logger; 14 | private readonly IIdentityService _identityService; 15 | 16 | public BasketRepository(ILoggerFactory logger, ConnectionMultiplexer connectionMultiplexer, IIdentityService identityService) 17 | { 18 | _logger = logger.CreateLogger(); 19 | _redisConnection = connectionMultiplexer; 20 | _identityService = identityService; 21 | } 22 | 23 | public async Task GetBasketAsync(string customerId) 24 | { 25 | var database = _redisConnection.GetDatabase(); 26 | var data = await database.StringGetAsync(customerId); 27 | if (data.IsNullOrEmpty) 28 | { 29 | return null; 30 | } 31 | 32 | return JsonConvert.DeserializeObject(data); 33 | } 34 | 35 | public async Task UpdateBasketAsync(CustomerBasket basket) 36 | { 37 | var database = _redisConnection.GetDatabase(); 38 | var created = await database.StringSetAsync(_identityService.GetUserIdentity(), JsonConvert.SerializeObject(basket)); 39 | if (!created) 40 | { 41 | _logger.LogWarning("Problem occur on basket set"); 42 | return null; 43 | } 44 | 45 | return await GetBasketAsync(_identityService.GetUserIdentity()); 46 | } 47 | 48 | public async Task DeleteBasketAsync(string id) 49 | { 50 | var database = _redisConnection.GetDatabase(); 51 | return await database.KeyDeleteAsync(id); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Infrastructure/Repositories/IBasketRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Basket.Application.Models; 3 | 4 | namespace Basket.Application.Infrastructure.Repositories 5 | { 6 | public interface IBasketRepository 7 | { 8 | Task GetBasketAsync(string customerId); 9 | Task UpdateBasketAsync(CustomerBasket basket); 10 | Task DeleteBasketAsync(string id); 11 | } 12 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/IntegrationEvents/DeleteBasketOnOrderStartedIntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Basket.Application.Infrastructure.Repositories; 3 | using Basket.Application.Services; 4 | using EventBus.Abstractions; 5 | using EventBusAwsSns.Shared.IntegrationEvents; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Basket.Application.IntegrationEvents 9 | { 10 | public class DeleteBasketOnOrderStartedIntegrationEventHandler : IIntegrationEventHandler 11 | { 12 | private readonly ILogger _logger; 13 | private readonly IBasketRepository _basketRepository; 14 | private readonly IIdentityService _identityService; 15 | 16 | public DeleteBasketOnOrderStartedIntegrationEventHandler( 17 | ILogger logger, 18 | IBasketRepository basketRepository, 19 | IIdentityService identityService) 20 | { 21 | _basketRepository = basketRepository; 22 | _logger = logger; 23 | _identityService = identityService; 24 | } 25 | 26 | public Task Handle(OrderStartedIntegrationEvent @event) 27 | { 28 | _logger.LogInformation($"Received integration event Order started, " + 29 | $"Creation date: {@event.CreationDate}, " + 30 | "Delete basket"); 31 | 32 | return _basketRepository.DeleteBasketAsync(@event.UserId); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Models/BasketItem.cs: -------------------------------------------------------------------------------- 1 | namespace Basket.Application.Models 2 | { 3 | public class BasketItem 4 | { 5 | public string ProductId { get; set; } 6 | public string ProductName { get; set; } 7 | public decimal UnitPrice { get; set; } 8 | public int Units { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Models/CustomerBasket.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Basket.Application.Models 4 | { 5 | public class CustomerBasket 6 | { 7 | public List BasketItems { get; set; } 8 | 9 | protected CustomerBasket() 10 | { 11 | BasketItems = new List(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net; 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | namespace Basket.Application 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | BuildWehHost(args).Run(); 13 | } 14 | 15 | private static IWebHost BuildWehHost(string[] args) => WebHost.CreateDefaultBuilder(args) 16 | .UseKestrel(options => 17 | { 18 | options.Listen(IPAddress.Any, 80); 19 | }) 20 | .UseContentRoot(Directory.GetCurrentDirectory()) 21 | .UseIISIntegration() 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Services/IIdentityService.cs: -------------------------------------------------------------------------------- 1 | namespace Basket.Application.Services 2 | { 3 | public interface IIdentityService 4 | { 5 | string GetUserIdentity(); 6 | } 7 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/Services/IdentityService.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Basket.Application.Services 6 | { 7 | public class IdentityService : IIdentityService 8 | { 9 | private readonly IHttpContextAccessor _context; 10 | 11 | public IdentityService(IHttpContextAccessor context) 12 | { 13 | _context = context; 14 | } 15 | 16 | public string GetUserIdentity() 17 | { 18 | var token = _context.HttpContext.User.FindFirst("access_token")?.Value; 19 | 20 | if (string.IsNullOrEmpty(token)) 21 | return null; 22 | 23 | var tokenData = new JwtSecurityTokenHandler().ReadJwtToken(token); 24 | 25 | return tokenData.Claims.FirstOrDefault(i => i.Type.Equals("userId"))?.Value; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "Redis": "192.168.104.100" 4 | }, 5 | "AWS": { 6 | "Profile": "aws.dev-profile", 7 | "Region": "eu-west-1", 8 | "ProfilesLocation": "./aws.dev/credentials" 9 | }, 10 | "Logging": { 11 | "IncludeScopes": false, 12 | "LogLevel": { 13 | "Default": "Trace", 14 | "System": "Information", 15 | "Microsoft": "Information" 16 | } 17 | }, 18 | "TokenAuthentication": { 19 | "Issuer": "DemoIssuer", 20 | "Audience": "DemoAudience" 21 | }, 22 | "AwsEventBus" : { 23 | "Topics": { 24 | "OrderStartedIntegrationEvent": { 25 | "arn": "arn:aws:sns:eu-west-1:069637010413:orderStarted", 26 | "SqsUrl": "https://sqs.eu-west-1.amazonaws.com/069637010413/started-orders" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Basket/Basket.Application/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "Redis": "192.168.104.100" 4 | }, 5 | "AWS": { 6 | "Profile": "aws.dev-profile", 7 | "Region": "eu-west-1", 8 | "ProfilesLocation": "./aws.dev/credentials" 9 | }, 10 | "Logging": { 11 | "IncludeScopes": false, 12 | "LogLevel": { 13 | "Default": "Trace", 14 | "System": "Information", 15 | "Microsoft": "Information" 16 | } 17 | }, 18 | "TokenAuthentication": { 19 | "Issuer": "DemoIssuer", 20 | "Audience": "DemoAudience" 21 | }, 22 | "AwsEventBus" : { 23 | "Topics": { 24 | "OrderStartedIntegrationEvent": { 25 | "arn": "arn:aws:sns:eu-west-1:069637010413:orderStarted", 26 | "SqsUrl": "https://sqs.eu-west-1.amazonaws.com/069637010413/started-orders" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | keys/ 4 | certificate/ 5 | aws.dev/ -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Catalog.Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | aws.dev\%(RecursiveDir)%(Filename)%(Extension) 32 | PreserveNewest 33 | 34 | 35 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Controllers/ProductsController.cs: -------------------------------------------------------------------------------- 1 | using Catalog.Application.Infrastructure.Repositories; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Catalog.Application.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [Authorize] 9 | public class ProductsController : Controller 10 | { 11 | private readonly ICatalogRepository _catalogRepository; 12 | 13 | public ProductsController(ICatalogRepository catalogRepository) 14 | { 15 | _catalogRepository = catalogRepository; 16 | } 17 | 18 | [HttpGet] 19 | public IActionResult Get() 20 | { 21 | return Ok(_catalogRepository.GetAllProducts()); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/DbModels/Product.cs: -------------------------------------------------------------------------------- 1 | namespace Catalog.Application.DbModels 2 | { 3 | public class Product 4 | { 5 | public int Id { get; set; } 6 | public string ProductName { get; set; } 7 | public decimal UnitPrice { get; set; } 8 | public string Package { get; set; } 9 | public int Assets { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:2.0.3 2 | 3 | # Copy application files 4 | WORKDIR /app 5 | COPY bin/Debug/netcoreapp2.0/publish /app 6 | 7 | EXPOSE 80 8 | 9 | ENTRYPOINT /bin/bash -c "dotnet Catalog.Application.dll" -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Filters/HttpGlobalExceptionHandlingFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Filters; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Catalog.Application.Filters 5 | { 6 | public class HttpGlobalExceptionHandlingFilter : IExceptionFilter 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public HttpGlobalExceptionHandlingFilter(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnException(ExceptionContext context) 16 | { 17 | _logger.LogError( 18 | new EventId(context.Exception.HResult), 19 | context.Exception, 20 | context.Exception.Message 21 | ); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Filters/RequestLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Catalog.Application.Filters 8 | { 9 | public class RequestLoggingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ILogger _logger; 13 | 14 | public RequestLoggingMiddleware(RequestDelegate next, ILogger logger) 15 | { 16 | _next = next; 17 | _logger = logger; 18 | } 19 | 20 | public async Task Invoke(HttpContext context) 21 | { 22 | var injectedRequestStream = new MemoryStream(); 23 | 24 | try 25 | { 26 | var requestLog = $"REQUEST HttpMethod: {context.Request.Method}, Path: {context.Request.Path}"; 27 | 28 | using (var bodyReader = new StreamReader(context.Request.Body)) 29 | { 30 | var bodyAsText = bodyReader.ReadToEnd(); 31 | if (string.IsNullOrWhiteSpace(bodyAsText) == false) 32 | { 33 | requestLog += $", Body : {bodyAsText}"; 34 | } 35 | 36 | var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText); 37 | injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length); 38 | injectedRequestStream.Seek(0, SeekOrigin.Begin); 39 | context.Request.Body = injectedRequestStream; 40 | } 41 | 42 | _logger.LogTrace(requestLog); 43 | 44 | await _next.Invoke(context); 45 | } 46 | finally 47 | { 48 | injectedRequestStream.Dispose(); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Filters/ResponseLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Catalog.Application.Filters 8 | { 9 | public class ResponseLoggingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ILogger _logger; 13 | private readonly Func _defaultFormatter = (state, exception) => state; 14 | 15 | public ResponseLoggingMiddleware(RequestDelegate next, ILogger logger) 16 | { 17 | _next = next; 18 | _logger = logger; 19 | } 20 | 21 | public async Task Invoke(HttpContext context) 22 | { 23 | var bodyStream = context.Response.Body; 24 | 25 | var responseBodyStream = new MemoryStream(); 26 | context.Response.Body = responseBodyStream; 27 | 28 | await _next(context); 29 | 30 | responseBodyStream.Seek(0, SeekOrigin.Begin); 31 | var responseBody = new StreamReader(responseBodyStream).ReadToEnd(); 32 | _logger.Log(LogLevel.Trace, 1, $"RESPONSE LOG: {responseBody}", null, _defaultFormatter); 33 | responseBodyStream.Seek(0, SeekOrigin.Begin); 34 | await responseBodyStream.CopyToAsync(bodyStream); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Infrastructure/CatalogContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Catalog.Application.DbModels; 4 | 5 | namespace Catalog.Application.Infrastructure 6 | { 7 | public class CatalogContext : DbContext 8 | { 9 | private const string DefaultSchema = "mymicsapp.Services.catalogDb"; 10 | 11 | public CatalogContext(DbContextOptions options) : base(options) {} 12 | 13 | public DbSet Products { get; set; } 14 | 15 | protected override void OnModelCreating(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder.Entity(ConfigureProducts); 18 | } 19 | 20 | private void ConfigureProducts(EntityTypeBuilder productConfiguration) 21 | { 22 | productConfiguration.ToTable("products", DefaultSchema); 23 | productConfiguration.HasKey(cr => cr.Id); 24 | productConfiguration.Property("ProductName").IsRequired(); 25 | productConfiguration.Property("UnitPrice").IsRequired(); 26 | productConfiguration.Property("Package").IsRequired(); 27 | productConfiguration.Property("Assets").IsRequired(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Infrastructure/CatalogContextSeed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Catalog.Application.DbModels; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Catalog.Application.Infrastructure 9 | { 10 | public class CatalogContextSeed 11 | { 12 | public async Task SeedAsync(CatalogContext context, ILoggerFactory loggerFactory) 13 | { 14 | try 15 | { 16 | using (context) 17 | { 18 | if (!context.Products.Any()) 19 | { 20 | foreach (var product in GetDefaultProducts()) 21 | { 22 | context.Products.Add(product); 23 | } 24 | 25 | await context.SaveChangesAsync(); 26 | } 27 | } 28 | } 29 | catch (Exception exception) 30 | { 31 | var log = loggerFactory.CreateLogger("catalog seed"); 32 | log.LogError(exception.Message); 33 | } 34 | } 35 | 36 | private List GetDefaultProducts() 37 | { 38 | return new List() 39 | { 40 | new Product() {ProductName = "Chai", Assets = 10, Package = "10 Boxes x 20 bags", UnitPrice = 19 }, 41 | new Product() {ProductName = "Chang", Assets = 10, Package = "24 - 12 oz Bottles", UnitPrice = 18 }, 42 | new Product() {ProductName = "Aniseed Syrup", Assets = 10, Package = "12 - 550 ml bottles", UnitPrice = 19 } 43 | }; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Infrastructure/Repositories/CatalogRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Catalog.Application.DbModels; 4 | using EventBusAwsSns.Shared.IntegrationEvents; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Catalog.Application.Infrastructure.Repositories 8 | { 9 | public class CatalogRepository : ICatalogRepository 10 | { 11 | private readonly CatalogContext _context; 12 | private readonly ILogger _logger; 13 | 14 | public CatalogRepository(CatalogContext context, ILogger logger) 15 | { 16 | _logger = logger; 17 | _context = context; 18 | } 19 | 20 | public IEnumerable GetAllProducts() 21 | { 22 | return _context.Products; 23 | } 24 | 25 | public Task UpdateProductsAssetsAsync(List orderItemInfo) 26 | { 27 | foreach (var productInfo in orderItemInfo) 28 | { 29 | var product = _context.Products.Find(productInfo.ProductId); 30 | 31 | if (product == null) 32 | { 33 | continue; 34 | } 35 | 36 | if (productInfo.Assets > product.Assets) 37 | { 38 | _logger.LogError($"A order item exceed the availability of the product, " + 39 | $"available: {product.Assets}, requested: {productInfo.Assets}"); 40 | continue; 41 | } 42 | 43 | product.Assets -= productInfo.Assets; 44 | } 45 | 46 | return _context.SaveChangesAsync(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Infrastructure/Repositories/ICatalogRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Catalog.Application.DbModels; 4 | using EventBusAwsSns.Shared.IntegrationEvents; 5 | 6 | namespace Catalog.Application.Infrastructure.Repositories 7 | { 8 | public interface ICatalogRepository 9 | { 10 | IEnumerable GetAllProducts(); 11 | Task UpdateProductsAssetsAsync(List orderItemInfo); 12 | } 13 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Migrations/20180327201902_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Catalog.Application.Infrastructure; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace Catalog.Application.Migrations 12 | { 13 | [DbContext(typeof(CatalogContext))] 14 | [Migration("20180327201902_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011") 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("Catalog.Application.DbModels.Product", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd(); 28 | 29 | b.Property("Assets"); 30 | 31 | b.Property("Package") 32 | .IsRequired(); 33 | 34 | b.Property("ProductName") 35 | .IsRequired(); 36 | 37 | b.Property("UnitPrice"); 38 | 39 | b.HasKey("Id"); 40 | 41 | b.ToTable("products","mymicsapp.Services.catalogDb"); 42 | }); 43 | #pragma warning restore 612, 618 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Migrations/20180327201902_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Catalog.Application.Migrations 7 | { 8 | public partial class InitialCreate : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.EnsureSchema( 13 | name: "mymicsapp.Services.catalogDb"); 14 | 15 | migrationBuilder.CreateTable( 16 | name: "products", 17 | schema: "mymicsapp.Services.catalogDb", 18 | columns: table => new 19 | { 20 | Id = table.Column(nullable: false) 21 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 22 | Assets = table.Column(nullable: false), 23 | Package = table.Column(nullable: false), 24 | ProductName = table.Column(nullable: false), 25 | UnitPrice = table.Column(nullable: false) 26 | }, 27 | constraints: table => 28 | { 29 | table.PrimaryKey("PK_products", x => x.Id); 30 | }); 31 | } 32 | 33 | protected override void Down(MigrationBuilder migrationBuilder) 34 | { 35 | migrationBuilder.DropTable( 36 | name: "products", 37 | schema: "mymicsapp.Services.catalogDb"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Migrations/CatalogContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Catalog.Application.Infrastructure; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace Catalog.Application.Migrations 12 | { 13 | [DbContext(typeof(CatalogContext))] 14 | partial class CatalogContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011") 21 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 22 | 23 | modelBuilder.Entity("Catalog.Application.DbModels.Product", b => 24 | { 25 | b.Property("Id") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("Assets"); 29 | 30 | b.Property("Package") 31 | .IsRequired(); 32 | 33 | b.Property("ProductName") 34 | .IsRequired(); 35 | 36 | b.Property("UnitPrice"); 37 | 38 | b.HasKey("Id"); 39 | 40 | b.ToTable("products","mymicsapp.Services.catalogDb"); 41 | }); 42 | #pragma warning restore 612, 618 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using Catalog.Application.Infrastructure; 5 | using Microsoft.AspNetCore; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace Catalog.Application 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | BuildWebHost(args).MigrateDbContext((context, services) => 17 | { 18 | var loggerFactory = services.GetRequiredService(); 19 | var logger = loggerFactory.CreateLogger("BuildWebHost"); 20 | 21 | try 22 | { 23 | new CatalogContextSeed().SeedAsync(context, loggerFactory).Wait(); 24 | } 25 | catch (Exception exception) 26 | { 27 | logger.LogError(exception, "An error occurred seeding the DB."); 28 | } 29 | }).Run(); 30 | } 31 | 32 | public static IWebHost BuildWebHost(string[] args) => 33 | WebHost.CreateDefaultBuilder(args) 34 | .UseKestrel(options => 35 | { 36 | options.Listen(IPAddress.Any, 80); 37 | }) 38 | .UseContentRoot(Directory.GetCurrentDirectory()) 39 | .UseIISIntegration() 40 | .UseStartup() 41 | .Build(); 42 | } 43 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=192.168.104.100; Port=3306; Database=mymicsapp.Services.catalogDb; Uid=sandhaka; Password=pwd", 4 | "Redis": "192.168.104.100" 5 | }, 6 | "AWS": { 7 | "Profile": "aws.dev-profile", 8 | "Region": "eu-west-1", 9 | "ProfilesLocation": "./aws.dev/credentials" 10 | }, 11 | "Logging": { 12 | "IncludeScopes": false, 13 | "LogLevel": { 14 | "Default": "Trace", 15 | "System": "Information", 16 | "Microsoft": "Information" 17 | } 18 | }, 19 | "TokenAuthentication": { 20 | "Issuer": "DemoIssuer", 21 | "Audience": "DemoAudience" 22 | }, 23 | "AwsEventBus" : { 24 | "Topics": { 25 | "OrderStartedIntegrationEvent": { 26 | "arn": "arn:aws:sns:eu-west-1:069637010413:orderStarted", 27 | "SqsUrl": "https://sqs.eu-west-1.amazonaws.com/069637010413/started-orders" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Catalog/Catalog.Application/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=192.168.104.100; Port=3306; Database=mymicsapp.Services.catalogDb; Uid=sandhaka; Password=pwd", 4 | "Redis": "192.168.104.100" 5 | }, 6 | "AWS": { 7 | "Profile": "aws.dev-profile", 8 | "Region": "eu-west-1", 9 | "ProfilesLocation": "./aws.dev/credentials" 10 | }, 11 | "Logging": { 12 | "IncludeScopes": false, 13 | "LogLevel": { 14 | "Default": "Warning" 15 | } 16 | }, 17 | "TokenAuthentication": { 18 | "Issuer": "DemoIssuer", 19 | "Audience": "DemoAudience" 20 | }, 21 | "AwsEventBus" : { 22 | "Topics": { 23 | "OrderStartedIntegrationEvent": { 24 | "arn": "arn:aws:sns:eu-west-1:069637010413:orderStarted", 25 | "SqsUrl": "https://sqs.eu-west-1.amazonaws.com/069637010413/started-orders" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Gateway/Api.Gateway/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | certificate/ -------------------------------------------------------------------------------- /dockerstack-application/Services/Gateway/Api.Gateway/Api.Gateway.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Gateway/Api.Gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:2.0.3 2 | 3 | # Copy application files 4 | WORKDIR /app 5 | COPY bin/Debug/netcoreapp2.0/publish /app 6 | 7 | EXPOSE 443 8 | 9 | ENTRYPOINT /bin/bash -c "dotnet Api.Gateway.dll" -------------------------------------------------------------------------------- /dockerstack-application/Services/Gateway/Api.Gateway/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | 7 | namespace Api.Gateway 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) 12 | { 13 | BuildWebHost(args).Run(); 14 | } 15 | 16 | public static IWebHost BuildWebHost(string[] args) => 17 | WebHost.CreateDefaultBuilder(args) 18 | .UseKestrel(options => 19 | { 20 | var certName = Environment.GetEnvironmentVariable("CERT_NAME"); 21 | var certPwdName = Environment.GetEnvironmentVariable("CERT_PWD_NAME"); 22 | 23 | var certPath = File.Exists(certName) 24 | ? certName 25 | : "/run/secrets/cert"; 26 | 27 | var certPwdPath = File.Exists(certPwdName) 28 | ? certPwdName 29 | : $"/run/secrets/cert_pwd"; 30 | 31 | options.Listen(IPAddress.Any, 443, listenOptions => 32 | { 33 | listenOptions.UseHttps(certPath, File.ReadAllText(certPwdPath)); 34 | }); 35 | }) 36 | .UseContentRoot(Directory.GetCurrentDirectory()) 37 | .UseIISIntegration() 38 | .UseUrls("https://*:443") 39 | .UseStartup() 40 | .Build(); 41 | } 42 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Gateway/Api.Gateway/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:36710", 7 | "sslPort": 44354 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development", 16 | "ASPNETCORE_HTTPS_PORT": "44354" 17 | } 18 | }, 19 | "Api.Gateway": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development", 24 | "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Gateway/Api.Gateway/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=192.168.104.100; Port=3306; Database=mymicsapp.Services.identityDb; Uid=sandhaka; Password=pwd" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "LogLevel": { 8 | "Default": "Trace", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | }, 13 | "TokenAuthentication": { 14 | "Issuer": "DemoIssuer", 15 | "Audience": "DemoAudience" 16 | }, 17 | "ReRoutes": [ 18 | { 19 | "DownstreamPathTemplate": "/api/orders/{command}", 20 | "DownstreamScheme": "http", 21 | "DownstreamHostAndPorts": [ 22 | { 23 | "Host": "orders", 24 | "Port": 80 25 | } 26 | ], 27 | "UpstreamPathTemplate": "/api/orders/{command}", 28 | "UpstreamHttpMethod": [ "Post" ] 29 | }, 30 | { 31 | "DownstreamPathTemplate": "/api/basket", 32 | "DownstreamScheme": "http", 33 | "DownstreamHostAndPorts": [ 34 | { 35 | "Host": "basket", 36 | "Port": 80 37 | } 38 | ], 39 | "UpstreamPathTemplate": "/api/basket", 40 | "UpstreamHttpMethod": [ "Post", "Get", "Delete" ] 41 | }, 42 | { 43 | "DownstreamPathTemplate": "/api/products", 44 | "DownstreamScheme": "http", 45 | "DownstreamHostAndPorts": [ 46 | { 47 | "Host": "catalog", 48 | "Port": 80 49 | } 50 | ], 51 | "UpstreamPathTemplate": "/api/products", 52 | "UpstreamHttpMethod": [ "Get" ] 53 | } 54 | ], 55 | "GlobalConfiguration": { 56 | "BaseUrl": "https://192.168.104.102" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Gateway/Api.Gateway/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=192.168.104.100; Port=3306; Database=mymicsapp.Services.identityDb; Uid=sandhaka; Password=pwd" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "LogLevel": { 8 | "Default": "Debug", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | }, 13 | "TokenAuthentication": { 14 | "Issuer": "DemoIssuer", 15 | "Audience": "DemoAudience" 16 | }, 17 | "ReRoutes": [ 18 | { 19 | "DownstreamPathTemplate": "/api/orders/{command}", 20 | "DownstreamScheme": "http", 21 | "DownstreamHostAndPorts": [ 22 | { 23 | "Host": "orders", 24 | "Port": 80 25 | } 26 | ], 27 | "UpstreamPathTemplate": "/api/orders/{command}", 28 | "UpstreamHttpMethod": [ "Post" ] 29 | }, 30 | { 31 | "DownstreamPathTemplate": "/api/basket", 32 | "DownstreamScheme": "http", 33 | "DownstreamHostAndPorts": [ 34 | { 35 | "Host": "basket", 36 | "Port": 80 37 | } 38 | ], 39 | "UpstreamPathTemplate": "/api/basket", 40 | "UpstreamHttpMethod": [ "Post", "Get", "Delete" ] 41 | }, 42 | { 43 | "DownstreamPathTemplate": "/api/products", 44 | "DownstreamScheme": "http", 45 | "DownstreamHostAndPorts": [ 46 | { 47 | "Host": "catalog", 48 | "Port": 80 49 | } 50 | ], 51 | "UpstreamPathTemplate": "/api/products", 52 | "UpstreamHttpMethod": [ "Get" ] 53 | } 54 | ], 55 | "GlobalConfiguration": { 56 | "BaseUrl": "https://192.168.104.103" 57 | } 58 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | keys/ 4 | certificate/ 5 | aws.dev/ -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Commands/CreateOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using MediatR; 5 | using Orders.Application.Controllers; 6 | 7 | namespace Orders.Application.Commands 8 | { 9 | /// 10 | /// Command create order 11 | /// 12 | [DataContract] 13 | public class CreateOrderCommand : IRequest 14 | { 15 | [DataMember] 16 | private readonly List _orderItems; 17 | 18 | [DataMember] 19 | public List OrderItems => _orderItems; 20 | 21 | [DataMember] 22 | public int PaymentId { get; private set; } 23 | 24 | [DataMember] 25 | public int BuyerId { get; private set; } 26 | 27 | [DataMember] 28 | public string CardNumber { get; private set; } 29 | 30 | [DataMember] 31 | public string CardHolderName { get; private set; } 32 | 33 | [DataMember] 34 | public DateTime CardExpiration { get; private set; } 35 | 36 | [DataMember] 37 | public string CardSecurityNumber { get; private set; } 38 | 39 | public CreateOrderCommand() 40 | { 41 | _orderItems = new List(); 42 | } 43 | 44 | public CreateOrderCommand(List orderItems) 45 | { 46 | _orderItems = orderItems; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Commands/CreateOrderCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using EventBus.Abstractions; 4 | using MediatR; 5 | using Orders.Domain.AggregatesModel.OrderAggregate; 6 | 7 | namespace Orders.Application.Commands 8 | { 9 | /// 10 | /// Create a new order 11 | /// 12 | public class CreateOrderCommandHandler 13 | : IAsyncRequestHandler 14 | { 15 | private readonly IOrderRepository _orderRepository; 16 | private readonly IEventBus _eventBus; 17 | 18 | public CreateOrderCommandHandler(IOrderRepository orderRepository, IEventBus eventBus) 19 | { 20 | _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); 21 | _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); 22 | } 23 | 24 | /// 25 | /// Create the order and commit it into the persistence layer 26 | /// 27 | /// command 28 | /// result 29 | public async Task Handle(CreateOrderCommand command) 30 | { 31 | var order = new Order( 32 | command.CardNumber, 33 | command.CardSecurityNumber, 34 | command.CardHolderName, 35 | command.CardExpiration, 36 | command.BuyerId, 37 | command.PaymentId); 38 | 39 | // Add order items 40 | foreach (var orderItem in command.OrderItems) 41 | { 42 | order.AddOrderItem(orderItem.ProductId, orderItem.ProductName, orderItem.UnitPrice, orderItem.Units); 43 | } 44 | 45 | // Save the new order on the db 46 | _orderRepository.Add(order); 47 | 48 | var result = await _orderRepository.UnitOfWork.SaveEntitiesAsync(); 49 | 50 | return result; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Controllers/OrderItemDto.cs: -------------------------------------------------------------------------------- 1 | namespace Orders.Application.Controllers 2 | { 3 | public class OrderItemDto 4 | { 5 | public int ProductId { get; set; } 6 | 7 | public string ProductName { get; set; } 8 | 9 | public decimal UnitPrice { get; set; } 10 | 11 | public int Units { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Orders.Application.Commands; 7 | 8 | namespace Orders.Application.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | [Authorize] 12 | public class OrdersController : Controller 13 | { 14 | private readonly IMediator _mediator; 15 | 16 | public OrdersController(IMediator mediator) 17 | { 18 | _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); 19 | } 20 | 21 | /// 22 | /// Create a new order 23 | /// 24 | /// order dto 25 | /// Http response 26 | [Route("new")] 27 | [HttpPost] 28 | public async Task CreateOrderAsync([FromBody]CreateOrderCommand createOrderCommand) 29 | { 30 | bool commandResult = false; 31 | commandResult = await _mediator.Send(createOrderCommand); 32 | 33 | return commandResult ? Ok() : (IActionResult)BadRequest(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:2.0.3 2 | 3 | # Copy application files 4 | WORKDIR /app 5 | COPY bin/Debug/netcoreapp2.0/publish /app 6 | 7 | EXPOSE 80 8 | 9 | ENTRYPOINT /bin/bash -c "dotnet Orders.Application.dll" -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/DomainEventsHandler/UpdateOrderOnPaymentMethodVerifiedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using EventBusAwsSns.Shared.IntegrationEvents; 6 | using MediatR; 7 | using Microsoft.Extensions.Logging; 8 | using Orders.Application.Services; 9 | using Orders.Domain.AggregatesModel.Events; 10 | using Orders.Domain.AggregatesModel.OrderAggregate; 11 | 12 | namespace Orders.Application.DomainEventsHandler 13 | { 14 | /// 15 | /// Update order with the verified payment information 16 | /// 17 | public class UpdateOrderOnPaymentMethodVerifiedEventHandler : 18 | IAsyncNotificationHandler 19 | { 20 | private readonly IOrderRepository _orderRepository; 21 | private readonly ILoggerFactory _logger; 22 | private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; 23 | 24 | public UpdateOrderOnPaymentMethodVerifiedEventHandler( 25 | IOrderRepository orderRepository, 26 | IOrderingIntegrationEventService orderingIntegrationEventService, 27 | ILoggerFactory loggerFactory) 28 | { 29 | _logger = loggerFactory; 30 | _orderRepository = orderRepository; 31 | _orderingIntegrationEventService = orderingIntegrationEventService; 32 | } 33 | 34 | public async Task Handle(PaymentMethodVerifiedDomainEvent paymentMethodVerifiedDomainEvent) 35 | { 36 | var order = await _orderRepository.GetAsync(paymentMethodVerifiedDomainEvent.OrderId); 37 | order.SetBuyerId(paymentMethodVerifiedDomainEvent.Buyer.Id); 38 | order.SetPaymentMethodId(paymentMethodVerifiedDomainEvent.PaymentMethod.Id); 39 | 40 | // Publish the new ordering integration event 41 | var items = new List(); 42 | order.OrderItems.ToList().ForEach((item) => 43 | { 44 | items.Add(new OrderItemInfo() {ProductId = item.ProductId, Assets = item.Units}); 45 | }); 46 | 47 | var integrationEvent = new OrderStartedIntegrationEvent( 48 | Guid.NewGuid(), paymentMethodVerifiedDomainEvent.Buyer.IdentityGuid, DateTime.UtcNow, items); 49 | 50 | await _orderingIntegrationEventService.PublishThroughEventBusAsync(integrationEvent); 51 | 52 | await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(); 53 | 54 | _logger.CreateLogger(nameof(UpdateOrderOnPaymentMethodVerifiedEventHandler)) 55 | .LogTrace($"Order with Id: {paymentMethodVerifiedDomainEvent.OrderId} has been successfully updated with a payment method id: { paymentMethodVerifiedDomainEvent.PaymentMethod.Id }"); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/DomainEventsHandler/ValidateOrAddBuyerOnOrderStartedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using MediatR; 3 | using Microsoft.Extensions.Logging; 4 | using Orders.Application.Services; 5 | using Orders.Domain.AggregatesModel.BuyerAggregate; 6 | using Orders.Domain.AggregatesModel.Events; 7 | 8 | namespace Orders.Application.DomainEventsHandler 9 | { 10 | /// 11 | /// Add buyer and payment info to the order 12 | /// 13 | public class ValidateOrAddBuyerOnOrderStartedEventHandler : 14 | IAsyncNotificationHandler 15 | { 16 | private readonly ILoggerFactory _logger; 17 | private readonly IIdentityService _identityService; 18 | private readonly IBuyerRepository _buyerRepository; 19 | 20 | public ValidateOrAddBuyerOnOrderStartedEventHandler( 21 | IIdentityService identityService, 22 | IBuyerRepository buyerRepository, 23 | ILoggerFactory loggerFactory) 24 | { 25 | _logger = loggerFactory; 26 | _identityService = identityService; 27 | _buyerRepository = buyerRepository; 28 | } 29 | 30 | public async Task Handle(OrderStartedDomainEvent notification) 31 | { 32 | var guid = _identityService.GetUserIdentity(); 33 | 34 | var buyer = await _buyerRepository.FindAsync(guid); 35 | var buyerOriginallyExisted = (buyer != null); 36 | 37 | if (!buyerOriginallyExisted) 38 | { 39 | buyer = new Buyer(guid); 40 | } 41 | 42 | buyer.VerifyOrAddPaymentMethod( 43 | notification.CardNumber, 44 | notification.CardSecurityNumber, 45 | notification.CardHolder, 46 | notification.CardExpiration, 47 | notification.Order.Id); 48 | 49 | var buyerUpdated = buyerOriginallyExisted ? _buyerRepository.Update(buyer) : _buyerRepository.Add(buyer); 50 | 51 | await _buyerRepository.UnitOfWork.SaveEntitiesAsync(); 52 | 53 | _logger.CreateLogger(typeof(ValidateOrAddBuyerOnOrderStartedEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {notification.Order.Id}."); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Filters/ErrorResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Orders.Application.Filters 2 | { 3 | public class ErrorResponse 4 | { 5 | public string[] Messages { get; set; } 6 | 7 | public object DeveloperMessage { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Filters/HttpGlobalExceptionHandlingFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Filters; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Orders.Application.Filters 5 | { 6 | public class HttpGlobalExceptionHandlingFilter : IExceptionFilter 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public HttpGlobalExceptionHandlingFilter(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnException(ExceptionContext context) 16 | { 17 | _logger.LogError( 18 | new EventId(context.Exception.HResult), 19 | context.Exception, 20 | context.Exception.Message 21 | ); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Filters/RequestLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Orders.Application.Filters 8 | { 9 | public class RequestLoggingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ILogger _logger; 13 | 14 | public RequestLoggingMiddleware(RequestDelegate next, ILogger logger) 15 | { 16 | _next = next; 17 | _logger = logger; 18 | } 19 | 20 | public async Task Invoke(HttpContext context) 21 | { 22 | var injectedRequestStream = new MemoryStream(); 23 | 24 | try 25 | { 26 | var requestLog = $"REQUEST HttpMethod: {context.Request.Method}, Path: {context.Request.Path}"; 27 | 28 | using (var bodyReader = new StreamReader(context.Request.Body)) 29 | { 30 | var bodyAsText = bodyReader.ReadToEnd(); 31 | if (string.IsNullOrWhiteSpace(bodyAsText) == false) 32 | { 33 | requestLog += $", Body : {bodyAsText}"; 34 | } 35 | 36 | var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText); 37 | injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length); 38 | injectedRequestStream.Seek(0, SeekOrigin.Begin); 39 | context.Request.Body = injectedRequestStream; 40 | } 41 | 42 | _logger.LogTrace(requestLog); 43 | 44 | await _next.Invoke(context); 45 | } 46 | finally 47 | { 48 | injectedRequestStream.Dispose(); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Filters/ResponseLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Orders.Application.Filters 8 | { 9 | public class ResponseLoggingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ILogger _logger; 13 | private readonly Func _defaultFormatter = (state, exception) => state; 14 | 15 | public ResponseLoggingMiddleware(RequestDelegate next, ILogger logger) 16 | { 17 | _next = next; 18 | _logger = logger; 19 | } 20 | 21 | public async Task Invoke(HttpContext context) 22 | { 23 | var bodyStream = context.Response.Body; 24 | 25 | var responseBodyStream = new MemoryStream(); 26 | context.Response.Body = responseBodyStream; 27 | 28 | await _next(context); 29 | 30 | responseBodyStream.Seek(0, SeekOrigin.Begin); 31 | var responseBody = new StreamReader(responseBodyStream).ReadToEnd(); 32 | _logger.Log(LogLevel.Trace, 1, $"RESPONSE LOG: {responseBody}", null, _defaultFormatter); 33 | responseBodyStream.Seek(0, SeekOrigin.Begin); 34 | await responseBodyStream.CopyToAsync(bodyStream); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Orders.Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | aws.dev\%(RecursiveDir)%(Filename)%(Extension) 37 | PreserveNewest 38 | 39 | 40 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using Orders.Infrastructure; 9 | 10 | namespace Orders.Application 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | BuildWebHost(args).MigrateDbContext((context, services) => 17 | { 18 | var loggerFactory = services.GetRequiredService(); 19 | var logger = loggerFactory.CreateLogger("BuildWebHost"); 20 | 21 | try 22 | { 23 | new OrdersDbContextSeed().Seed(context, loggerFactory); 24 | } 25 | catch (Exception exception) 26 | { 27 | logger.LogError(exception, "An error occurred seeding the DB."); 28 | } 29 | }).Run(); 30 | } 31 | 32 | public static IWebHost BuildWebHost(string[] args) => 33 | WebHost.CreateDefaultBuilder(args) 34 | .UseKestrel(options => 35 | { 36 | options.Listen(IPAddress.Any, 80); 37 | }) 38 | .UseContentRoot(Directory.GetCurrentDirectory()) 39 | .UseIISIntegration() 40 | .UseStartup() 41 | .Build(); 42 | } 43 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Services/IIdentityService.cs: -------------------------------------------------------------------------------- 1 | namespace Orders.Application.Services 2 | { 3 | public interface IIdentityService 4 | { 5 | string GetUserIdentity(); 6 | } 7 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Services/IOrderingIntegrationEventService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EventBus.Events; 3 | 4 | namespace Orders.Application.Services 5 | { 6 | public interface IOrderingIntegrationEventService 7 | { 8 | Task SaveEventAndOrderingContextChangesAsync(); 9 | Task PublishThroughEventBusAsync(IntegrationEvent evt); 10 | } 11 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Services/IdentityService.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Orders.Application.Services 6 | { 7 | public class IdentityService : IIdentityService 8 | { 9 | private IHttpContextAccessor _context; 10 | 11 | public IdentityService(IHttpContextAccessor context) 12 | { 13 | _context = context; 14 | } 15 | 16 | public string GetUserIdentity() 17 | { 18 | var token = _context.HttpContext.User.FindFirst("access_token")?.Value; 19 | 20 | if (string.IsNullOrEmpty(token)) 21 | return null; 22 | 23 | var tokenData = new JwtSecurityTokenHandler().ReadJwtToken(token); 24 | 25 | return tokenData.Claims.FirstOrDefault(i => i.Type.Equals("userId"))?.Value; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Services/OrderingIntegrationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using EventBus; 5 | using EventBus.Abstractions; 6 | using EventBus.Events; 7 | using EventBusAwsSns.Shared.IntegrationEvents; 8 | using Newtonsoft.Json; 9 | using Orders.Infrastructure; 10 | 11 | namespace Orders.Application.Services 12 | { 13 | public class OrderingIntegrationService : IOrderingIntegrationEventService 14 | { 15 | private readonly OrdersContext _ordersContext; 16 | private readonly IEventBus _eventBus; 17 | 18 | public OrderingIntegrationService(OrdersContext ordersContext, IEventBus eventBus) 19 | { 20 | _ordersContext = ordersContext; 21 | _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); 22 | } 23 | 24 | public async Task SaveEventAndOrderingContextChangesAsync() 25 | { 26 | //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): 27 | //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency 28 | await ResilientTransaction.New(_ordersContext) 29 | .ExecuteAsync(async () => { 30 | // Achieving atomicity between original ordering database operation and the IntegrationEventLog thanks to a local transaction 31 | await _ordersContext.SaveChangesAsync(); 32 | }); 33 | } 34 | 35 | public async Task PublishThroughEventBusAsync(IntegrationEvent integrationEvent) 36 | { 37 | await _eventBus.PublishAsync( 38 | JsonConvert.SerializeObject(integrationEvent), 39 | CancellationToken.None 40 | ); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Validation/CreateOrderCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentValidation; 5 | using Orders.Application.Commands; 6 | using Orders.Application.Controllers; 7 | 8 | namespace Orders.Application.Validation 9 | { 10 | public class CreateOrderCommandValidator : AbstractValidator 11 | { 12 | public CreateOrderCommandValidator() 13 | { 14 | RuleFor(c => c.OrderItems).Must(ContainOrderItems).WithMessage("No order items found"); 15 | RuleFor(c => c.CardNumber).NotEmpty().Length(12, 19); 16 | RuleFor(c => c.CardHolderName).NotEmpty(); 17 | RuleFor(c => c.CardExpiration).NotEmpty().Must(BeValidExpirationDate) 18 | .WithMessage("Please specify a valid card expiration date"); 19 | RuleFor(c => c.CardSecurityNumber).NotEmpty().Length(3); 20 | } 21 | 22 | private bool BeValidExpirationDate(DateTime dateTime) 23 | { 24 | return dateTime >= DateTime.UtcNow; 25 | } 26 | 27 | private bool ContainOrderItems(IEnumerable orderItems) 28 | { 29 | return orderItems.Any(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/Validation/ValidatorPipeline.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using FluentValidation; 5 | using MediatR; 6 | 7 | namespace Orders.Application.Validation 8 | { 9 | public class ValidatorPipeline : IPipelineBehavior 10 | where TRequest : IRequest 11 | { 12 | private readonly IEnumerable> _validators; 13 | 14 | public ValidatorPipeline(IEnumerable> validators) 15 | { 16 | _validators = validators; 17 | } 18 | 19 | public Task Handle(TRequest request, RequestHandlerDelegate next) 20 | { 21 | var context = new ValidationContext(request); 22 | var failures = _validators 23 | .Select(v => v.Validate(context)) 24 | .SelectMany(result => result.Errors) 25 | .Where(f => f != null) 26 | .ToList(); 27 | 28 | if (failures.Count != 0) 29 | { 30 | throw new ValidationException(failures); 31 | } 32 | 33 | return next(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=192.168.104.100; Port=3306; Database=mymicsapp.Services.ordersDb; Uid=sandhaka; Password=pwd", 4 | "Redis": "192.168.104.100" 5 | }, 6 | "AWS": { 7 | "Profile": "aws.dev-profile", 8 | "Region": "eu-west-1", 9 | "ProfilesLocation": "./aws.dev/credentials" 10 | }, 11 | "Logging": { 12 | "IncludeScopes": false, 13 | "LogLevel": { 14 | "Default": "Trace", 15 | "System": "Information", 16 | "Microsoft": "Information" 17 | } 18 | }, 19 | "TokenAuthentication": { 20 | "Issuer": "DemoIssuer", 21 | "Audience": "DemoAudience" 22 | }, 23 | "AwsEventBus" : { 24 | "Topics": { 25 | "OrderStartedIntegrationEvent": { 26 | "arn": "arn:aws:sns:eu-west-1:069637010413:orderStarted", 27 | "SqsUrl": "https://sqs.eu-west-1.amazonaws.com/069637010413/started-orders" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Application/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=192.168.104.100; Port=3306; Database=mymicsapp.Services.ordersDb; Uid=sandhaka; Password=pwd", 4 | "Redis": "192.168.104.100" 5 | }, 6 | "AWS": { 7 | "Profile": "aws.dev-profile", 8 | "Region": "eu-west-1", 9 | "ProfilesLocation": "./aws.dev/credentials" 10 | }, 11 | "Logging": { 12 | "IncludeScopes": false, 13 | "LogLevel": { 14 | "Default": "Trace", 15 | "System": "Information", 16 | "Microsoft": "Information" 17 | } 18 | }, 19 | "TokenAuthentication": { 20 | "Issuer": "DemoIssuer", 21 | "Audience": "DemoAudience" 22 | }, 23 | "AwsEventBus" : { 24 | "Topics": { 25 | "OrderStartedIntegrationEvent": { 26 | "arn": "arn:aws:sns:eu-west-1:069637010413:orderStarted", 27 | "SqsUrl": "https://sqs.eu-west-1.amazonaws.com/069637010413/started-orders" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/AggregatesModel/BuyerAggregate/Buyer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Orders.Domain.AggregatesModel.Events; 5 | using Orders.Domain.SeedWork; 6 | 7 | namespace Orders.Domain.AggregatesModel.BuyerAggregate 8 | { 9 | /// 10 | /// Buyer entity 11 | /// 12 | public class Buyer : 13 | Entity, IAggregateRoot 14 | { 15 | public string IdentityGuid { get; private set; } 16 | private List _paymentMethods; 17 | public IEnumerable PaymentMethods => _paymentMethods.AsReadOnly(); 18 | 19 | protected Buyer() 20 | { 21 | _paymentMethods = new List(); 22 | } 23 | 24 | public Buyer(string identity) : this() 25 | { 26 | IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity)); 27 | } 28 | 29 | public PaymentMethod VerifyOrAddPaymentMethod(string cardNumber, string securityNumber, string cardHolder, 30 | DateTime expiration, int orderId) 31 | { 32 | var existingPaymentMethod = _paymentMethods.FirstOrDefault(i => i.IsEqualTo(cardNumber, expiration)); 33 | 34 | if (existingPaymentMethod != null) 35 | { 36 | AddDomainEvent(new PaymentMethodVerifiedDomainEvent(this, existingPaymentMethod, orderId)); 37 | 38 | return existingPaymentMethod; 39 | } 40 | 41 | var paymentMethod = new PaymentMethod(cardNumber, securityNumber, cardHolder, expiration); 42 | 43 | _paymentMethods.Add(paymentMethod); 44 | 45 | AddDomainEvent(new PaymentMethodVerifiedDomainEvent(this, paymentMethod, orderId)); 46 | 47 | return paymentMethod; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/AggregatesModel/BuyerAggregate/IBuyerRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Orders.Domain.SeedWork; 3 | 4 | namespace Orders.Domain.AggregatesModel.BuyerAggregate 5 | { 6 | public interface IBuyerRepository : IRepository 7 | { 8 | Buyer Add(Buyer buyer); 9 | Buyer Update(Buyer buyer); 10 | Task FindAsync(string BuyerIdentityGuid); 11 | } 12 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Orders.Domain.SeedWork; 3 | 4 | namespace Orders.Domain.AggregatesModel.BuyerAggregate 5 | { 6 | public class PaymentMethod : Entity 7 | { 8 | private string _cardNumber; 9 | private string _securityNumber; 10 | private string _cardHolder; 11 | private DateTime _expiration; 12 | 13 | protected PaymentMethod() { } 14 | 15 | public PaymentMethod(string cardNumber, string securityNumber, string cardHolder, DateTime expiration) 16 | { 17 | _cardHolder = cardHolder; 18 | _cardNumber = cardNumber; 19 | _expiration = expiration; 20 | _securityNumber = securityNumber; 21 | // TODO: check if any arguments is null 22 | } 23 | 24 | public bool IsEqualTo(string cardNumber, DateTime expiration) 25 | { 26 | return _cardNumber == cardNumber && _expiration == expiration; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/AggregatesModel/Events/OrderStartedDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | using Orders.Domain.AggregatesModel.OrderAggregate; 4 | 5 | namespace Orders.Domain.AggregatesModel.Events 6 | { 7 | public class OrderStartedDomainEvent: INotification 8 | { 9 | public Order Order { get; private set; } 10 | public string CardNumber { get; private set; } 11 | public string CardHolder { get; private set; } 12 | public string CardSecurityNumber { get; private set; } 13 | public DateTime CardExpiration { get; private set; } 14 | 15 | public OrderStartedDomainEvent( 16 | Order order, 17 | string cardNumber, 18 | string cardHolder, 19 | string cardSecurityNumber, 20 | DateTime cardExpiration) 21 | { 22 | Order = order; 23 | CardExpiration = cardExpiration; 24 | CardHolder = cardHolder; 25 | CardNumber = cardNumber; 26 | CardSecurityNumber = cardSecurityNumber; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/AggregatesModel/Events/PaymentMethodVerifiedDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Orders.Domain.AggregatesModel.BuyerAggregate; 3 | 4 | namespace Orders.Domain.AggregatesModel.Events 5 | { 6 | public class PaymentMethodVerifiedDomainEvent : INotification 7 | { 8 | public Buyer Buyer { get; private set; } 9 | public PaymentMethod PaymentMethod { get; private set; } 10 | public int OrderId { get; private set; } 11 | 12 | public PaymentMethodVerifiedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId) 13 | { 14 | Buyer = buyer; 15 | PaymentMethod = payment; 16 | OrderId = orderId; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/AggregatesModel/OrderAggregate/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Orders.Domain.SeedWork; 3 | 4 | namespace Orders.Domain.AggregatesModel.OrderAggregate 5 | { 6 | public interface IOrderRepository: IRepository 7 | { 8 | Order Add(Order order); 9 | void Update(Order order); 10 | Task GetAsync(int orderId); 11 | } 12 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/AggregatesModel/OrderAggregate/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Orders.Domain.AggregatesModel.Events; 5 | using Orders.Domain.SeedWork; 6 | 7 | namespace Orders.Domain.AggregatesModel.OrderAggregate 8 | { 9 | /// 10 | /// Order entity 11 | /// 12 | public class Order : 13 | Entity, IAggregateRoot 14 | { 15 | private DateTime _orderDate; 16 | private int? _buyerId; 17 | private int? _paymentMethodId; 18 | 19 | public decimal TotalAmount() 20 | { 21 | return _orderItems.Select(i => i.GetUnitPrice()).Sum(); 22 | } 23 | 24 | private readonly List _orderItems; 25 | 26 | public IEnumerable OrderItems => _orderItems.AsReadOnly(); 27 | 28 | public void SetBuyerId(int buyerId) 29 | { 30 | _buyerId = buyerId; 31 | } 32 | 33 | public void SetPaymentMethodId(int paymentMethodId) 34 | { 35 | _paymentMethodId = paymentMethodId; 36 | } 37 | 38 | public Order( 39 | string cardNumber, 40 | string cardSecurityNumber, 41 | string cardHolderName, 42 | DateTime cardExpiration, 43 | int? buyerId = null, 44 | int? paymentMethodId = null) 45 | { 46 | _orderItems = new List(); 47 | _orderDate = DateTime.UtcNow; 48 | 49 | _buyerId = buyerId; 50 | _paymentMethodId = paymentMethodId; 51 | 52 | // Emit a order started domain event 53 | AddOrderStartedDomainEvent(cardNumber, cardHolderName, cardSecurityNumber, cardExpiration); 54 | } 55 | 56 | public void AddOrderItem(int productId, string productName, decimal unitPrize, int units) 57 | { 58 | var existingOrderForProduct = _orderItems.SingleOrDefault(o => o.ProductId == productId); 59 | 60 | if (existingOrderForProduct != null) 61 | { 62 | existingOrderForProduct.AddUnits(units); 63 | } 64 | else 65 | { 66 | _orderItems.Add(new OrderItem(productId, productName, unitPrize, units)); 67 | } 68 | } 69 | 70 | private void AddOrderStartedDomainEvent(string cardNumber, string cardHolder, string cardSecurityNumber, 71 | DateTime cardExpiration) 72 | { 73 | var orderStartedDomainEvent = new OrderStartedDomainEvent( 74 | this,cardNumber,cardHolder,cardSecurityNumber,cardExpiration); 75 | 76 | this.AddDomainEvent(orderStartedDomainEvent); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/AggregatesModel/OrderAggregate/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using Orders.Domain.SeedWork; 2 | 3 | namespace Orders.Domain.AggregatesModel.OrderAggregate 4 | { 5 | public class OrderItem : Entity 6 | { 7 | private string _productName; 8 | private decimal _unitPrice; 9 | 10 | public int Units { get; private set; } 11 | public int ProductId { get; private set; } 12 | 13 | protected OrderItem() {} 14 | 15 | public OrderItem(int productId, string productName, decimal unitPrize, int units = 1) 16 | { 17 | if (units <= 0) 18 | { 19 | // TODO Order exception 20 | } 21 | 22 | ProductId = productId; 23 | Units = units; 24 | 25 | _productName = productName; 26 | _unitPrice = unitPrize; 27 | } 28 | 29 | public void AddUnits(int units) 30 | { 31 | if (units < 0) 32 | { 33 | // TODO the same 34 | } 35 | 36 | Units += units; 37 | } 38 | 39 | public decimal GetUnitPrice() 40 | { 41 | return _unitPrice; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/Exceptions/OrderingDomainException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Orders.Domain.Exceptions 4 | { 5 | public class OrderingDomainException : Exception 6 | { 7 | // TODO 8 | } 9 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/Orders.Domain.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/SeedWork/Entity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MediatR; 4 | 5 | namespace Orders.Domain.SeedWork 6 | { 7 | /// 8 | /// Database entity base class 9 | /// 10 | public abstract class Entity 11 | { 12 | int? _requestedHashCode; 13 | int _id; 14 | 15 | private List _domainEvents; 16 | 17 | public virtual int Id 18 | { 19 | get { return _id; } 20 | protected set { _id = value; } 21 | } 22 | 23 | public List DomainEvents => _domainEvents; 24 | public void AddDomainEvent(INotification eventItem) 25 | { 26 | _domainEvents = _domainEvents ?? new List(); 27 | _domainEvents.Add(eventItem); 28 | } 29 | 30 | public bool IsTransient() 31 | { 32 | return this.Id == default(Int32); 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | if (obj == null || !(obj is Entity)) 38 | return false; 39 | 40 | if (Object.ReferenceEquals(this, obj)) 41 | return true; 42 | 43 | if (this.GetType() != obj.GetType()) 44 | return false; 45 | 46 | Entity item = (Entity)obj; 47 | 48 | if (item.IsTransient() || this.IsTransient()) 49 | return false; 50 | else 51 | return item.Id == this.Id; 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | if (!IsTransient()) 57 | { 58 | if (!_requestedHashCode.HasValue) 59 | _requestedHashCode = this.Id.GetHashCode() ^ 31; // XOR for random distribution (http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx) 60 | 61 | return _requestedHashCode.Value; 62 | } 63 | else 64 | return base.GetHashCode(); 65 | } 66 | 67 | public static bool operator ==(Entity left, Entity right) 68 | { 69 | if (Object.Equals(left, null)) 70 | return (Object.Equals(right, null)) ? true : false; 71 | else 72 | return left.Equals(right); 73 | } 74 | 75 | public static bool operator !=(Entity left, Entity right) 76 | { 77 | return !(left == right); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/SeedWork/IAggregateRoot.cs: -------------------------------------------------------------------------------- 1 | namespace Orders.Domain.SeedWork 2 | { 3 | public interface IAggregateRoot 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/SeedWork/IRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Orders.Domain.SeedWork 2 | { 3 | public interface IRepository where T : IAggregateRoot 4 | { 5 | IUnitOfWork UnitOfWork { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Domain/SeedWork/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Orders.Domain.SeedWork 6 | { 7 | public interface IUnitOfWork : IDisposable 8 | { 9 | Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); 10 | Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)); 11 | } 12 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Infrastructure/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Infrastructure/MediatorExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | using Orders.Domain.SeedWork; 5 | 6 | namespace Orders.Infrastructure 7 | { 8 | static class MediatorExtension 9 | { 10 | /// 11 | /// Process all the domain events by the mediator. Important to avoid side-effect on transaction operations. 12 | /// 13 | /// Mediator instance 14 | /// Db context 15 | /// Task 16 | public static async Task DispatchDomainEventsAsync(this IMediator mediator, OrdersContext ctx) 17 | { 18 | var domainEntities = ctx.ChangeTracker 19 | .Entries() 20 | .Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any()); 21 | 22 | var domainEvents = domainEntities 23 | .SelectMany(x => x.Entity.DomainEvents) 24 | .ToList(); 25 | 26 | domainEntities.ToList() 27 | .ForEach(entity => entity.Entity.DomainEvents.Clear()); 28 | 29 | var tasks = domainEvents 30 | .Select(async (domainEvent) => { 31 | await mediator.Publish(domainEvent); 32 | }); 33 | 34 | // Ensures the atomicity of the all db changes 35 | await Task.WhenAll(tasks); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Infrastructure/Orders.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Infrastructure/OrdersDbContextSeed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Orders.Infrastructure 5 | { 6 | public class OrdersDbContextSeed 7 | { 8 | public void Seed(OrdersContext context, ILoggerFactory loggerFactory) 9 | { 10 | try 11 | { 12 | using (context) 13 | { 14 | // Nothing to initialize 15 | } 16 | } 17 | catch (Exception exception) 18 | { 19 | var log = loggerFactory.CreateLogger("Orders seed"); 20 | log.LogError(exception.Message); 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Infrastructure/Repositories/BuyerRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.EntityFrameworkCore; 4 | using Orders.Domain.AggregatesModel.BuyerAggregate; 5 | using Orders.Domain.SeedWork; 6 | 7 | namespace Orders.Infrastructure.Repositories 8 | { 9 | public class BuyerRepository : IBuyerRepository 10 | { 11 | private readonly OrdersContext _context; 12 | 13 | public IUnitOfWork UnitOfWork => _context; 14 | 15 | public BuyerRepository(OrdersContext ordersContext) 16 | { 17 | _context = ordersContext; 18 | } 19 | 20 | public Buyer Add(Buyer buyer) 21 | { 22 | if (buyer.IsTransient()) 23 | { 24 | return _context.Buyers 25 | .Add(buyer) 26 | .Entity; 27 | } 28 | 29 | return buyer; 30 | } 31 | 32 | public Buyer Update(Buyer buyer) 33 | { 34 | return _context.Buyers 35 | .Update(buyer) 36 | .Entity; 37 | } 38 | 39 | public async Task FindAsync(string identity) 40 | { 41 | var buyer = await _context.Buyers 42 | .FirstOrDefaultAsync(b => b.IdentityGuid == identity); 43 | 44 | return buyer; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /dockerstack-application/Services/Orders/Orders.Infrastructure/Repositories/OrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.EntityFrameworkCore; 3 | using Orders.Domain.AggregatesModel.OrderAggregate; 4 | using Orders.Domain.SeedWork; 5 | 6 | namespace Orders.Infrastructure.Repositories 7 | { 8 | /// 9 | /// Orders repository 10 | /// 11 | public class OrderRepository : IOrderRepository 12 | { 13 | private readonly OrdersContext _context; 14 | 15 | public IUnitOfWork UnitOfWork => _context; 16 | 17 | public OrderRepository(OrdersContext ordersContext) 18 | { 19 | _context = ordersContext; 20 | } 21 | 22 | public Order Add(Order order) 23 | { 24 | return _context.Orders.Add(order).Entity; 25 | } 26 | 27 | public void Update(Order order) 28 | { 29 | _context.Entry(order).State = EntityState.Modified; 30 | } 31 | 32 | public async Task GetAsync(int orderId) 33 | { 34 | return await _context.Orders.FindAsync(orderId); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /dockerstack-system/docker-stack.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | services: 3 | fluentd: 4 | build: ./fluentd 5 | image: sandhaka/mymicsapp_fluentd 6 | ports: 7 | - "24224:24224" 8 | - "24224:24224/udp" 9 | networks: 10 | - mymsc_network 11 | deploy: 12 | replicas: 1 13 | elasticsearch: 14 | image: elasticsearch 15 | ports: 16 | - "9200:9200" 17 | networks: 18 | - mymsc_network 19 | deploy: 20 | replicas: 1 21 | kibana: 22 | image: kibana 23 | networks: 24 | - mymsc_network 25 | ports: 26 | - "5601:5601" 27 | deploy: 28 | replicas: 1 29 | 30 | networks: 31 | mymsc_network: -------------------------------------------------------------------------------- /dockerstack-system/fluentd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fluent/fluentd:v0.14-debian 2 | 3 | RUN ["gem", "install", "fluent-plugin-elasticsearch", "--no-rdoc", "--no-ri", "--version", "1.10.0"] 4 | 5 | COPY fluent.conf /fluentd/etc/ -------------------------------------------------------------------------------- /dockerstack-system/fluentd/fluent.conf: -------------------------------------------------------------------------------- 1 | 2 | @type forward 3 | port 24224 4 | bind 0.0.0.0 5 | 6 | 7 | @type copy 8 | 9 | @type elasticsearch 10 | host elasticsearch 11 | port 9200 12 | logstash_format true 13 | logstash_prefix fluentd 14 | logstash_dateformat %Y%m%d 15 | include_tag_key true 16 | type_name access_log 17 | tag_key @log_name 18 | flush_interval 1s 19 | 20 | 21 | @type stdout 22 | 23 | -------------------------------------------------------------------------------- /scripts/copy-ssh-id-to-host.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash - 2 | 3 | if [[ -z $1 ]] 4 | then 5 | echo -e "\033[31m No user@host supplied.\033[0m" 6 | exit 1 7 | fi 8 | 9 | SSH_PORT=22 10 | 11 | if [[ -n $2 ]] 12 | then 13 | SSH_PORT=$2 14 | fi 15 | 16 | ssh-copy-id -p ${SSH_PORT} -i "/c/Users/shams/.ssh/id_rsa_clrdbg.pub" -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" $1 17 | exit 0 --------------------------------------------------------------------------------