├── .gitignore ├── README.md ├── angular-shopping-cart ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── Dockerfile ├── README.md ├── angular.json ├── config │ └── nginx.conf ├── docker-compose.yml ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── proxy.conf.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── package.json │ │ ├── products │ │ │ ├── components │ │ │ │ └── products │ │ │ │ │ ├── products.component.html │ │ │ │ │ ├── products.component.scss │ │ │ │ │ └── products.component.ts │ │ │ ├── models │ │ │ │ └── product.ts │ │ │ ├── products-routing.module.ts │ │ │ ├── products.module.ts │ │ │ └── services │ │ │ │ └── product.service.ts │ │ ├── shared │ │ │ ├── components │ │ │ │ └── header │ │ │ │ │ ├── header.component.html │ │ │ │ │ └── header.component.ts │ │ │ ├── services │ │ │ │ └── event-source.service.ts │ │ │ └── shared.module.ts │ │ └── shopping-cart │ │ │ ├── checkout-message.ts │ │ │ ├── checkout │ │ │ ├── checkout.component.html │ │ │ ├── checkout.component.scss │ │ │ └── checkout.component.ts │ │ │ ├── shopping-cart-routing.module.ts │ │ │ ├── shopping-cart.module.ts │ │ │ └── shopping-cart.service.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ ├── 1.jpg │ │ │ ├── 10.jpg │ │ │ ├── 11.jpg │ │ │ ├── 12.jpg │ │ │ ├── 13.jpg │ │ │ ├── 14.jpg │ │ │ ├── 14.webp │ │ │ ├── 15.webp │ │ │ ├── 17.webp │ │ │ ├── 18.webp │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ ├── 5.jpg │ │ │ ├── 6.jpg │ │ │ ├── 7.jpg │ │ │ ├── 8.jpg │ │ │ └── 9.jpg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ ├── styles │ │ ├── _app.variables.scss │ │ ├── _breakpoints.scss │ │ ├── _cards.scss │ │ ├── _mixins.scss │ │ ├── checkout.component.scss │ │ └── material.variables.scss │ └── test.ts ├── tsconfig.app.json ├── tsconfig.base.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json └── spring-shopping-cart ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── Dockerfile-build ├── docker-compose.build.yml ├── docker-compose.yml ├── manifest.json ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── assembly └── distribution.xml ├── main ├── java │ └── com │ │ └── loiane │ │ └── springshoppingcart │ │ ├── SpringShoppingCartApplication.java │ │ ├── controller │ │ ├── OrderController.java │ │ └── ProductController.java │ │ ├── model │ │ ├── Order.java │ │ ├── OrderEvent.java │ │ └── Product.java │ │ └── repository │ │ ├── OrderRepository.java │ │ └── ProductRepository.java └── resources │ └── application.properties └── test └── java └── com └── loiane └── springshoppingcart ├── SpringShoppingCartApplicationTests.java └── controller ├── OrderControllerTests.java └── ProductControllerTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | angular-shopping-cart/.angulardoc.json 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reactive-spring-angular 2 | -------------------------------------------------------------------------------- /angular-shopping-cart/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major version 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /angular-shopping-cart/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://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 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /angular-shopping-cart/.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 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /angular-shopping-cart/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "docker.defaultRegistryPath": "loiane", 3 | } 4 | -------------------------------------------------------------------------------- /angular-shopping-cart/Dockerfile: -------------------------------------------------------------------------------- 1 | ##### Angular build 2 | FROM node:latest as angular-client 3 | LABEL author="Loiane Groner" 4 | WORKDIR /app 5 | COPY package.json package.json 6 | RUN npm install 7 | COPY . . 8 | RUN npm run build --prod 9 | 10 | ##### Nginx 11 | FROM nginx:alpine 12 | VOLUME /var/cache/nginx 13 | COPY --from=angular-client /app/dist/angular-shopping-cart /usr/share/nginx/html 14 | COPY ./config/nginx.conf /etc/nginx/conf.d/default.conf 15 | 16 | # docker build -t angular-shopping-cart -f Dockerfile . 17 | # docker run -p 8081:80 angular-shopping-cart 18 | -------------------------------------------------------------------------------- /angular-shopping-cart/README.md: -------------------------------------------------------------------------------- 1 | # AngularShoppingCart 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.1. 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 | 25 | ## Further help 26 | 27 | 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). 28 | -------------------------------------------------------------------------------- /angular-shopping-cart/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-shopping-cart": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "inlineStyle": true, 11 | "style": "scss", 12 | "skipTests": true 13 | }, 14 | "@schematics/angular:class": { 15 | "skipTests": true 16 | }, 17 | "@schematics/angular:directive": { 18 | "skipTests": true 19 | }, 20 | "@schematics/angular:guard": { 21 | "skipTests": true 22 | }, 23 | "@schematics/angular:interceptor": { 24 | "skipTests": true 25 | }, 26 | "@schematics/angular:module": { 27 | "skipTests": true 28 | }, 29 | "@schematics/angular:pipe": { 30 | "skipTests": true 31 | }, 32 | "@schematics/angular:service": { 33 | "skipTests": true 34 | }, 35 | "@schematics/angular:application": { 36 | "strict": true 37 | } 38 | }, 39 | "root": "", 40 | "sourceRoot": "src", 41 | "prefix": "app", 42 | "architect": { 43 | "build": { 44 | "builder": "@angular-devkit/build-angular:browser", 45 | "options": { 46 | "outputPath": "dist/angular-shopping-cart", 47 | "index": "src/index.html", 48 | "main": "src/main.ts", 49 | "polyfills": "src/polyfills.ts", 50 | "tsConfig": "tsconfig.app.json", 51 | "aot": true, 52 | "assets": [ 53 | "src/favicon.ico", 54 | "src/assets" 55 | ], 56 | "styles": [ 57 | "src/styles.scss" 58 | ], 59 | "scripts": [] 60 | }, 61 | "configurations": { 62 | "production": { 63 | "fileReplacements": [ 64 | { 65 | "replace": "src/environments/environment.ts", 66 | "with": "src/environments/environment.prod.ts" 67 | } 68 | ], 69 | "optimization": true, 70 | "outputHashing": "all", 71 | "sourceMap": false, 72 | "extractCss": true, 73 | "namedChunks": false, 74 | "extractLicenses": true, 75 | "vendorChunk": false, 76 | "buildOptimizer": true, 77 | "budgets": [ 78 | { 79 | "type": "initial", 80 | "maximumWarning": "500kb", 81 | "maximumError": "1mb" 82 | }, 83 | { 84 | "type": "anyComponentStyle", 85 | "maximumWarning": "2kb", 86 | "maximumError": "4kb" 87 | } 88 | ] 89 | } 90 | } 91 | }, 92 | "serve": { 93 | "builder": "@angular-devkit/build-angular:dev-server", 94 | "options": { 95 | "browserTarget": "angular-shopping-cart:build" 96 | }, 97 | "configurations": { 98 | "production": { 99 | "browserTarget": "angular-shopping-cart:build:production" 100 | } 101 | } 102 | }, 103 | "extract-i18n": { 104 | "builder": "@angular-devkit/build-angular:extract-i18n", 105 | "options": { 106 | "browserTarget": "angular-shopping-cart:build" 107 | } 108 | }, 109 | "test": { 110 | "builder": "@angular-devkit/build-angular:karma", 111 | "options": { 112 | "main": "src/test.ts", 113 | "polyfills": "src/polyfills.ts", 114 | "tsConfig": "tsconfig.spec.json", 115 | "karmaConfig": "karma.conf.js", 116 | "assets": [ 117 | "src/favicon.ico", 118 | "src/assets" 119 | ], 120 | "styles": [ 121 | "src/styles.scss" 122 | ], 123 | "scripts": [] 124 | } 125 | }, 126 | "lint": { 127 | "builder": "@angular-devkit/build-angular:tslint", 128 | "options": { 129 | "tsConfig": [ 130 | "tsconfig.app.json", 131 | "tsconfig.spec.json", 132 | "e2e/tsconfig.json" 133 | ], 134 | "exclude": [ 135 | "**/node_modules/**" 136 | ] 137 | } 138 | }, 139 | "e2e": { 140 | "builder": "@angular-devkit/build-angular:protractor", 141 | "options": { 142 | "protractorConfig": "e2e/protractor.conf.js", 143 | "devServerTarget": "angular-shopping-cart:serve" 144 | }, 145 | "configurations": { 146 | "production": { 147 | "devServerTarget": "angular-shopping-cart:serve:production" 148 | } 149 | } 150 | } 151 | } 152 | }}, 153 | "defaultProject": "angular-shopping-cart" 154 | } 155 | -------------------------------------------------------------------------------- /angular-shopping-cart/config/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 0.0.0.0:80; 3 | listen [::]:80; 4 | default_type application/octet-stream; 5 | 6 | gzip on; 7 | gzip_comp_level 6; 8 | gzip_vary on; 9 | gzip_min_length 1000; 10 | gzip_proxied any; 11 | gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 12 | gzip_buffers 16 8k; 13 | client_max_body_size 256M; 14 | 15 | root /usr/share/nginx/html; 16 | 17 | location / { 18 | try_files $uri $uri/ /index.html =404; 19 | } 20 | 21 | # proxy 22 | location /api { 23 | proxy_pass http://129.150.73.207:8080; 24 | proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504; 25 | proxy_buffering off; 26 | proxy_set_header Accept-Encoding ""; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /angular-shopping-cart/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | angular-shopping-cart: 5 | image: angular-shopping-cart 6 | build: . 7 | environment: 8 | NODE_ENV: production 9 | ports: 10 | - 8081:80 11 | -------------------------------------------------------------------------------- /angular-shopping-cart/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /angular-shopping-cart/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('angular-shopping-cart app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-shopping-cart/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /angular-shopping-cart/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /angular-shopping-cart/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 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-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/angular-shopping-cart'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /angular-shopping-cart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-shopping-cart", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --proxy-config proxy.conf.json", 7 | "build": "ng build --prod", 8 | "build-op": "ng build --prod --output-path ../spring-shopping-cart/target/classes/static", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "~10.0.2", 16 | "@angular/cdk": "^10.0.1", 17 | "@angular/common": "~10.0.2", 18 | "@angular/compiler": "~10.0.2", 19 | "@angular/core": "~10.0.2", 20 | "@angular/flex-layout": "^10.0.0-beta.32", 21 | "@angular/forms": "~10.0.2", 22 | "@angular/material": "^10.0.1", 23 | "@angular/platform-browser": "~10.0.2", 24 | "@angular/platform-browser-dynamic": "~10.0.2", 25 | "@angular/router": "~10.0.2", 26 | "rxjs": "~6.5.5", 27 | "tslib": "^2.0.0", 28 | "zone.js": "~0.10.3" 29 | }, 30 | "devDependencies": { 31 | "@angular-devkit/build-angular": "~0.1000.1", 32 | "@angular/cli": "~10.0.1", 33 | "@angular/compiler-cli": "~10.0.2", 34 | "@types/node": "^12.11.1", 35 | "@types/jasmine": "~3.5.0", 36 | "@types/jasminewd2": "~2.0.3", 37 | "codelyzer": "^6.0.0-next.1", 38 | "jasmine-core": "~3.5.0", 39 | "jasmine-spec-reporter": "~5.0.0", 40 | "karma": "~5.0.0", 41 | "karma-chrome-launcher": "~3.1.0", 42 | "karma-coverage-istanbul-reporter": "~3.0.2", 43 | "karma-jasmine": "~3.3.0", 44 | "karma-jasmine-html-reporter": "^1.5.0", 45 | "protractor": "~7.0.0", 46 | "ts-node": "~8.3.0", 47 | "tslint": "~6.1.0", 48 | "typescript": "~3.9.5" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /angular-shopping-cart/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:8080", 4 | "secure": false 5 | }, 6 | "logLevel": "debug" 7 | } 8 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: '', pathMatch: 'full', 7 | loadChildren: () => import('./products/products.module').then(m => m.ProductsModule) 8 | }, 9 | { 10 | path: 'checkout', 11 | loadChildren: () => import('./shopping-cart/shopping-cart.module').then(m => m.ShoppingCartModule) 12 | } 13 | ]; 14 | 15 | @NgModule({ 16 | imports: [RouterModule.forRoot(routes)], 17 | exports: [RouterModule] 18 | }) 19 | export class AppRoutingModule { } 20 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styles: [] 7 | }) 8 | export class AppComponent { 9 | } 10 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { SharedModule } from './shared/shared.module'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | import { AppRoutingModule } from './app-routing.module'; 6 | import { AppComponent } from './app.component'; 7 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 8 | 9 | @NgModule({ 10 | declarations: [ 11 | AppComponent 12 | ], 13 | imports: [ 14 | BrowserModule, 15 | AppRoutingModule, 16 | BrowserAnimationsModule, 17 | SharedModule 18 | ], 19 | providers: [], 20 | bootstrap: [AppComponent] 21 | }) 22 | export class AppModule { } 23 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-shopping-cart", 3 | "private": true, 4 | "description_1": "This is a special package.json file that is not used by package managers.", 5 | "description_2": "It is used to tell the tools and bundlers whether the code under this directory is free of code with non-local side-effect. Any code that does have non-local side-effects can't be well optimized (tree-shaken) and will result in unnecessary increased payload size.", 6 | "description_3": "It should be safe to set this option to 'false' for new applications, but existing code bases could be broken when built with the production config if the application code does contain non-local side-effects that the application depends on.", 7 | "description_4": "To learn more about this file see: https://angular.io/config/app-package-json.", 8 | "sideEffects": false 9 | } 10 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/products/components/products/products.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 |
Sale
8 | 9 |
10 |
11 | 12 |
13 |
14 |

{{ product.name }}

15 |

{{ product.description }}

16 |
17 |
18 | $ {{ 19 | product.discount }} 20 | $ {{ product.price }} 21 |
22 | 25 |
26 |
27 |
28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/products/components/products/products.component.scss: -------------------------------------------------------------------------------- 1 | .background-none { 2 | background-color: transparent!important; 3 | } 4 | .shadow-none { 5 | box-shadow: none!important; 6 | } 7 | .product-figure { 8 | background-color:white; 9 | height: 245px; 10 | } 11 | .product-badge { 12 | color: #fff; 13 | text-align: center; 14 | position: absolute; 15 | font-weight: bold; 16 | border-top: 30px solid #7CB342; 17 | border-bottom: 30px solid transparent; 18 | border-right: 30px solid transparent; 19 | border-left: 30px solid #7CB342; 20 | top: 0; 21 | left: 0; 22 | z-index: 100; 23 | span { 24 | position: absolute; 25 | transform: rotate(-45deg); 26 | top: -18px; 27 | left: -25px; 28 | white-space: nowrap; 29 | } 30 | } 31 | .strikethrough { 32 | text-decoration: line-through; 33 | } 34 | .text-sm { font-size: 70% !important; } 35 | 36 | $border-radius-base: 2px !default; 37 | $transition-duration: 300ms !default; 38 | $spacer: 1rem !default; 39 | .media-grid { 40 | text-align: center; 41 | overflow: hidden; 42 | figure { 43 | cursor: pointer; 44 | border-radius: $border-radius-base $border-radius-base 0 0; 45 | text-align: center; 46 | } 47 | 48 | figure img { 49 | position: relative; 50 | display: block; 51 | max-width: 100%; 52 | border-radius: $border-radius-base $border-radius-base 0 0; 53 | } 54 | &.compact { 55 | border-radius: $border-radius-base 0 0 $border-radius-base; 56 | figure { 57 | border-radius: $border-radius-base 0 0 $border-radius-base; 58 | } 59 | figure img { 60 | border-radius: $border-radius-base 0 0 $border-radius-base; 61 | } 62 | } 63 | 64 | .product-badge { 65 | color: #fff; 66 | text-align: center; 67 | position: absolute; 68 | font-weight: bold; 69 | border-top: 30px solid #7CB342; 70 | border-bottom: 30px solid transparent; 71 | border-right: 30px solid transparent; 72 | border-left: 30px solid #7CB342; 73 | top: 0; 74 | left: 0; 75 | z-index: 100; 76 | span { 77 | position: absolute; 78 | transform: rotate(-45deg); 79 | top: -18px; 80 | left: -25px; 81 | white-space: nowrap; 82 | } 83 | } 84 | 85 | figure img { 86 | opacity: 0.85; 87 | transition: opacity $transition-duration, transform $transition-duration; 88 | } 89 | 90 | figure figcaption { 91 | color: #fff; 92 | text-transform: uppercase; 93 | backface-visibility: hidden; 94 | } 95 | 96 | figure figcaption::before { 97 | pointer-events: none; 98 | position: absolute; 99 | width: 100%; 100 | height: 100%; 101 | border-style: solid; 102 | border-color: rgba(0,0,0,.38); 103 | content: ''; 104 | transition: transform $transition-duration; 105 | } 106 | 107 | figure figcaption::before { 108 | right: 0; 109 | bottom: 0; 110 | border-width: 0 45px 0 0; 111 | transform: translate3d(45px,0,0); 112 | } 113 | 114 | figure figcaption, 115 | figure figcaption > a { 116 | position: absolute; 117 | top: 0; 118 | left: 0; 119 | width: 100%; 120 | height: 100%; 121 | } 122 | 123 | figure figcaption > a { 124 | z-index: 1000; 125 | text-indent: 200%; 126 | white-space: nowrap; 127 | font-size: 0; 128 | opacity: 0; 129 | } 130 | } 131 | 132 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/products/components/products/products.component.ts: -------------------------------------------------------------------------------- 1 | import { ShoppingCartService } from './../../../shopping-cart/shopping-cart.service'; 2 | import { ProductService } from './../../services/product.service'; 3 | import { Product } from './../../models/product'; 4 | import { Component, OnInit } from '@angular/core'; 5 | import { Observable } from 'rxjs'; 6 | 7 | @Component({ 8 | selector: 'app-products', 9 | templateUrl: './products.component.html', 10 | styleUrls: ['./products.component.scss'] 11 | }) 12 | export class ProductsComponent implements OnInit { 13 | 14 | products$: Observable; 15 | 16 | constructor(private service: ProductService, private cartService: ShoppingCartService) { 17 | this.products$ = this.service.load(); 18 | } 19 | 20 | ngOnInit(): void { 21 | } 22 | 23 | addProductCart(product: Product): void { 24 | this.cartService.addProduct(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/products/models/product.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | id: string; 3 | price: number; 4 | status: string; 5 | discounted: string; 6 | discount: number; 7 | name: string; 8 | description: string; 9 | image: string; 10 | } 11 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/products/products-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { ProductsComponent } from './components/products/products.component'; 5 | 6 | const routes: Routes = [ 7 | { path: '', component: ProductsComponent} 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [RouterModule.forChild(routes)], 12 | exports: [RouterModule] 13 | }) 14 | export class ProductsRoutingModule { } 15 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { SharedModule } from './../shared/shared.module'; 5 | import { ProductsComponent } from './components/products/products.component'; 6 | import { ProductsRoutingModule } from './products-routing.module'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [ProductsComponent], 11 | imports: [ 12 | CommonModule, 13 | ProductsRoutingModule, 14 | SharedModule 15 | ] 16 | }) 17 | export class ProductsModule { } 18 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/products/services/product.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable, of } from 'rxjs'; 4 | 5 | import { environment } from './../../../environments/environment'; 6 | import { Product } from './../models/product'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class ProductService { 12 | 13 | private products: Product[] = []; 14 | 15 | private readonly BASE_URL = environment.baseUrl; 16 | private readonly API = `${this.BASE_URL}/products`; 17 | private readonly isLocal = environment.isLocal; 18 | 19 | constructor(private http: HttpClient) {} 20 | 21 | load(): Observable { 22 | if (this.isLocal) { 23 | for (let num = 1; num <= 10; num++) { 24 | this.addProducts(num); 25 | } 26 | return of(this.products); 27 | } 28 | return this.http.get(this.API); 29 | } 30 | 31 | create(record: Product): Observable { 32 | return this.http.post(this.API, record); 33 | } 34 | 35 | update(record: Product): Observable { 36 | return this.http.put(`${this.API}/${record.id}`, record); 37 | } 38 | 39 | remove(id: string): Observable { 40 | return this.http.delete(`${this.API}/${id}`); 41 | } 42 | 43 | private addProducts(i: number): void { 44 | this.products.push({ 45 | id: `${i}`, 46 | price: parseFloat((Math.random() * (0.0 - 10.0) + 10.0).toFixed(2)), 47 | status: ['', '', '', 'sale'][Math.floor(Math.random() * 4)], 48 | discounted: ['', '', '', 'discounted'][Math.floor(Math.random() * 4)], 49 | discount: parseFloat((Math.random() * (0.0 - 10.0) + 10.0).toFixed(2)), 50 | name: ['Coffee'][Math.floor(Math.random() * 1)], 51 | description: ['B & W', 'Grey', 'Black', 'Green', 'Black'][Math.floor(Math.random() * 5)], 52 | image: `${i}` 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shared/components/header/header.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 |
6 | JavaNg 7 |
8 |
9 | 14 | 15 | 19 | 20 | 23 | 24 | 28 | 32 | 36 | 37 |
38 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shared/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { Observable, Subject } from 'rxjs'; 3 | 4 | import { ShoppingCartService } from '../../../shopping-cart/shopping-cart.service'; 5 | 6 | @Component({ 7 | selector: 'app-header', 8 | templateUrl: './header.component.html', 9 | styles: [ 10 | ] 11 | }) 12 | export class HeaderComponent implements OnInit, OnDestroy { 13 | 14 | searchQuery = ''; 15 | cartCount$ = new Observable(); 16 | destroySub = new Subject(); 17 | 18 | constructor(private service: ShoppingCartService) { } 19 | 20 | ngOnInit(): void { 21 | this.cartCount$ = this.service.getCartCount(); 22 | } 23 | 24 | ngOnDestroy(): void { 25 | this.destroySub.next(true); 26 | this.destroySub.complete(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shared/services/event-source.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | interface StreamData { 5 | data: string; 6 | } 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class EventSourceService { 12 | 13 | constructor() { } 14 | 15 | observeMessages(url: string): Observable { 16 | return new Observable(obs => { 17 | const es = new EventSource(url); 18 | es.addEventListener('message', (evt: StreamData) => { 19 | // console.log(evt.data); 20 | obs.next(evt.data != null ? JSON.parse(evt.data) : evt.data); 21 | }); 22 | return () => es.close(); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule } from '@angular/router'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { NgModule } from '@angular/core'; 5 | import { FlexLayoutModule } from '@angular/flex-layout'; 6 | import { MatButtonModule } from '@angular/material/button'; 7 | import { MatButtonToggleModule } from '@angular/material/button-toggle'; 8 | import { MatCardModule } from '@angular/material/card'; 9 | import { MatCheckboxModule } from '@angular/material/checkbox'; 10 | import { MatDividerModule } from '@angular/material/divider'; 11 | import { MatExpansionModule } from '@angular/material/expansion'; 12 | import { MatFormFieldModule } from '@angular/material/form-field'; 13 | import { MatIconModule } from '@angular/material/icon'; 14 | import { MatInputModule } from '@angular/material/input'; 15 | import { MatListModule } from '@angular/material/list'; 16 | import { MatMenuModule } from '@angular/material/menu'; 17 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 18 | import { MatSidenavModule } from '@angular/material/sidenav'; 19 | import { MatSliderModule } from '@angular/material/slider'; 20 | import { MatTabsModule } from '@angular/material/tabs'; 21 | import { MatToolbarModule } from '@angular/material/toolbar'; 22 | import { HeaderComponent } from './components/header/header.component'; 23 | 24 | const materialComponents = [ 25 | FlexLayoutModule, 26 | MatToolbarModule, 27 | MatIconModule, 28 | MatButtonModule, 29 | MatMenuModule, 30 | MatCardModule, 31 | MatSidenavModule, 32 | MatCheckboxModule, 33 | MatSliderModule, 34 | MatFormFieldModule, 35 | MatInputModule, 36 | MatDividerModule, 37 | MatListModule, 38 | MatTabsModule, 39 | MatButtonToggleModule, 40 | MatExpansionModule, 41 | MatProgressSpinnerModule 42 | ]; 43 | 44 | @NgModule({ 45 | declarations: [HeaderComponent], 46 | imports: [ 47 | CommonModule, 48 | RouterModule, 49 | ...materialComponents 50 | ], 51 | exports: [ 52 | ...materialComponents, 53 | HeaderComponent, 54 | HttpClientModule 55 | ] 56 | }) 57 | export class SharedModule { } 58 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shopping-cart/checkout-message.ts: -------------------------------------------------------------------------------- 1 | export interface CheckoutMessage { 2 | color: string; 3 | description: string; 4 | date: Date; 5 | } 6 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shopping-cart/checkout/checkout.component.html: -------------------------------------------------------------------------------- 1 |

Order Status

2 |
3 |
4 |
5 |
6 | 7 | 8 |

{{ item.description }}

9 |
10 |
11 |
{{ item.date | date: 'mediumTime' }}
12 |
13 |
14 |
15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shopping-cart/checkout/checkout.component.scss: -------------------------------------------------------------------------------- 1 | .mat-h2, .mat-title, .mat-typography h2 { 2 | font: 500 20px/32px Roboto, "Helvetica Neue", sans-serif; 3 | margin: 0 0 16px; 4 | font-weight: 500; 5 | } 6 | 7 | .mat-blue { 8 | background-color: #2196f3 !important; 9 | color: white !important; 10 | } 11 | .mat-purple { 12 | background-color: #9c27b0 !important; 13 | color: white !important; 14 | } 15 | .mat-pink { 16 | background-color: #e91e63 !important; 17 | color: white !important; 18 | } 19 | .mat-green { 20 | background-color: #4caf50 !important; 21 | color: rgba(0, 0, 0, 0.87) !important; 22 | } 23 | .mat-grey { 24 | background-color: #9e9e9e !important; 25 | color: rgba(0, 0, 0, 0.87) !important; 26 | } 27 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shopping-cart/checkout/checkout.component.ts: -------------------------------------------------------------------------------- 1 | import { ShoppingCartService } from './../shopping-cart.service'; 2 | import { CheckoutMessage } from './../checkout-message'; 3 | import { Component, OnDestroy, OnInit } from '@angular/core'; 4 | import { Subscription } from 'rxjs'; 5 | import { map, take } from 'rxjs/operators'; 6 | 7 | import { environment } from './../../../environments/environment'; 8 | import { EventSourceService } from './../../shared/services/event-source.service'; 9 | 10 | @Component({ 11 | selector: 'app-checkout', 12 | templateUrl: './checkout.component.html', 13 | styleUrls: ['./checkout.component.scss'] 14 | }) 15 | export class CheckoutComponent implements OnInit, OnDestroy { 16 | 17 | private readonly BASE_URL = environment.baseUrl; 18 | private sseStream: Subscription; 19 | messages: CheckoutMessage[] = []; 20 | 21 | private orders = [ 22 | { id: '2facf25d-b1e1-4fd6-9829-d8c7f49e33c4', color: 'blue', description: 'Order Received' }, 23 | { id: '18a1d2ea-3652-4ef0-bbdf-576593d0fd9a', color: 'purple', description: 'Order Confirmed' }, 24 | { 25 | id: '872bbaad-c9ae-4d62-9de7-f9ff3188d56a', 26 | color: 'pink', 27 | description: 'Order Being Prepared' 28 | }, 29 | { id: '7468b388-88d3-469c-9426-9c32b033e92b', color: 'green', description: 'Delivered' } 30 | ]; 31 | 32 | constructor(private sseService: EventSourceService, private cartService: ShoppingCartService) { 33 | this.sseStream = this.sseService 34 | .observeMessages(`${this.BASE_URL}/orders/stream`) 35 | .pipe( 36 | map((message: any) => { 37 | message.date = new Date(); 38 | return message; 39 | }), 40 | take(4) 41 | ) 42 | .subscribe((message: CheckoutMessage) => { 43 | this.messages.push(message); 44 | }); 45 | } 46 | 47 | ngOnInit(): void { 48 | this.cartService.checkout(); 49 | } 50 | 51 | ngOnDestroy(): void { 52 | if (this.sseStream) { 53 | this.sseStream.unsubscribe(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shopping-cart/shopping-cart-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { CheckoutComponent } from './checkout/checkout.component'; 5 | 6 | const routes: Routes = [ 7 | { path: '', component: CheckoutComponent} 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [RouterModule.forChild(routes)], 12 | exports: [RouterModule] 13 | }) 14 | export class ShoppingCartRoutingModule { } 15 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shopping-cart/shopping-cart.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { SharedModule } from './../shared/shared.module'; 5 | import { CheckoutComponent } from './checkout/checkout.component'; 6 | import { ShoppingCartRoutingModule } from './shopping-cart-routing.module'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [CheckoutComponent], 11 | imports: [ 12 | CommonModule, 13 | ShoppingCartRoutingModule, 14 | SharedModule 15 | ] 16 | }) 17 | export class ShoppingCartModule { } 18 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/app/shopping-cart/shopping-cart.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Subject, Observable } from 'rxjs'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class ShoppingCartService { 8 | 9 | private total = 0; 10 | private cartCount$ = new Subject(); 11 | 12 | constructor() { } 13 | 14 | getCartCount(): Observable { 15 | return this.cartCount$.asObservable(); 16 | } 17 | 18 | addProduct(): void { 19 | this.total++; 20 | this.updateCart(); 21 | } 22 | 23 | checkout(): void { 24 | this.total = 0; 25 | this.updateCart(); 26 | } 27 | 28 | private updateCart(): void { 29 | this.cartCount$.next(this.total); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/.gitkeep -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/1.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/10.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/11.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/12.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/13.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/14.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/14.webp -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/15.webp -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/17.webp -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/18.webp -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/2.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/3.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/4.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/5.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/6.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/7.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/8.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/assets/images/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/assets/images/9.jpg -------------------------------------------------------------------------------- /angular-shopping-cart/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | baseUrl: '/api', 4 | isLocal: false 5 | }; 6 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | baseUrl: '/api', 8 | isLocal: true 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/angular-shopping-cart/src/favicon.ico -------------------------------------------------------------------------------- /angular-shopping-cart/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularShoppingCart 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /angular-shopping-cart/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 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /angular-shopping-cart/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/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/styles.scss: -------------------------------------------------------------------------------- 1 | @import "styles/material.variables"; 2 | @import "styles/_app.variables"; 3 | @import "styles/_cards"; 4 | @import "styles/checkout.component"; 5 | 6 | html, body { 7 | height: 100%; 8 | } 9 | body { 10 | font-family: Roboto; 11 | margin: 0px; 12 | padding: 0px; 13 | } 14 | 15 | .pa-1 { 16 | padding: 1rem 1rem !important; 17 | } 18 | 19 | .ma-0 { 20 | margin: 0 0 !important; 21 | } 22 | 23 | .text-sm { font-size: 70% !important; } 24 | .text-md { font-size: 80% !important; } 25 | .text-xl { font-size: 120% !important; } 26 | .mat-text-muted { 27 | color: rgba(0, 0, 0, 0.54) !important; 28 | } 29 | .mat-text-primary { 30 | color: #673AB7 !important; 31 | } 32 | 33 | mat-toolbar { 34 | .notification-label { 35 | position: absolute; 36 | top: 0; 37 | left: 50%; 38 | font-size: 12px; 39 | font-weight: 700; 40 | line-height: 13px; 41 | border-radius: 50%; 42 | width: 13px; 43 | height: 13px; 44 | background-color: #f44336; 45 | border: 4px solid #f44336; 46 | color: white; 47 | text-align: center; 48 | } 49 | &.main-header { 50 | padding: 0 8px; 51 | position: relative; 52 | box-shadow: 0 1px 8px rgba(0,0,0,.3); 53 | z-index: 9; 54 | .branding { 55 | display: -webkit-box; 56 | display: -moz-box; 57 | display: -webkit-flex; 58 | display: -ms-flexbox; 59 | display: flex; 60 | overflow: hidden; 61 | text-overflow: ellipsis; 62 | white-space: nowrap; 63 | margin: auto 0; 64 | line-height: 50px; 65 | padding: 0 64px 0 16px; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/styles/_app.variables.scss: -------------------------------------------------------------------------------- 1 | // 2 | // General 3 | // 4 | $transition-duration: 300ms !default; 5 | $gutter: 1rem !default; 6 | 7 | // 8 | // Widths 9 | // 10 | $sidebar-width: 15rem !default; 11 | $compact-sidebar-width: 100px !default; 12 | $collapsed-sidebar-width: 80px !default; 13 | $sidebar-width-mobile: 18rem !default; 14 | 15 | // 16 | // Typography 17 | // 18 | $font-size-base: 0.875rem !default; 19 | $font-weight-base: 400 !default; 20 | $font-weight-medium: 500 !default; 21 | $font-weight-bold: 600 !default; 22 | 23 | $font-size-h1: 2.5rem !default; 24 | $font-size-h2: 2rem !default; 25 | $font-size-h3: 1.75rem !default; 26 | $font-size-h4: 1.5rem !default; 27 | $font-size-h5: 1.25rem !default; 28 | $font-size-h6: 1rem !default; 29 | 30 | $headings-margin-bottom: (1rem / 2) !default; 31 | $headings-font-family: inherit !default; 32 | $headings-font-weight: 400 !default; 33 | $headings-line-height: 1.1 !default; 34 | $headings-color: inherit !default; 35 | 36 | // 37 | // Radius 38 | // 39 | $border-radius-base: 2px !default; 40 | $border-radius-large: 2px !default; 41 | $border-radius-small: 1px !default; 42 | 43 | // 44 | // Spacing 45 | // 46 | $spacer: 1rem !default; 47 | $spacer-x: $spacer !default; 48 | $spacer-y: $spacer !default; 49 | $spacers: ( 50 | 0: ( 51 | x: 0, 52 | y: 0 53 | ), 54 | xs: ( 55 | x: ($spacer-x / 1.8), 56 | y: ($spacer-y / 1.8) 57 | ), 58 | 1: ( 59 | x: $spacer-x, 60 | y: $spacer-y 61 | ), 62 | 2: ( 63 | x: ($spacer-x * 1.5), 64 | y: ($spacer-y * 1.5) 65 | ), 66 | 3: ( 67 | x: ($spacer-x * 3), 68 | y: ($spacer-y * 3) 69 | ) 70 | ) !default; 71 | $border-width: 1px !default; 72 | 73 | 74 | // Media queries breakpoints 75 | // -------------------------------------------------- 76 | 77 | // Grid breakpoints 78 | // 79 | // Define the minimum and maximum dimensions at which your layout will change, 80 | // adapting to different screen sizes, for use in media queries. 81 | $grid-breakpoints: ( 82 | // Extra small screen / phone 83 | xs: 0, 84 | // Small screen / phone 85 | sm: 600px, 86 | // Medium screen / tablet 87 | md: 960px, 88 | // Large screen / desktop 89 | lg: 1280px, 90 | // Extra large screen / wide desktop 91 | xl: 1920px 92 | ) !default; 93 | 94 | 95 | // Grid containers 96 | // 97 | // Define the maximum width of `.container` for different screen sizes. 98 | $container-max-widths: ( 99 | sm: 576px, 100 | md: 720px, 101 | lg: 940px, 102 | xl: 1140px 103 | ) !default; 104 | 105 | 106 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/styles/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | // Breakpoint viewport sizes and media queries. 2 | // 3 | // Breakpoints are defined as a map of (name: minimum width), order from small to large: 4 | // 5 | // (xs: 0, sm: 544px, md: 768px) 6 | // 7 | // The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default. 8 | 9 | // Name of the next breakpoint, or null for the last breakpoint. 10 | // 11 | // >> breakpoint-next(sm) 12 | // md 13 | // >> breakpoint-next(sm, (xs: 0, sm: 544px, md: 768px)) 14 | // md 15 | // >> breakpoint-next(sm, $breakpoint-names: (xs sm md)) 16 | // md 17 | @function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) { 18 | $n: index($breakpoint-names, $name); 19 | @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null); 20 | } 21 | 22 | // Minimum breakpoint width. Null for the smallest (first) breakpoint. 23 | // 24 | // >> breakpoint-min(sm, (xs: 0, sm: 544px, md: 768px)) 25 | // 544px 26 | @function breakpoint-min($name, $breakpoints: $grid-breakpoints) { 27 | $min: map-get($breakpoints, $name); 28 | @return if($min != 0, $min, null); 29 | } 30 | 31 | // Maximum breakpoint width. Null for the largest (last) breakpoint. 32 | // The maximum value is calculated as the minimum of the next one less 0.1. 33 | // 34 | // >> breakpoint-max(sm, (xs: 0, sm: 544px, md: 768px)) 35 | // 767px 36 | @function breakpoint-max($name, $breakpoints: $grid-breakpoints) { 37 | $next: breakpoint-next($name, $breakpoints); 38 | @return if($next, breakpoint-min($next, $breakpoints) - 1px, null); 39 | } 40 | 41 | // Media of at least the minimum breakpoint width. No query for the smallest breakpoint. 42 | // Makes the @content apply to the given breakpoint and wider. 43 | @mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { 44 | $min: breakpoint-min($name, $breakpoints); 45 | @if $min { 46 | @media (min-width: $min) { 47 | @content; 48 | } 49 | } @else { 50 | @content; 51 | } 52 | } 53 | 54 | // Media of at most the maximum breakpoint width. No query for the largest breakpoint. 55 | // Makes the @content apply to the given breakpoint and narrower. 56 | @mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) { 57 | $max: breakpoint-max($name, $breakpoints); 58 | @if $max { 59 | @media (max-width: $max) { 60 | @content; 61 | } 62 | } @else { 63 | @content; 64 | } 65 | } 66 | 67 | // Media between the breakpoint's minimum and maximum widths. 68 | // No minimum for the smallest breakpoint, and no maximum for the largest one. 69 | // Makes the @content apply only to the given breakpoint, not viewports any wider or narrower. 70 | @mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) { 71 | @include media-breakpoint-up($name, $breakpoints) { 72 | @include media-breakpoint-down($name, $breakpoints) { 73 | @content; 74 | } 75 | } 76 | } 77 | 78 | // Media that spans multiple breakpoint widths. 79 | // Makes the @content apply between the min and max breakpoints 80 | @mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) { 81 | @include media-breakpoint-up($lower, $breakpoints) { 82 | @include media-breakpoint-down($upper, $breakpoints) { 83 | @content; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/styles/_cards.scss: -------------------------------------------------------------------------------- 1 | /* $cards 2 | ------------------------------------------*/ 3 | $mat-card-header-size: 40px !default; 4 | body { 5 | .mat-card { 6 | padding: 0; 7 | margin: ($gutter/3); 8 | border-radius: $border-radius-base; 9 | @include mat-elevation(1); 10 | color: rgba(mat-color($foreground, base), 0.87); 11 | .mat-card-header { 12 | height: auto; 13 | } 14 | > :first-child { 15 | border-radius: $border-radius-base $border-radius-base 0 0; 16 | } 17 | > :last-child { 18 | border-radius: 0 0 $border-radius-base $border-radius-base; 19 | } 20 | [matCardAvatar] { 21 | height: $mat-card-header-size; 22 | width: $mat-card-header-size; 23 | border-radius: 50%; 24 | } 25 | [matCardAvatar] { 26 | font-size: 40px; 27 | line-height: 40px; 28 | margin: $gutter 0 0 $gutter; 29 | } 30 | .mat-card-image { 31 | width: 100%; 32 | margin: 0; 33 | } 34 | .mat-card-image:first-child { 35 | margin-top: 0; 36 | border-radius: $border-radius-base $border-radius-base 0 0; 37 | } 38 | .mat-card-title { 39 | padding-top: $gutter; 40 | padding-left: $gutter; 41 | padding-right: $gutter; 42 | line-height: 1; 43 | font-size: 16px; 44 | font-weight: 400; 45 | } 46 | .mat-card-subtitle { 47 | padding-left: $gutter; 48 | padding-right: $gutter; 49 | line-height: 1; 50 | font-size: 13px; 51 | } 52 | .mat-card-subtitle:first-child { 53 | padding-top: $gutter; 54 | } 55 | .mat-card-title:nth-child(2) { 56 | margin-top: -24px; 57 | } 58 | .mat-card-content { 59 | padding: $gutter; 60 | margin-bottom: 0; 61 | position: relative; 62 | } 63 | [mat-fab-card-float] { 64 | top: -36px; 65 | position: absolute; 66 | right: 8px; 67 | } 68 | .mat-card-actions, 69 | .mat-card-actions:last-child { 70 | padding: $gutter / 2; 71 | margin: 0; 72 | } 73 | &.mat-card { 74 | padding: 0; 75 | } 76 | [mat-card-float-icon] { 77 | position: absolute; 78 | right: 15px; 79 | top: 50%; 80 | margin-top: -20px; 81 | width: 40px; 82 | height: 40px; 83 | .material-icons { 84 | font-size: 40px; 85 | opacity: .2; 86 | transform: rotate(-5deg); 87 | } 88 | } 89 | [mat-card-widget] { 90 | height: auto; 91 | display: flex; 92 | flex-direction: row; 93 | -webkit-box-align: center; 94 | -webkit-align-items: center; 95 | -ms-grid-row-align: center; 96 | align-items: center; 97 | -webkit-align-content: center; 98 | align-content: center; 99 | max-width: 100%; 100 | padding: $gutter; 101 | [mat-card-widget-title], p { 102 | margin: 0; 103 | padding: 0; 104 | line-height: $headings-line-height!important; 105 | } 106 | } 107 | .card-image-header { 108 | position: relative; 109 | background-size: cover; 110 | background-position: center bottom; 111 | background-repeat: no-repat; 112 | width: 100%; 113 | img { 114 | border-radius: $border-radius-base $border-radius-base 0 0; 115 | display: block; 116 | max-width: 100%; 117 | } 118 | } 119 | &.card-widget { 120 | .card-widget-content { 121 | display: flex; 122 | flex-direction: row; 123 | height: 40px; 124 | margin: -($gutter/2) 0 $gutter 0; 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | // ------------------------------------------ 2 | // left to right variables to be used by bi-app mixins 3 | // authors: 4 | // twitter.com/anasnakawa 5 | // twitter.com/victorzamfir 6 | // licensed under the MIT license 7 | // http://www.opensource.org/licenses/mit-license.php 8 | // ------------------------------------------ 9 | 10 | // namespacing variables with bi-app to 11 | // avoid conflicting with other global variables 12 | $bi-app-left : left; 13 | $bi-app-right : right; 14 | $bi-app-direction : ltr; 15 | $bi-app-invert-direction : rtl; 16 | $imp : !important; 17 | 18 | // ------------------------------------------ 19 | // bi app mixins 20 | // authors: 21 | // twitter.com/anasnakawa 22 | // twitter.com/victorzamfir 23 | // licensed under the MIT license 24 | // http://www.opensource.org/licenses/mit-license.php 25 | // ------------------------------------------ 26 | 27 | // ------------------------------------------ 28 | // Table of contents 29 | // ------------------------------------------ 30 | // padding 31 | // margin 32 | // float 33 | // text align 34 | // clear 35 | // left / right 36 | // border 37 | // - width 38 | // - style 39 | // - color 40 | // - generic 41 | // - radius 42 | // ltr / rtl contents 43 | // ------------------------------------------ 44 | 45 | // generic mixin for properties with values 46 | // (top right bottom left) 47 | // ------------------------------------------ 48 | @mixin bi-app-compact($property, $top, $right, $bottom, $left) { 49 | @if $bi-app-direction == ltr { 50 | #{$property}: $top $right $bottom $left; 51 | } @else { 52 | #{$property}: $top $left $bottom $right; 53 | } 54 | } 55 | 56 | // padding 57 | // ------------------------------------------ 58 | @mixin padding-left($distance) { 59 | padding-#{$bi-app-left}: $distance; 60 | } 61 | 62 | @mixin padding-right($distance) { 63 | padding-#{$bi-app-right}: $distance; 64 | } 65 | 66 | @mixin padding($top, $right, $bottom, $left) { 67 | @include bi-app-compact(padding, $top, $right, $bottom, $left); 68 | } 69 | 70 | // margin 71 | // ------------------------------------------ 72 | @mixin margin-left($distance) { 73 | margin-#{$bi-app-left}: $distance; 74 | } 75 | 76 | @mixin margin-right($distance) { 77 | margin-#{$bi-app-right}: $distance; 78 | } 79 | 80 | @mixin margin($top, $right, $bottom, $left) { 81 | @include bi-app-compact(margin, $top, $right, $bottom, $left); 82 | } 83 | 84 | // float 85 | // ------------------------------------------ 86 | @mixin bi-app-float-left($important: '') { 87 | float: $bi-app-left unquote($important); 88 | } 89 | 90 | @mixin bi-app-float-right($important: '') { 91 | float: $bi-app-right unquote($important); 92 | } 93 | 94 | @mixin float($direction, $important: '') { 95 | @if $direction == left { 96 | @include bi-app-float-left($important); 97 | } @else if $direction == right { 98 | @include bi-app-float-right($important); 99 | } @else { 100 | float: $direction; 101 | } 102 | } 103 | 104 | // text align 105 | // ------------------------------------------ 106 | @mixin bi-app-text-align-left { 107 | text-align: $bi-app-left; 108 | } 109 | 110 | @mixin bi-app-text-align-right { 111 | text-align: $bi-app-right; 112 | } 113 | 114 | @mixin text-align($direction) { 115 | @if $direction == left { 116 | @include bi-app-text-align-left; 117 | } @else if $direction == right { 118 | @include bi-app-text-align-right; 119 | } @else { 120 | text-align: $direction; 121 | } 122 | } 123 | 124 | // clear 125 | // ------------------------------------------ 126 | @mixin bi-app-clear-left { 127 | clear: $bi-app-left; 128 | } 129 | 130 | @mixin bi-app-clear-right { 131 | clear: $bi-app-right; 132 | } 133 | 134 | @mixin clear($direction) { 135 | @if $direction == left { 136 | @include bi-app-clear-left; 137 | } @else if $direction == right { 138 | @include bi-app-clear-right; 139 | } @else { 140 | clear: $direction; 141 | } 142 | } 143 | 144 | // left / right 145 | // ------------------------------------------ 146 | @mixin left($distance) { 147 | @if $bi-app-direction == ltr { 148 | left: $distance; 149 | } @else if $bi-app-direction == rtl { 150 | right: $distance; 151 | } 152 | } 153 | 154 | @mixin right($distance) { 155 | @if $bi-app-direction == ltr { 156 | right: $distance; 157 | } @else if $bi-app-direction == rtl { 158 | left: $distance; 159 | } 160 | } 161 | 162 | // border 163 | // ------------------------------------------ 164 | 165 | // width 166 | @mixin border-left-width($width) { 167 | border-#{$bi-app-left}-width: $width; 168 | } 169 | 170 | @mixin border-right-width($width) { 171 | border-#{$bi-app-right}-width: $width; 172 | } 173 | 174 | @mixin border-width($top, $right, $bottom, $left) { 175 | @include bi-app-compact(border-width, $top, $right, $bottom, $left); 176 | } 177 | 178 | // style 179 | @mixin border-left-style($style) { 180 | border-#{$bi-app-left}-style: $style; 181 | } 182 | 183 | @mixin border-right-style($style) { 184 | border-#{$bi-app-right}-style: $style; 185 | } 186 | 187 | @mixin border-style($top, $right, $bottom, $left) { 188 | @include bi-app-compact(border-style, $top, $right, $bottom, $left); 189 | } 190 | 191 | // color 192 | @mixin border-left-color($color) { 193 | border-#{$bi-app-left}-color: $color; 194 | } 195 | 196 | @mixin border-right-color($color) { 197 | border-#{$bi-app-right}-color: $color; 198 | } 199 | 200 | @mixin border-color($top, $right, $bottom, $left) { 201 | @include bi-app-compact(border-color, $top, $right, $bottom, $left); 202 | } 203 | 204 | // generic 205 | @mixin border-left($border-style) { 206 | border-#{$bi-app-left}: $border-style; 207 | } 208 | 209 | @mixin border-right($border-style) { 210 | border-#{$bi-app-right}: $border-style; 211 | } 212 | 213 | // radius 214 | @mixin border-top-left-radius($radius) { 215 | -webkit-border-top-#{$bi-app-left}-radius: $radius; 216 | -moz-border-top#{$bi-app-left}-radius: $radius; 217 | border-top-#{$bi-app-left}-radius: $radius; 218 | } 219 | 220 | @mixin border-top-right-radius($radius) { 221 | -webkit-border-top-#{$bi-app-right}-radius: $radius; 222 | -moz-border-top#{$bi-app-right}-radius: $radius; 223 | border-top-#{$bi-app-right}-radius: $radius; 224 | } 225 | 226 | @mixin border-bottom-left-radius($radius) { 227 | -webkit-border-bottom-#{$bi-app-left}-radius: $radius; 228 | -moz-border-bottom#{$bi-app-left}-radius: $radius; 229 | border-bottom-#{$bi-app-left}-radius: $radius; 230 | } 231 | 232 | @mixin border-bottom-right-radius($radius) { 233 | -webkit-border-bottom-#{$bi-app-right}-radius: $radius; 234 | -moz-border-bottom#{$bi-app-right}-radius: $radius; 235 | border-bottom-#{$bi-app-right}-radius: $radius; 236 | } 237 | 238 | @mixin border-right-radius($radius) { 239 | @include border-top-right-radius($radius); 240 | @include border-bottom-right-radius($radius); 241 | } 242 | 243 | @mixin border-left-radius($radius) { 244 | @include border-top-left-radius($radius); 245 | @include border-bottom-left-radius($radius); 246 | } 247 | 248 | @mixin border-top-radius($radius) { 249 | @include border-top-left-radius($radius); 250 | @include border-top-right-radius($radius); 251 | } 252 | 253 | @mixin border-bottom-radius($radius) { 254 | @include border-bottom-left-radius($radius); 255 | @include border-bottom-right-radius($radius); 256 | } 257 | 258 | @mixin border-radius($topLeft, $topRight: null, $bottomRight: null, $bottomLeft: null) { 259 | @if $topRight != null { 260 | @include border-top-left-radius($topLeft); 261 | @include border-top-right-radius($topRight); 262 | @include border-bottom-right-radius($bottomRight); 263 | @include border-bottom-left-radius($bottomLeft); 264 | } @else { 265 | -webkit-border-radius: $topLeft; 266 | -moz-border-radius: $topLeft; 267 | -ms-border-radius: $topLeft; 268 | -o-border-radius: $topLeft; 269 | border-radius: $topLeft; 270 | } 271 | } 272 | 273 | // Returns "en" or "ar", useful for image suffixes. 274 | // Usage: background-image: url(/img/header-#{lang()}.png); 275 | @function lang() { 276 | @if $bi-app-direction == ltr { 277 | @return 'en'; 278 | } @else { 279 | @return 'ar'; 280 | } 281 | } 282 | 283 | // Support for "direction" declaration (renders ltr/rtl). 284 | // Useful for form elements as they swap the text-indent property and align the text accordingly. 285 | @mixin direction { 286 | direction: $bi-app-direction; 287 | } 288 | 289 | // Inverts a percentage value. Example: 97% becames 3%. 290 | // Useful for background-position. 291 | @function bi-app-invert-percentage($percentage) { 292 | @if $bi-app-direction == rtl { 293 | @return 100% - $percentage; 294 | } @else { 295 | @return $percentage; 296 | } 297 | } 298 | 299 | // ltr / rtl contents 300 | // ------------------------------------------ 301 | @mixin ltr { 302 | @if $bi-app-direction == ltr { 303 | @content; 304 | } 305 | } 306 | 307 | @mixin rtl { 308 | @if $bi-app-direction == rtl { 309 | @content; 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/styles/checkout.component.scss: -------------------------------------------------------------------------------- 1 | @import "material.variables"; 2 | @import "app.variables"; 3 | @import "breakpoints"; 4 | @import "mixins"; 5 | 6 | $gutter: 1rem !default; 7 | 8 | @mixin rounded($radius: 2px) { 9 | border-radius: $radius; 10 | } 11 | 12 | @mixin clearfix() { 13 | &::after { 14 | content: ""; 15 | display: table; 16 | clear: both; 17 | } 18 | } 19 | 20 | .timeline { 21 | position: relative; 22 | margin: ($gutter/3); 23 | &::before { 24 | position: absolute; 25 | top: 6px; 26 | width: 4px; 27 | height: 100%; 28 | content: ''; 29 | background: mat-color($background, card); 30 | @include left(4px); 31 | } 32 | .timeline-icon { 33 | position: absolute; 34 | top: 15px; 35 | width: 12px; 36 | height: 12px; 37 | text-align: center; 38 | @include left(0); 39 | @include rounded(50%); 40 | &::after { 41 | content: ''; 42 | position: absolute; 43 | top: 2px; 44 | @include left(2px); 45 | width: 12px; 46 | height: 12px; 47 | @include rounded(50%); 48 | // background: white; 49 | } 50 | } 51 | .timeline-card { 52 | position: relative; 53 | margin: 40px 0; 54 | @include clearfix(); 55 | &:first-child .timeline-icon { 56 | top: 0; 57 | } 58 | &:last-child .timeline-icon { 59 | top: 0; 60 | } 61 | } 62 | .timeline-card:first-child { 63 | margin-top: 0; 64 | } 65 | .timeline-content { 66 | position: relative; 67 | @include margin-left(30px); 68 | @include clearfix(); 69 | > .mat-card { 70 | @include float(left); 71 | display: inline-block; 72 | margin: 0; 73 | } 74 | } 75 | .timeline-content .timeline-date { 76 | display: inline-block; 77 | padding: 4px 0 10px; 78 | } 79 | .timeline-content > .mat-card::before { 80 | position: absolute; 81 | top: 11px; 82 | width: 0; 83 | height: 0; 84 | content: ' '; 85 | pointer-events: none; 86 | border-width: 10px; 87 | border-style: solid; 88 | @include right(100%); 89 | @include border-color(transparent, rgba(150, 150, 150, .2), transparent, transparent); 90 | } 91 | .timeline-content > .mat-card::after { 92 | position: absolute; 93 | top: 12px; 94 | width: 0; 95 | height: 0; 96 | content: ' '; 97 | pointer-events: none; 98 | border-width: 9px; 99 | border-style: solid; 100 | @include right(100%); 101 | @include border-color(transparent, mat-color($background, card), transparent, transparent); 102 | } 103 | } 104 | 105 | @include media-breakpoint-up(lg) { 106 | .timeline:not(.stacked) { 107 | &::before { 108 | @include left(50%); 109 | @include margin-left(-2px); 110 | } 111 | .timeline-card:nth-child(odd) .timeline-content { 112 | @include float(right); 113 | } 114 | .timeline-card:nth-child(even) .timeline-content { 115 | @include float(left); 116 | } 117 | .timeline-card:nth-child(even) .timeline-content > .mat-card { 118 | @include float(right); 119 | } 120 | .timeline-card:nth-child(even) .timeline-content > .mat-card::before { 121 | position: absolute; 122 | @include right(auto); 123 | @include left(100%); 124 | @include border-color(transparent, transparent, transparent, rgba(150, 150, 150, .2)); 125 | } 126 | .timeline-card:nth-child(even) .timeline-content > .mat-card::after { 127 | position: absolute; 128 | @include right(auto); 129 | @include left(100%); 130 | @include border-color(transparent, transparent, transparent, mat-color($background, card)); 131 | } 132 | .timeline-card:nth-child(odd) .timeline-content .timeline-date { 133 | @include right(112%); 134 | @include left(auto); 135 | @include text-align(right); 136 | } 137 | .timeline-card:first-child { 138 | margin-top: 0; 139 | } 140 | .timeline-icon { 141 | @include left(50%); 142 | @include margin-left(-6px); 143 | } 144 | .timeline-content { 145 | width: 47%; 146 | @include margin-left(0); 147 | } 148 | } 149 | .timeline .timeline-date { 150 | position: absolute; 151 | top: 7px; 152 | width: 100%; 153 | @include left(112%); 154 | } 155 | .timeline.stacked { 156 | .timeline-content > .mat-card { 157 | margin-bottom: 0; 158 | } 159 | .timeline-date { 160 | position: relative; 161 | top: 0; 162 | width: 100%; 163 | @include left(auto); 164 | } 165 | } 166 | } 167 | @include media-breakpoint-down(md) { 168 | .timeline { 169 | .timeline-content { 170 | margin-left: 30px; 171 | } 172 | .timeline-content > .mat-card { 173 | @include float(left); 174 | } 175 | .timeline-content > .mat-card { 176 | margin-bottom: 0; 177 | } 178 | .timeline-date { 179 | position: relative; 180 | top: 0; 181 | width: 100%; 182 | @include left(auto); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /angular-shopping-cart/src/styles/material.variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Custom Theming for Angular Material 3 | // For more information: https://material.angular.io/guide/theming 4 | @import '~@angular/material/theming'; 5 | // Plus imports for other components in your app. 6 | 7 | // Include the common styles for Angular Material. We include this here so that you only 8 | // have to load a single css file for Angular Material in your app. 9 | // Be sure that you only ever include this mixin once! 10 | @include mat-core(); 11 | 12 | // Define the palettes for your theme using the Material Design palettes available in palette.scss 13 | // (imported above). For each palette, you can optionally specify a default, lighter, and darker 14 | // hue. Available color palettes: https://material.io/design/color/ 15 | $angular-shopping-cart-primary: mat-palette($mat-cyan, 800, 800); 16 | $angular-shopping-cart-accent: mat-palette($mat-teal, 400); 17 | 18 | // The warn palette is optional (defaults to red). 19 | $angular-shopping-cart-warn: mat-palette($mat-red); 20 | 21 | // Create the theme object. A theme consists of configurations for individual 22 | // theming systems such as "color" or "typography". 23 | $angular-shopping-cart-theme: mat-light-theme(( 24 | color: ( 25 | primary: $angular-shopping-cart-primary, 26 | accent: $angular-shopping-cart-accent, 27 | warn: $angular-shopping-cart-warn, 28 | ) 29 | )); 30 | 31 | // Include theme styles for core and each component used in your app. 32 | // Alternatively, you can import and @include the theme mixins for each component 33 | // that you are using. 34 | @include angular-material-theme($angular-shopping-cart-theme); 35 | 36 | /* You can add global styles to this file, and also import other style files */ 37 | $foreground: map-get($angular-shopping-cart-theme, foreground); 38 | $background: map-get($angular-shopping-cart-theme, background); 39 | -------------------------------------------------------------------------------- /angular-shopping-cart/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/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /angular-shopping-cart/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /angular-shopping-cart/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "sourceMap": true, 12 | "declaration": false, 13 | "downlevelIteration": true, 14 | "experimentalDecorators": true, 15 | "moduleResolution": "node", 16 | "importHelpers": true, 17 | "target": "es2015", 18 | "module": "es2020", 19 | "lib": [ 20 | "es2018", 21 | "dom" 22 | ] 23 | }, 24 | "angularCompilerOptions": { 25 | "strictInjectionParameters": true, 26 | "strictTemplates": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /angular-shopping-cart/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. 3 | It is not intended to be used to perform a compilation. 4 | 5 | To learn more about this file see: https://angular.io/config/solution-tsconfig. 6 | */ 7 | { 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | }, 16 | { 17 | "path": "./e2e/tsconfig.json" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /angular-shopping-cart/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /angular-shopping-cart/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "app", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "app", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-any": true, 59 | "no-console": [ 60 | true, 61 | "debug", 62 | "info", 63 | "time", 64 | "timeEnd", 65 | "trace" 66 | ], 67 | "no-empty": false, 68 | "no-inferrable-types": [ 69 | true, 70 | "ignore-params" 71 | ], 72 | "no-non-null-assertion": true, 73 | "no-redundant-jsdoc": true, 74 | "no-switch-case-fall-through": true, 75 | "no-var-requires": false, 76 | "object-literal-key-quotes": [ 77 | true, 78 | "as-needed" 79 | ], 80 | "quotemark": [ 81 | true, 82 | "single" 83 | ], 84 | "semicolon": { 85 | "options": [ 86 | "always" 87 | ] 88 | }, 89 | "space-before-function-paren": { 90 | "options": { 91 | "anonymous": "never", 92 | "asyncArrow": "always", 93 | "constructor": "never", 94 | "method": "never", 95 | "named": "never" 96 | } 97 | }, 98 | "typedef": [ 99 | true, 100 | "call-signature" 101 | ], 102 | "typedef-whitespace": { 103 | "options": [ 104 | { 105 | "call-signature": "nospace", 106 | "index-signature": "nospace", 107 | "parameter": "nospace", 108 | "property-declaration": "nospace", 109 | "variable-declaration": "nospace" 110 | }, 111 | { 112 | "call-signature": "onespace", 113 | "index-signature": "onespace", 114 | "parameter": "onespace", 115 | "property-declaration": "onespace", 116 | "variable-declaration": "onespace" 117 | } 118 | ] 119 | }, 120 | "variable-name": { 121 | "options": [ 122 | "ban-keywords", 123 | "check-format", 124 | "allow-pascal-case" 125 | ] 126 | }, 127 | "whitespace": { 128 | "options": [ 129 | "check-branch", 130 | "check-decl", 131 | "check-operator", 132 | "check-separator", 133 | "check-type", 134 | "check-typecast" 135 | ] 136 | }, 137 | "no-conflicting-lifecycle": true, 138 | "no-host-metadata-property": true, 139 | "no-input-rename": true, 140 | "no-inputs-metadata-property": true, 141 | "no-output-native": true, 142 | "no-output-on-prefix": true, 143 | "no-output-rename": true, 144 | "no-outputs-metadata-property": true, 145 | "template-banana-in-box": true, 146 | "template-no-negated-async": true, 147 | "use-lifecycle-interface": true, 148 | "use-pipe-transform-interface": true 149 | }, 150 | "rulesDirectory": [ 151 | "codelyzer" 152 | ] 153 | } -------------------------------------------------------------------------------- /spring-shopping-cart/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /spring-shopping-cart/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiane/reactive-spring-angular/388c4691e00988da301a524fd4ee474169f0ab92/spring-shopping-cart/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-shopping-cart/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /spring-shopping-cart/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "java", 5 | "name": "Spring Boot-SpringShoppingCartApplication", 6 | "request": "launch", 7 | "cwd": "${workspaceFolder}", 8 | "console": "internalConsole", 9 | "mainClass": "com.loiane.springshoppingcart.SpringShoppingCartApplication", 10 | "projectName": "spring-shopping-cart", 11 | "args": "" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /spring-shopping-cart/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "interactive" 3 | } -------------------------------------------------------------------------------- /spring-shopping-cart/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:14-alpine 2 | COPY ./target/spring-shopping-cart-1.0-SNAPSHOT.jar /usr/src/app/ 3 | WORKDIR /usr/src/app 4 | EXPOSE 8080 5 | CMD ["java", "-jar", "spring-shopping-cart-1.0-SNAPSHOT.jar"] 6 | 7 | # docker build -t spring-shopping-cart -f Dockerfile . 8 | # docker run -p 8080:8080 spring-shopping-cart 9 | # docker-compose up 10 | 11 | # lsof -n -i4TCP:8080 12 | # kill -9 P 13 | -------------------------------------------------------------------------------- /spring-shopping-cart/Dockerfile-build: -------------------------------------------------------------------------------- 1 | FROM maven:3.6.3-jdk-14 2 | WORKDIR /usr/src/java-code 3 | COPY . /usr/src/java-code/ 4 | RUN mvn package 5 | 6 | WORKDIR /usr/src/java-app 7 | RUN cp /usr/src/java-code/target/*.jar ./app.jar 8 | EXPOSE 8080 9 | CMD ["java", "-jar", "app.jar"] 10 | 11 | ## docker-compose -f docker-compose.build.yml build 12 | ## docker-compose -f docker-compose.build.yml up -------------------------------------------------------------------------------- /spring-shopping-cart/docker-compose.build.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | spring-shopping-cart: 5 | image: spring-shopping-cart 6 | build: 7 | context: ./ 8 | dockerfile: Dockerfile-build 9 | networks: 10 | - network1 11 | environment: 12 | PORT: 8080 13 | ports: 14 | - 8080:8080 15 | 16 | networks: 17 | network1: -------------------------------------------------------------------------------- /spring-shopping-cart/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | spring-shopping-cart: 5 | image: spring-shopping-cart 6 | build: . 7 | networks: 8 | - network1 9 | environment: 10 | PORT: 8080 11 | ports: 12 | - 8080:8080 13 | 14 | networks: 15 | network1: -------------------------------------------------------------------------------- /spring-shopping-cart/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtime":{ 3 | "majorVersion": "8" 4 | }, 5 | "command": "java -jar spring-shopping-cart-1.0-SNAPSHOT.jar", 6 | "release": { 7 | "build": "20170113Build", 8 | "commit": "commitString", 9 | "version": "20170113Version" 10 | }, 11 | "notes": "REST app for testing" 12 | } -------------------------------------------------------------------------------- /spring-shopping-cart/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /spring-shopping-cart/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /spring-shopping-cart/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.loiane 7 | spring-shopping-cart 8 | 1.0-SNAPSHOT 9 | jar 10 | 11 | spring-shopping-cart 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.3.0.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 14 25 | ./../angular-shopping-cart 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-mongodb-reactive 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-webflux 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-devtools 44 | runtime 45 | 46 | 47 | org.projectlombok 48 | lombok 49 | true 50 | 51 | 52 | de.flapdoodle.embed 53 | de.flapdoodle.embed.mongo 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-test 58 | test 59 | 60 | 61 | io.projectreactor 62 | reactor-test 63 | test 64 | 65 | 66 | org.junit.jupiter 67 | junit-jupiter-engine 68 | 5.6.2 69 | test 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-compiler-plugin 78 | 79 | 15 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-surefire-plugin 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-maven-plugin 89 | 90 | 91 | 92 | 133 | 134 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/assembly/distribution.xml: -------------------------------------------------------------------------------- 1 | 4 | dist 5 | 6 | zip 7 | 8 | false 9 | 10 | 11 | ${project.basedir} 12 | 13 | manifest.json 14 | 15 | 16 | 17 | ${project.build.directory} 18 | \ 19 | 20 | spring-shopping-cart-1.0-SNAPSHOT.jar 21 | 22 | 23 | *.zip 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/java/com/loiane/springshoppingcart/SpringShoppingCartApplication.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart; 2 | 3 | import com.loiane.springshoppingcart.model.Order; 4 | import com.loiane.springshoppingcart.model.Product; 5 | import com.loiane.springshoppingcart.repository.OrderRepository; 6 | import com.loiane.springshoppingcart.repository.ProductRepository; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.context.annotation.Bean; 11 | import reactor.core.publisher.Flux; 12 | 13 | import java.time.Duration; 14 | import java.util.UUID; 15 | 16 | @SpringBootApplication 17 | public class SpringShoppingCartApplication { 18 | 19 | public static void main(String[] args) { 20 | SpringApplication.run(SpringShoppingCartApplication.class, args); 21 | } 22 | 23 | @Bean 24 | CommandLineRunner init(ProductRepository repository) { 25 | return args -> { 26 | repository 27 | .deleteAll() 28 | .subscribe(null, null, () -> { 29 | Flux.interval(Duration.ofSeconds(1)) 30 | .take(11) 31 | .map(i -> i.intValue() + 1) 32 | .map(i -> { 33 | Product p = new Product(); 34 | p.setId(UUID.randomUUID().toString()); 35 | p.setName("Coffee " + i); 36 | p.setDescription("Coffee"); 37 | p.setPrice(i + 1.50); 38 | p.setDiscount(0.7); 39 | if (i % 3 == 0) { 40 | p.setStatus("sale"); 41 | p.setDiscounted("discounted"); 42 | } 43 | p.setImage(i.toString()); 44 | return p; 45 | }) 46 | .map(record -> repository.save(record) 47 | .subscribe(System.out::println)) 48 | .subscribe(); 49 | }); 50 | }; 51 | } 52 | 53 | @Bean 54 | CommandLineRunner initOrder(OrderRepository repository) { 55 | 56 | String[] colors = {"blue", "purple", "pink", "green"}; 57 | String[] status = {"Order Received", "Order Confirmed", "Order Being Prepared", "Delivered"}; 58 | 59 | return args -> { 60 | repository 61 | .deleteAll() 62 | .subscribe(null, null, () -> { 63 | Flux.interval(Duration.ofSeconds(1)) 64 | .take(4) 65 | .map(i -> i.intValue() + 1) 66 | .map(i -> new Order(UUID.randomUUID().toString(), colors[i-1], status[i-1])) 67 | .map(record -> repository.save(record) 68 | .subscribe(System.out::println)) 69 | .subscribe(); 70 | }); 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/java/com/loiane/springshoppingcart/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.controller; 2 | 3 | import com.loiane.springshoppingcart.model.Order; 4 | import com.loiane.springshoppingcart.repository.OrderRepository; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.bind.annotation.CrossOrigin; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import reactor.core.publisher.Flux; 11 | 12 | import java.time.Duration; 13 | 14 | @CrossOrigin(origins = "http://localhost:4200", maxAge = 3600) 15 | @RestController 16 | @RequestMapping("/api/orders") 17 | public class OrderController { 18 | 19 | private final OrderRepository repository; 20 | 21 | public OrderController(OrderRepository repository) { 22 | this.repository = repository; 23 | } 24 | 25 | @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 26 | public Flux streamOrderStatus() { 27 | return repository.findAll().delayElements(Duration.ofSeconds(7)); 28 | /* return repository.findAll() 29 | .flatMap(record -> { 30 | Flux interval = Flux.interval(ofSeconds(3)); 31 | Flux events = Flux.fromStream( 32 | Stream.generate(() -> new OrderEvent(record, new Date())) 33 | ); 34 | return Flux.zip(interval, events).map(Tuple2::getT2); 35 | }); */ 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/java/com/loiane/springshoppingcart/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.controller; 2 | 3 | import com.loiane.springshoppingcart.model.Product; 4 | import com.loiane.springshoppingcart.repository.ProductRepository; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.*; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | @RestController 12 | @RequestMapping("/api/products") 13 | public class ProductController { 14 | 15 | private final ProductRepository repository; 16 | 17 | public ProductController(ProductRepository repository) { 18 | this.repository = repository; 19 | } 20 | 21 | @GetMapping 22 | public Flux getAll() { 23 | return repository.findAll(); 24 | } 25 | 26 | @GetMapping("{id}") 27 | public Mono> getById(@PathVariable String id) { 28 | return repository.findById(id) 29 | .map(ResponseEntity::ok) 30 | .defaultIfEmpty(ResponseEntity.notFound().build()); 31 | } 32 | 33 | @PostMapping 34 | @ResponseStatus(HttpStatus.CREATED) 35 | public Mono create(@RequestBody Product product) { 36 | return repository.save(product); 37 | } 38 | 39 | @PutMapping("{id}") 40 | public Mono> update(@PathVariable String id, 41 | @RequestBody Product product) { 42 | return repository.findById(id) 43 | .flatMap(existingProduct -> { 44 | existingProduct.setName(product.getName()); 45 | return repository.save(existingProduct); 46 | }) 47 | .map(ResponseEntity::ok) 48 | .defaultIfEmpty(ResponseEntity.notFound().build()); 49 | } 50 | 51 | @DeleteMapping("{id}") 52 | public Mono> delete(@PathVariable(value = "id") String id) { 53 | return repository.findById(id) 54 | .flatMap(existingProduct -> 55 | repository.delete(existingProduct) 56 | .then(Mono.just(ResponseEntity.ok().build())) 57 | ) 58 | .defaultIfEmpty(ResponseEntity.notFound().build()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/java/com/loiane/springshoppingcart/model/Order.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Document(collection = "orders") 13 | public class Order { 14 | 15 | @Id 16 | private String id; 17 | 18 | private String color; 19 | private String description; 20 | } 21 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/java/com/loiane/springshoppingcart/model/OrderEvent.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Date; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class OrderEvent { 13 | 14 | private Order order; 15 | private Date when; 16 | } 17 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/java/com/loiane/springshoppingcart/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Data 12 | @Document(collection = "products") 13 | public class Product { 14 | 15 | @Id 16 | private String id; 17 | 18 | private String name; 19 | private String description; 20 | private Double price; 21 | private String image; 22 | private String status; 23 | private String discounted; 24 | private Double discount; 25 | } 26 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/java/com/loiane/springshoppingcart/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.repository; 2 | 3 | import com.loiane.springshoppingcart.model.Order; 4 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 5 | 6 | public interface OrderRepository extends ReactiveMongoRepository { 7 | } 8 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/java/com/loiane/springshoppingcart/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.repository; 2 | 3 | import com.loiane.springshoppingcart.model.Product; 4 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 5 | 6 | public interface ProductRepository 7 | extends ReactiveMongoRepository { 8 | } 9 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #server.port=${PORT} -------------------------------------------------------------------------------- /spring-shopping-cart/src/test/java/com/loiane/springshoppingcart/SpringShoppingCartApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringShoppingCartApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /spring-shopping-cart/src/test/java/com/loiane/springshoppingcart/controller/OrderControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.controller; 2 | 3 | import com.loiane.springshoppingcart.model.Order; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | import org.springframework.test.web.reactive.server.WebTestClient; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 15 | public class OrderControllerTests { 16 | 17 | private static final String ORDERS_URI = "/api/orders/stream"; 18 | 19 | @Autowired 20 | private WebTestClient webTestClient; 21 | 22 | @Test 23 | public void testStreamOrderStatusIsOk() { 24 | webTestClient 25 | .get().uri(ORDERS_URI) 26 | .accept(MediaType.TEXT_EVENT_STREAM) 27 | .exchange() 28 | .expectStatus().isOk() 29 | .returnResult(Order.class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-shopping-cart/src/test/java/com/loiane/springshoppingcart/controller/ProductControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.loiane.springshoppingcart.controller; 2 | 3 | import com.loiane.springshoppingcart.model.Product; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | import org.springframework.test.web.reactive.server.WebTestClient; 12 | import org.springframework.web.reactive.function.BodyInserters; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 16 | public class ProductControllerTests { 17 | 18 | private static final String PRODUCTS_URI = "/api/products"; 19 | 20 | @Autowired 21 | private WebTestClient webTestClient; 22 | 23 | @Test 24 | public void testGetAll() { 25 | webTestClient 26 | .get().uri(PRODUCTS_URI) 27 | .exchange() 28 | .expectStatus().isOk() 29 | .expectBodyList(Product.class); 30 | } 31 | 32 | @Test 33 | public void testCreate() { 34 | Product product = new Product(null, "name", "description", 1.0, "image", "Active", "false", 0.0); 35 | webTestClient 36 | .post().uri(PRODUCTS_URI) 37 | .contentType(MediaType.APPLICATION_JSON) 38 | .body(BodyInserters.fromValue(product)) 39 | .exchange() 40 | .expectStatus().isCreated(); 41 | } 42 | 43 | @Test 44 | public void testGetByIdNotFound() { 45 | webTestClient 46 | .get().uri(PRODUCTS_URI + "/1") 47 | .exchange() 48 | .expectStatus().isNotFound(); 49 | } 50 | } --------------------------------------------------------------------------------