├── .gitignore ├── Client ├── .editorconfig ├── .gitignore ├── .stylelintrc ├── Dockerfile ├── README.md ├── _scripts │ └── create-docker-image.sh ├── angular.json ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.e2e.json ├── nginx.conf ├── ngsw-config.json ├── package-lock.json ├── package.json ├── proxy.config.json ├── scss │ └── bootstrap │ │ └── _variables.scss ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── layout │ │ │ └── nav │ │ │ │ ├── nav.component.html │ │ │ │ ├── nav.component.scss │ │ │ │ ├── nav.component.spec.ts │ │ │ │ └── nav.component.ts │ │ ├── pages │ │ │ ├── auth │ │ │ │ ├── _guards │ │ │ │ │ ├── auth.guard.spec.ts │ │ │ │ │ └── auth.guard.ts │ │ │ │ ├── _services │ │ │ │ │ ├── auth.service.spec.ts │ │ │ │ │ └── auth.service.ts │ │ │ │ ├── auth-routing.module.ts │ │ │ │ ├── auth.module.spec.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── login │ │ │ │ │ ├── login.component.html │ │ │ │ │ ├── login.component.scss │ │ │ │ │ ├── login.component.spec.ts │ │ │ │ │ └── login.component.ts │ │ │ │ ├── logout │ │ │ │ │ ├── logout.component.html │ │ │ │ │ ├── logout.component.scss │ │ │ │ │ ├── logout.component.spec.ts │ │ │ │ │ └── logout.component.ts │ │ │ │ ├── register │ │ │ │ │ ├── register.component.html │ │ │ │ │ ├── register.component.scss │ │ │ │ │ ├── register.component.spec.ts │ │ │ │ │ └── register.component.ts │ │ │ │ └── user.ts │ │ │ ├── bikes │ │ │ │ ├── _pipes │ │ │ │ │ ├── bike-search.pipe.spec.ts │ │ │ │ │ └── bike-search.pipe.ts │ │ │ │ ├── _services │ │ │ │ │ ├── bikes.service.spec.ts │ │ │ │ │ └── bikes.service.ts │ │ │ │ ├── bike-detail │ │ │ │ │ ├── bike-detail.component.html │ │ │ │ │ ├── bike-detail.component.scss │ │ │ │ │ ├── bike-detail.component.spec.ts │ │ │ │ │ └── bike-detail.component.ts │ │ │ │ ├── bike-list │ │ │ │ │ ├── bike-list.component.html │ │ │ │ │ ├── bike-list.component.scss │ │ │ │ │ ├── bike-list.component.spec.ts │ │ │ │ │ └── bike-list.component.ts │ │ │ │ ├── bike.ts │ │ │ │ ├── bikes-routing.module.ts │ │ │ │ ├── bikes.component.html │ │ │ │ ├── bikes.component.scss │ │ │ │ ├── bikes.component.spec.ts │ │ │ │ ├── bikes.component.ts │ │ │ │ ├── bikes.module.spec.ts │ │ │ │ └── bikes.module.ts │ │ │ ├── builders │ │ │ │ ├── _services │ │ │ │ │ ├── builders.service.spec.ts │ │ │ │ │ └── builders.service.ts │ │ │ │ ├── builder-detail │ │ │ │ │ ├── builder-detail.component.html │ │ │ │ │ ├── builder-detail.component.scss │ │ │ │ │ ├── builder-detail.component.spec.ts │ │ │ │ │ └── builder-detail.component.ts │ │ │ │ ├── builder-list │ │ │ │ │ ├── builder-list.component.html │ │ │ │ │ ├── builder-list.component.scss │ │ │ │ │ ├── builder-list.component.spec.ts │ │ │ │ │ └── builder-list.component.ts │ │ │ │ ├── builder.ts │ │ │ │ ├── builders-routing.module.ts │ │ │ │ ├── builders.component.html │ │ │ │ ├── builders.component.scss │ │ │ │ ├── builders.component.spec.ts │ │ │ │ ├── builders.component.ts │ │ │ │ ├── builders.module.spec.ts │ │ │ │ └── builders.module.ts │ │ │ └── home │ │ │ │ ├── home-routing.module.ts │ │ │ │ ├── home.component.html │ │ │ │ ├── home.component.scss │ │ │ │ ├── home.component.spec.ts │ │ │ │ ├── home.component.ts │ │ │ │ ├── home.module.spec.ts │ │ │ │ └── home.module.ts │ │ └── shared │ │ │ └── _services │ │ │ ├── app-http-interceptor.service.spec.ts │ │ │ ├── app-http-interceptor.service.ts │ │ │ ├── http-handle-error.service.spec.ts │ │ │ └── http-handle-error.service.ts │ ├── assets │ │ ├── .gitkeep │ │ └── icons │ │ │ ├── icon-128x128.png │ │ │ ├── icon-144x144.png │ │ │ ├── icon-152x152.png │ │ │ ├── icon-192x192.png │ │ │ ├── icon-384x384.png │ │ │ ├── icon-512x512.png │ │ │ ├── icon-72x72.png │ │ │ └── icon-96x96.png │ ├── browserslist │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── karma.conf.js │ ├── main.ts │ ├── manifest.json │ ├── polyfills.ts │ ├── styles.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── tslint.json ├── tsconfig.json └── tslint.json ├── README.md ├── Server ├── .env.example ├── .gitattributes ├── .gitignore ├── app │ ├── Bike.php │ ├── Builder.php │ ├── Console │ │ └── Kernel.php │ ├── Exceptions │ │ └── Handler.php │ ├── Garage.php │ ├── Http │ │ ├── Controllers │ │ │ ├── API │ │ │ │ ├── AuthController.php │ │ │ │ ├── BikeController.php │ │ │ │ ├── BuilderController.php │ │ │ │ ├── ItemController.php │ │ │ │ └── RatingController.php │ │ │ ├── ApiController.php │ │ │ ├── Auth │ │ │ │ ├── ForgotPasswordController.php │ │ │ │ ├── LoginController.php │ │ │ │ ├── RegisterController.php │ │ │ │ └── ResetPasswordController.php │ │ │ └── Controller.php │ │ ├── Kernel.php │ │ ├── Middleware │ │ │ ├── EncryptCookies.php │ │ │ ├── RedirectIfAuthenticated.php │ │ │ ├── TrimStrings.php │ │ │ ├── TrustProxies.php │ │ │ └── VerifyCsrfToken.php │ │ └── Resources │ │ │ ├── BikesResource.php │ │ │ ├── BuildersResource.php │ │ │ ├── ItemsResource.php │ │ │ └── RatingResource.php │ ├── Item.php │ ├── Providers │ │ ├── AppServiceProvider.php │ │ ├── AuthServiceProvider.php │ │ ├── BroadcastServiceProvider.php │ │ ├── EventServiceProvider.php │ │ └── RouteServiceProvider.php │ ├── Rating.php │ └── User.php ├── artisan ├── bootstrap │ ├── app.php │ └── cache │ │ └── .gitignore ├── composer.json ├── composer.lock ├── config │ ├── app.php │ ├── auth.php │ ├── broadcasting.php │ ├── cache.php │ ├── database.php │ ├── filesystems.php │ ├── hashing.php │ ├── jwt.php │ ├── l5-swagger.php │ ├── logging.php │ ├── mail.php │ ├── queue.php │ ├── services.php │ ├── session.php │ └── view.php ├── database │ ├── .gitignore │ ├── data-sample │ │ ├── bikes.json │ │ ├── bikes_garages.json │ │ ├── builders.json │ │ ├── garages.json │ │ └── items.json │ ├── factories │ │ └── UserFactory.php │ ├── migrations │ │ ├── 2014_10_12_000000_create_users_table.php │ │ ├── 2014_10_12_100000_create_password_resets_table.php │ │ ├── 2018_04_08_141302_create_bikes_table.php │ │ ├── 2018_04_15_145832_create_builders_table.php │ │ ├── 2018_04_15_150139_create_items_table.php │ │ ├── 2018_04_15_150327_create_garages_table.php │ │ ├── 2018_04_18_011906_create_bike_garage_table.php │ │ └── 2018_05_01_134024_create_ratings_table.php │ └── seeds │ │ ├── BikeGarageTableSeeder.php │ │ ├── BikesTableSeeder.php │ │ ├── BuildersTableSeeder.php │ │ ├── DatabaseSeeder.php │ │ ├── GaragesTableSeeder.php │ │ ├── ItemsTableSeeder.php │ │ ├── RatingsTableSeeder.php │ │ └── UsersTableSeeder.php ├── package.json ├── phpunit.xml ├── public │ ├── .htaccess │ ├── css │ │ └── app.css │ ├── favicon.ico │ ├── index.php │ ├── js │ │ └── app.js │ ├── robots.txt │ └── web.config ├── readme.md ├── resources │ ├── assets │ │ ├── js │ │ │ ├── app.js │ │ │ ├── bootstrap.js │ │ │ └── components │ │ │ │ └── ExampleComponent.vue │ │ └── sass │ │ │ ├── _variables.scss │ │ │ └── app.scss │ ├── lang │ │ └── en │ │ │ ├── auth.php │ │ │ ├── pagination.php │ │ │ ├── passwords.php │ │ │ └── validation.php │ └── views │ │ ├── vendor │ │ └── l5-swagger │ │ │ ├── .gitkeep │ │ │ └── index.blade.php │ │ └── welcome.blade.php ├── routes │ ├── api.php │ ├── channels.php │ ├── console.php │ └── web.php ├── server.php ├── storage │ ├── api-docs │ │ └── api-docs.json │ ├── app │ │ ├── .gitignore │ │ └── public │ │ │ └── .gitignore │ ├── framework │ │ ├── .gitignore │ │ ├── cache │ │ │ └── .gitignore │ │ ├── sessions │ │ │ └── .gitignore │ │ ├── testing │ │ │ └── .gitignore │ │ └── views │ │ │ └── .gitignore │ └── logs │ │ └── .gitignore ├── tests │ ├── CreatesApplication.php │ ├── Feature │ │ └── ExampleTest.php │ ├── TestCase.php │ └── Unit │ │ └── ExampleTest.php └── webpack.mix.js ├── docker-compose.yml └── docker ├── nginx └── nginx.conf └── php-fpm ├── Dockerfile └── php-ini-overrides.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore Storage folder due size. 8 | storage-db 9 | 10 | # compiled output 11 | /dist 12 | /tmp 13 | /out-tsc 14 | 15 | # dependencies 16 | /node_modules 17 | 18 | # IDEs and editors 19 | /.idea 20 | .project 21 | .classpath 22 | .c9/ 23 | *.launch 24 | .settings/ 25 | *.sublime-workspace 26 | 27 | # IDE - VSCode 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 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 | -------------------------------------------------------------------------------- /Client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /Client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /Client/.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "rules": { 4 | "font-family-name-quotes": "always-where-recommended", 5 | "function-url-quotes": [ 6 | "always", 7 | { 8 | "except": ["empty"] 9 | } 10 | ], 11 | "selector-attribute-quotes": "always", 12 | "string-quotes": "double", 13 | "max-nesting-depth": 3, 14 | "selector-max-compound-selectors": 3, 15 | "selector-max-specificity": "0,3,2", 16 | "declaration-no-important": true, 17 | "at-rule-no-vendor-prefix": true, 18 | "media-feature-name-no-vendor-prefix": true, 19 | "property-no-vendor-prefix": true, 20 | "selector-no-vendor-prefix": true, 21 | "value-no-vendor-prefix": true, 22 | "no-empty-source": null, 23 | "selector-class-pattern": "[a-z-]+", 24 | "selector-id-pattern": "[a-z-]+", 25 | "selector-max-id": 0, 26 | "selector-no-qualifying-type": true, 27 | "selector-max-universal": 0, 28 | "selector-pseudo-element-no-unknown": [ 29 | true, 30 | { 31 | "ignorePseudoElements": ["ng-deep"] 32 | } 33 | ], 34 | "unit-whitelist": ["px", "%", "em", "rem", "vw", "vh", "deg"], 35 | "max-empty-lines": 2 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | 3 | COPY nginx.conf /etc/nginx/nginx.conf 4 | 5 | WORKDIR /usr/share/nginx/html 6 | COPY dist/ . 7 | -------------------------------------------------------------------------------- /Client/README.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0. 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 | -------------------------------------------------------------------------------- /Client/_scripts/create-docker-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # Docker command to create the front-end application 4 | docker image build -t angular-laravel-docker . 5 | -------------------------------------------------------------------------------- /Client/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /Client/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display app title', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Custom Bikes Garage'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /Client/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Client/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /Client/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | server { 9 | listen 81; 10 | server_name localhost; 11 | 12 | root /usr/share/nginx/html; 13 | index index.html index.htm; 14 | include /etc/nginx/mime.types; 15 | 16 | gzip on; 17 | gzip_min_length 1000; 18 | gzip_proxied expired no-cache no-store private auth; 19 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; 20 | 21 | location / { 22 | try_files $uri $uri/ /index.html; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Client/ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "/index.html", 3 | "assetGroups": [{ 4 | "name": "app", 5 | "installMode": "prefetch", 6 | "resources": { 7 | "files": [ 8 | "/favicon.ico", 9 | "/index.html", 10 | "/*.css", 11 | "/*.js" 12 | ] 13 | } 14 | }, { 15 | "name": "assets", 16 | "installMode": "lazy", 17 | "updateMode": "prefetch", 18 | "resources": { 19 | "files": [ 20 | "/assets/**" 21 | ] 22 | } 23 | }] 24 | } -------------------------------------------------------------------------------- /Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "lint:dev": "npm run sasslint && npm run tslint", 11 | "sasslint": "./node_modules/.bin/stylelint \"src/**/*.scss\" --syntax scss || echo \"Ops: Stylelint faild for some file(s).\"", 12 | "tslint": "./node_modules/.bin/tslint --project tsconfig.json || echo \"Ops: TSlint faild for some file(s).\"", 13 | "e2e": "ng e2e", 14 | "build:docker":"npm run lint:dev && npm run test && npm run e2e && npm rum build && npm rum docker:image", 15 | "docker:image":"./_scripts/create-docker-image.sh" 16 | }, 17 | "private": true, 18 | "dependencies": { 19 | "@angular/animations": "^6.0.0", 20 | "@angular/common": "^6.0.0", 21 | "@angular/compiler": "^6.0.0", 22 | "@angular/core": "^6.0.0", 23 | "@angular/forms": "^6.0.0", 24 | "@angular/http": "^6.0.0", 25 | "@angular/platform-browser": "^6.0.0", 26 | "@angular/platform-browser-dynamic": "^6.0.0", 27 | "@angular/pwa": "^0.6.0", 28 | "@angular/router": "^6.0.0", 29 | "@angular/service-worker": "^6.0.0", 30 | "@ng-bootstrap/schematics": "^2.0.0-alpha.1", 31 | "core-js": "^2.5.4", 32 | "rxjs": "^6.0.0", 33 | "zone.js": "^0.8.26", 34 | "@ng-bootstrap/ng-bootstrap": "^2.0.0-alpha.0", 35 | "bootstrap": "^4.0.0" 36 | }, 37 | "devDependencies": { 38 | "@angular-devkit/build-angular": "~0.6.0", 39 | "@angular/cli": "~6.0.0", 40 | "@angular/compiler-cli": "^6.0.0", 41 | "@angular/language-service": "^6.0.0", 42 | "@types/jasmine": "~2.8.6", 43 | "@types/jasminewd2": "~2.0.3", 44 | "@types/node": "~8.9.4", 45 | "codelyzer": "~4.2.1", 46 | "jasmine-core": "~2.99.1", 47 | "jasmine-spec-reporter": "~4.2.1", 48 | "karma": "~1.7.1", 49 | "karma-chrome-launcher": "~2.2.0", 50 | "karma-coverage-istanbul-reporter": "~1.4.2", 51 | "karma-firefox-launcher": "^1.1.0", 52 | "karma-jasmine": "~1.1.1", 53 | "karma-jasmine-html-reporter": "^0.2.2", 54 | "protractor": "~5.3.0", 55 | "stylelint": "^9.2.1", 56 | "stylelint-config-standard": "^18.2.0", 57 | "stylelint-scss": "^3.1.0", 58 | "ts-node": "~5.0.1", 59 | "tslint": "~5.9.1", 60 | "tslint-angular": "^1.1.2", 61 | "typescript": "~2.7.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Client/proxy.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v2/*": { 3 | "target":"http://localhost:8081", 4 | "secure": false, 5 | "logLevel": "debug" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Client/scss/bootstrap/_variables.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | // 3 | 4 | // Removing border-radius and box-shadow from components 5 | 6 | $border-radius: 0; 7 | $border-radius-lg: 0; 8 | $border-radius-sm: 0; 9 | 10 | $box-shadow-sm: none; 11 | $box-shadow: none; 12 | $box-shadow-lg: none; 13 | -------------------------------------------------------------------------------- /Client/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 | @NgModule({ 7 | imports: [RouterModule.forRoot(routes)], 8 | exports: [RouterModule] 9 | }) 10 | export class AppRoutingModule { } 11 | -------------------------------------------------------------------------------- /Client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /Client/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/app.component.scss -------------------------------------------------------------------------------- /Client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, ComponentFixture } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 4 | 5 | // App imports 6 | import { AppComponent } from './app.component'; 7 | 8 | describe('AppComponent', () => { 9 | let component: AppComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | RouterTestingModule 16 | ], 17 | declarations: [ 18 | AppComponent 19 | ], 20 | schemas: [NO_ERRORS_SCHEMA] 21 | }).compileComponents(); 22 | })); 23 | 24 | beforeEach(() => { 25 | fixture = TestBed.createComponent(AppComponent); 26 | component = fixture.componentInstance; 27 | fixture.detectChanges(); 28 | }); 29 | 30 | it('should create', async(() => { 31 | expect(component).toBeTruthy(); 32 | })); 33 | }); 34 | -------------------------------------------------------------------------------- /Client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'app'; 10 | } 11 | -------------------------------------------------------------------------------- /Client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | 2 | import { BrowserModule, Title } from '@angular/platform-browser'; 3 | import { NgModule } from '@angular/core'; 4 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 5 | 6 | import { AppRoutingModule } from './app-routing.module'; 7 | import { AppComponent } from './app.component'; 8 | import { ServiceWorkerModule } from '@angular/service-worker'; 9 | import { environment } from '../environments/environment'; 10 | 11 | // Application modules 12 | import { HomeModule } from './pages/home/home.module'; 13 | import { BikesModule } from './pages/bikes/bikes.module'; 14 | import { BuildersModule } from './pages/builders/builders.module'; 15 | import { AuthModule } from './pages/auth/auth.module'; 16 | import { NavComponent } from './layout/nav/nav.component'; 17 | import { HttpErrorHandler } from './shared/_services/http-handle-error.service'; 18 | 19 | import { AppHttpInterceptorService } from './shared/_services/app-http-interceptor.service'; 20 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 21 | 22 | @NgModule({ 23 | declarations: [ 24 | AppComponent, 25 | NavComponent 26 | ], 27 | imports: [ 28 | BrowserModule, 29 | AppRoutingModule, 30 | HttpClientModule, 31 | HomeModule, 32 | BikesModule, 33 | BuildersModule, 34 | AuthModule, 35 | ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production }), 36 | NgbModule.forRoot() 37 | ], 38 | providers: [ 39 | Title, 40 | HttpErrorHandler, 41 | { 42 | provide: HTTP_INTERCEPTORS, 43 | useClass: AppHttpInterceptorService , 44 | multi: true 45 | } 46 | ], 47 | 48 | bootstrap: [AppComponent], 49 | }) 50 | export class AppModule { } 51 | -------------------------------------------------------------------------------- /Client/src/app/layout/nav/nav.component.html: -------------------------------------------------------------------------------- 1 |
2 | 33 |
34 | -------------------------------------------------------------------------------- /Client/src/app/layout/nav/nav.component.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Client/src/app/layout/nav/nav.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NavComponent } from './nav.component'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { HttpClientModule } from '@angular/common/http'; 6 | 7 | describe('NavComponent', () => { 8 | let component: NavComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | RouterTestingModule, 15 | HttpClientModule 16 | ], 17 | declarations: [ NavComponent ] 18 | }) 19 | .compileComponents(); 20 | })); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(NavComponent); 24 | component = fixture.componentInstance; 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('should create', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /Client/src/app/layout/nav/nav.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Title } from '@angular/platform-browser'; 3 | 4 | // App imports 5 | import { AuthService } from '../../pages/auth/_services/auth.service'; 6 | 7 | @Component({ 8 | selector: 'app-nav', 9 | templateUrl: './nav.component.html', 10 | styleUrls: ['./nav.component.scss'] 11 | }) 12 | export class NavComponent implements OnInit { 13 | 14 | public constructor( 15 | private titleTagService: Title, 16 | public auth: AuthService) {} 17 | 18 | public setTitle( pageTitle: string) { 19 | this.titleTagService.setTitle( pageTitle ); 20 | } 21 | 22 | ngOnInit() { 23 | if (this.auth.getToken()) { 24 | this.auth.getUser().subscribe(); 25 | } 26 | } 27 | 28 | onLogout() { 29 | this.auth.onLogout().subscribe(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/_guards/auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { TestBed, async, inject } from '@angular/core/testing'; 3 | import { HttpClient, HttpHandler } from '@angular/common/http'; 4 | import { Router } from '@angular/router'; 5 | 6 | // App imports 7 | import { AuthGuard } from './auth.guard'; 8 | import { AuthService } from '../_services/auth.service'; 9 | 10 | describe('AuthGuard Tests: ', () => { 11 | const router = { 12 | navigate: jasmine.createSpy('navigate') 13 | }; 14 | 15 | beforeEach(async(() => { 16 | TestBed.configureTestingModule({ 17 | imports: [ 18 | RouterTestingModule.withRoutes([ 19 | {path: 'bikes:id'} 20 | ]) 21 | ], 22 | providers: [AuthGuard, AuthService, HttpClient, HttpHandler, { provide: Router, useValue: router } ] 23 | }); 24 | })); 25 | 26 | it('should AuthGuartd to be defined', inject([AuthGuard], (guard: AuthGuard) => { 27 | expect(guard).toBeTruthy(); 28 | })); 29 | 30 | it('should AuthService to be defined', inject([AuthService], (auth: AuthService) => { 31 | expect(auth).toBeTruthy(); 32 | })); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/_guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | import { AuthService } from '../_services/auth.service'; 5 | 6 | @Injectable() 7 | export class AuthGuard implements CanActivate { 8 | 9 | constructor( 10 | private router: Router, 11 | private auth: AuthService) {} 12 | 13 | canActivate( 14 | next: ActivatedRouteSnapshot, 15 | state: RouterStateSnapshot): Observable | Promise | boolean { 16 | 17 | if (this.auth.isAuthenticated()) { 18 | // logged in so return true 19 | return true; 20 | } 21 | 22 | // not logged in so redirect to login page with the return url 23 | this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }}); 24 | 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/_services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { AuthService } from './auth.service'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | 6 | describe('AuthService', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | imports: [ 10 | RouterTestingModule, 11 | HttpClientModule 12 | ], 13 | providers: [AuthService] 14 | }); 15 | }); 16 | 17 | it('should be created', inject([AuthService], (service: AuthService) => { 18 | expect(service).toBeTruthy(); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/auth-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | // Auth Routes Imports 5 | import { RegisterComponent } from './register/register.component'; 6 | import { LoginComponent } from './login/login.component'; 7 | import { LogoutComponent } from './logout/logout.component'; 8 | 9 | const routes: Routes = [ 10 | { path: 'register', component: RegisterComponent }, 11 | { path: 'login', component: LoginComponent }, 12 | { path: 'logout', component: LogoutComponent } 13 | ]; 14 | 15 | @NgModule({ 16 | imports: [RouterModule.forChild(routes)], 17 | exports: [RouterModule] 18 | }) 19 | export class AuthRoutingModule { } 20 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/auth.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { AuthModule } from './auth.module'; 2 | 3 | describe('AuthModule', () => { 4 | let authModule: AuthModule; 5 | 6 | beforeEach(() => { 7 | authModule = new AuthModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(authModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { AuthRoutingModule } from './auth-routing.module'; 6 | import { LoginComponent } from './login/login.component'; 7 | import { RegisterComponent } from './register/register.component'; 8 | import { LogoutComponent } from './logout/logout.component'; 9 | import { AuthGuard } from './_guards/auth.guard'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | AuthRoutingModule, 15 | FormsModule, 16 | ReactiveFormsModule 17 | ], 18 | providers: [AuthGuard], 19 | declarations: [LoginComponent, RegisterComponent, LogoutComponent] 20 | }) 21 | export class AuthModule { } 22 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 30 |
31 |
32 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/login/login.component.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | // App imports 7 | import { LoginComponent } from './login.component'; 8 | import { AuthService } from '../_services/auth.service'; 9 | 10 | describe('LoginComponent', () => { 11 | let component: LoginComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [ 17 | RouterTestingModule, 18 | FormsModule, 19 | HttpClientModule 20 | ], 21 | declarations: [ LoginComponent ], 22 | providers: [AuthService] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(LoginComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router, ActivatedRoute } from '@angular/router'; 3 | 4 | // App imports 5 | import { AuthService } from '../_services/auth.service'; 6 | import { User } from '../user'; 7 | 8 | @Component({ 9 | selector: 'app-login', 10 | templateUrl: './login.component.html', 11 | styleUrls: ['./login.component.scss'] 12 | }) 13 | export class LoginComponent implements OnInit { 14 | user: User = new User(); 15 | error: any; 16 | returnUrl: string; 17 | 18 | constructor( 19 | private authService: AuthService, 20 | private router: Router, 21 | private route: ActivatedRoute) { } 22 | 23 | ngOnInit() { 24 | // Set the return url 25 | this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; 26 | } 27 | 28 | onSubmit(loginForm): void { 29 | this.authService.onLogin(this.user).subscribe( 30 | (response) => { 31 | // get return url from route parameters or default to '/' 32 | this.router.navigate([this.returnUrl]); 33 | }, 34 | (error) => { 35 | this.error = error.error; 36 | } 37 | ); 38 | // Clear form fields 39 | loginForm.reset(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/logout/logout.component.html: -------------------------------------------------------------------------------- 1 |

2 | logout works! 3 |

4 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/logout/logout.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/pages/auth/logout/logout.component.scss -------------------------------------------------------------------------------- /Client/src/app/pages/auth/logout/logout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LogoutComponent } from './logout.component'; 4 | 5 | describe('LogoutComponent', () => { 6 | let component: LogoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LogoutComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LogoutComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/logout/logout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-logout', 5 | templateUrl: './logout.component.html', 6 | styleUrls: ['./logout.component.scss'] 7 | }) 8 | export class LogoutComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Welcome

6 |

Motorcycle builders and road lovers

7 |
8 |
9 |
10 | 11 | 12 | 16 |
17 |
18 | 19 | 20 | 25 |
26 |
27 | 28 | 29 | 34 |
35 | 38 | 39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/register/register.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/pages/auth/register/register.component.scss -------------------------------------------------------------------------------- /Client/src/app/pages/auth/register/register.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 4 | 5 | // App imports 6 | import { RegisterComponent } from './register.component'; 7 | import { HttpClientModule } from '@angular/common/http'; 8 | import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; 9 | 10 | describe('RegisterComponent', () => { 11 | let component: RegisterComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [ 17 | RouterTestingModule, 18 | HttpClientModule, 19 | FormsModule, 20 | ReactiveFormsModule 21 | ], 22 | declarations: [ RegisterComponent ], 23 | schemas: [NO_ERRORS_SCHEMA], 24 | providers: [FormBuilder] 25 | }) 26 | .compileComponents(); 27 | })); 28 | 29 | beforeEach(() => { 30 | fixture = TestBed.createComponent(RegisterComponent); 31 | component = fixture.componentInstance; 32 | fixture.detectChanges(); 33 | }); 34 | 35 | it('should create', () => { 36 | expect(component).toBeTruthy(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 4 | 5 | // App imports 6 | import { User } from '../user'; 7 | import { AuthService } from '../_services/auth.service'; 8 | 9 | @Component({ 10 | selector: 'app-register', 11 | templateUrl: './register.component.html', 12 | styleUrls: ['./register.component.scss'] 13 | }) 14 | export class RegisterComponent implements OnInit { 15 | 16 | user: User = new User(); 17 | error: any; 18 | registerForm: FormGroup; 19 | 20 | constructor(private authService: AuthService, private router: Router, private fb: FormBuilder) { 21 | this.createForm(); 22 | } 23 | 24 | ngOnInit() {} 25 | 26 | createForm() { 27 | this.registerForm = this.fb.group({ 28 | name: [this.user.name, Validators.compose([Validators.required])], 29 | email: [this.user.email, Validators.compose([Validators.required, Validators.email ])], 30 | password: [this.user.password, Validators.compose([Validators.required, Validators.minLength(6)])], 31 | }); 32 | } 33 | 34 | onSubmit(): void { 35 | 36 | this.authService.onRegister(this.registerForm.value).subscribe( 37 | (response) => { 38 | this.router.navigate(['bikes']); 39 | }, 40 | (response) => { 41 | if (response.status === 422) { 42 | Object.keys(response.error).map((err) => { 43 | this.error = `${response.error[err]}`; 44 | }); 45 | 46 | } else { 47 | this.error = response.error; 48 | } 49 | 50 | } 51 | ); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Client/src/app/pages/auth/user.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | id?: number; 3 | name?: string; 4 | email?: string; 5 | password?: string; 6 | 7 | constructor() {} 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/_pipes/bike-search.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { BikeSearchPipe } from './bike-search.pipe'; 2 | 3 | describe('BikeSearchPipe', () => { 4 | it('create an instance', () => { 5 | const pipe = new BikeSearchPipe(); 6 | expect(pipe).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/_pipes/bike-search.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'bikeSearch' 5 | }) 6 | export class BikeSearchPipe implements PipeTransform { 7 | 8 | transform(items: any, searchText: string): any { 9 | if (searchText) { 10 | searchText = searchText.toLowerCase(); 11 | return items.filter((item: any) => item.model.toLowerCase().indexOf(searchText) > -1); 12 | } 13 | return items; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/_services/bikes.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | 4 | // App imports 5 | import { BikesService } from './bikes.service'; 6 | import { HttpErrorHandler } from '../../../shared/_services/http-handle-error.service'; 7 | 8 | describe('BikesService', () => { 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [ 12 | HttpClientModule 13 | ], 14 | providers: [ 15 | BikesService, 16 | HttpErrorHandler 17 | ] 18 | }); 19 | }); 20 | 21 | it('should be created', inject([BikesService], (service: BikesService) => { 22 | expect(service).toBeTruthy(); 23 | })); 24 | }); 25 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/_services/bikes.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { catchError } from 'rxjs/operators'; 5 | 6 | // App import 7 | import { environment } from '../../../../environments/environment'; 8 | import { Bike } from '../bike'; 9 | import { HttpErrorHandler, HandleError } from '../../../shared/_services/http-handle-error.service'; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class BikesService { 15 | private readonly apiUrl = environment.apiUrl; 16 | private bikesUrl = this.apiUrl + '/bikes'; 17 | private handleError: HandleError; 18 | 19 | constructor( 20 | private http: HttpClient, 21 | httpErrorHandler: HttpErrorHandler ) { 22 | this.handleError = httpErrorHandler.createHandleError('BikesService'); 23 | } 24 | 25 | /** GET bikes from bikes endpoint */ 26 | getBikes (): Observable { 27 | return this.http.get(this.bikesUrl) 28 | .pipe( 29 | catchError(this.handleError('getBikes', [])) 30 | ); 31 | } 32 | 33 | /** GET bike detail from bike-detail endpoint */ 34 | getBikeDetail (id: number): Observable { 35 | return this.http.get(this.bikesUrl + `/${id}`) 36 | .pipe( 37 | catchError(this.handleError('getBikeDetail', [])) 38 | ); 39 | } 40 | 41 | /** POST bike to bikes endpoint */ 42 | addBike (bike: Bike): Observable { 43 | return this.http.post(this.bikesUrl, bike) 44 | .pipe( 45 | catchError(this.handleError('addBike', bike)) 46 | ); 47 | } 48 | 49 | /** PUT bike to bikes endpoint */ 50 | updateBike (id: number, bike: Bike): Observable { 51 | return this.http.put(this.bikesUrl + `/${id}`, bike) 52 | .pipe( 53 | catchError(this.handleError('updateBike', bike)) 54 | ); 55 | } 56 | 57 | /** DELETE bike bike endpoint */ 58 | deleteBike (id: number): Observable { 59 | return this.http.delete(this.bikesUrl + `/${id}`) 60 | .pipe( 61 | catchError(this.handleError('deleteBike')) 62 | ); 63 | } 64 | 65 | /** Vote on bike */ 66 | voteOnBike (vote: number, bike: number): Observable { 67 | const rating = vote; 68 | return this.http.post(this.bikesUrl + `/${bike}/ratings`, {rating}) 69 | .pipe( 70 | catchError(this.handleError('voteOnBike', [])) 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bike-detail/bike-detail.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/pages/bikes/bike-detail/bike-detail.component.scss -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bike-detail/bike-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 4 | 5 | // App imports 6 | import { BikeDetailComponent } from './bike-detail.component'; 7 | import { FormsModule } from '@angular/forms'; 8 | import { HttpClientModule } from '@angular/common/http'; 9 | import { HttpErrorHandler } from '../../../shared/_services/http-handle-error.service'; 10 | 11 | describe('BikeDetailComponent', () => { 12 | let component: BikeDetailComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(async(() => { 16 | TestBed.configureTestingModule({ 17 | imports: [ 18 | RouterTestingModule, 19 | FormsModule, 20 | HttpClientModule 21 | ], 22 | declarations: [ 23 | BikeDetailComponent 24 | ], 25 | schemas: [NO_ERRORS_SCHEMA], 26 | providers: [HttpErrorHandler] 27 | }) 28 | .compileComponents(); 29 | })); 30 | 31 | beforeEach(() => { 32 | fixture = TestBed.createComponent(BikeDetailComponent); 33 | component = fixture.componentInstance; 34 | fixture.detectChanges(); 35 | }); 36 | 37 | it('should create', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bike-detail/bike-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | // App imports 5 | import { Bike } from '../bike'; 6 | import { BikesService } from '../_services/bikes.service'; 7 | import { AuthService } from '../../auth/_services/auth.service'; 8 | 9 | @Component({ 10 | selector: 'app-bike-detail', 11 | templateUrl: './bike-detail.component.html', 12 | styleUrls: ['./bike-detail.component.scss'] 13 | }) 14 | export class BikeDetailComponent implements OnInit { 15 | 16 | bike: Bike; 17 | isLoading: Boolean = false; 18 | userVote: number; 19 | 20 | builders: Array = [ 21 | {id: 1, name: 'Diamond Atelier'}, 22 | {id: 2, name: 'Deus Ex Machina\'s'}, 23 | {id: 3, name: 'Rough Crafts'}, 24 | {id: 4, name: 'Roldand Sands'}, 25 | {id: 5, name: 'Chopper Dave'} 26 | ]; 27 | 28 | constructor( 29 | private bikeService: BikesService, 30 | private route: ActivatedRoute, 31 | private auth: AuthService ) {} 32 | 33 | ngOnInit() { 34 | // Get bike details 35 | this.getBikeDetail(); 36 | } 37 | 38 | getBikeDetail(): void { 39 | this.isLoading = true; 40 | const id = +this.route.snapshot.paramMap.get('id'); 41 | this.bikeService.getBikeDetail(id) 42 | .subscribe(bike => { 43 | this.isLoading = false; 44 | this.bike = bike['data']; 45 | }); 46 | } 47 | 48 | onVote(rating: number, id: number): void { 49 | // Check if user already vote on a bike 50 | if (this.checkUserVote(this.bike.ratings)) { 51 | alert('you already vote on this bike'); 52 | return; 53 | } 54 | // Get bike id 55 | id = +this.route.snapshot.paramMap.get('id'); 56 | // post vote 57 | this.bikeService.voteOnBike(rating, id) 58 | .subscribe( 59 | (response) => { 60 | this.userVote = response.data.rating; 61 | // Update the average rating and rating object on bike 62 | this.bike['average_rating'] = response.data.average_rating; 63 | // Update ratings array 64 | this.bike.ratings.push(response.data); 65 | } 66 | ); 67 | } 68 | 69 | checkUserVote(ratings: any[]): Boolean { 70 | const currentUserId = this.auth.currentUser.id; 71 | let ratingUserId: number; 72 | Object.keys(ratings).forEach( (i) => { 73 | ratingUserId = ratings[i].user_id; 74 | }); 75 | if ( currentUserId === ratingUserId ) { 76 | return true; 77 | } else { 78 | return false; 79 | } 80 | } 81 | 82 | onSubmit(bike) { 83 | this.isLoading = true; 84 | const id = +this.route.snapshot.paramMap.get('id'); 85 | this.bikeService.updateBike(id, bike.value) 86 | .subscribe(response => { 87 | this.isLoading = false; 88 | this.bike = response['data']; 89 | }); 90 | } 91 | 92 | checkBikeOwner(): Boolean { 93 | if (this.auth.currentUser.id === this.bike.user.id) { 94 | return true; 95 | } else { 96 | return false; 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bike-list/bike-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 | 10 |
11 |
12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ bike.model }} 29 |
30 |

{{ bike.model }} | {{ bike.year }}

31 |

{{ bike.mods }}

32 | Vote 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bike-list/bike-list.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/pages/bikes/bike-list/bike-list.component.scss -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bike-list/bike-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | 6 | // App imports 7 | import { BikeListComponent } from './bike-list.component'; 8 | import { BikeSearchPipe } from '../_pipes/bike-search.pipe'; 9 | import { HttpErrorHandler } from './../../../shared/_services/http-handle-error.service'; 10 | 11 | 12 | describe('BikeListComponent', () => { 13 | let component: BikeListComponent; 14 | let fixture: ComponentFixture; 15 | 16 | beforeEach(async(() => { 17 | TestBed.configureTestingModule({ 18 | imports: [ 19 | RouterTestingModule, 20 | HttpClientModule 21 | ], 22 | declarations: [ 23 | BikeListComponent, 24 | BikeSearchPipe 25 | ], 26 | schemas: [NO_ERRORS_SCHEMA], 27 | providers: [HttpErrorHandler] 28 | }) 29 | .compileComponents(); 30 | })); 31 | 32 | beforeEach(() => { 33 | fixture = TestBed.createComponent(BikeListComponent); 34 | component = fixture.componentInstance; 35 | fixture.detectChanges(); 36 | }); 37 | 38 | it('should create', () => { 39 | expect(component).toBeTruthy(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bike-list/bike-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | // App imports 4 | import { Bike } from '../bike'; 5 | import { BikesService } from '../_services/bikes.service'; 6 | 7 | @Component({ 8 | selector: 'app-bike-list', 9 | templateUrl: './bike-list.component.html', 10 | styleUrls: ['./bike-list.component.scss'] 11 | }) 12 | export class BikeListComponent implements OnInit { 13 | // Using Bike Model class 14 | bikes: Bike[]; 15 | isLoading: Boolean = false; 16 | public searchText: string; 17 | 18 | constructor( 19 | private bikeService: BikesService) {} 20 | 21 | ngOnInit() { 22 | // Get bike list 23 | this.getBikes(); 24 | } 25 | 26 | getBikes(): void { 27 | this.isLoading = true; 28 | this.bikeService.getBikes() 29 | .subscribe( 30 | response => this.handleResponse(response), 31 | error => this.handleError(error)); 32 | } 33 | 34 | protected handleResponse(response: Bike[]) { 35 | this.isLoading = false, 36 | this.bikes = response; 37 | } 38 | protected handleError(error: any) { 39 | this.isLoading = false, 40 | console.error(error); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bike.ts: -------------------------------------------------------------------------------- 1 | import { User } from './../auth/user'; 2 | import { Builder } from '../builders/builder'; 3 | 4 | export class Bike { 5 | id: number; 6 | make: string; 7 | model: string; 8 | year: string; 9 | mods: string; 10 | picture: string; 11 | user_id: number; 12 | builder_id: number; 13 | average_rating?: number; 14 | user?: User; 15 | builder?: Builder; 16 | items?: any; 17 | ratings?: any; 18 | 19 | constructor() {} 20 | } 21 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bikes-routing.module.ts: -------------------------------------------------------------------------------- 1 | 2 | import { NgModule } from '@angular/core'; 3 | import { Routes, RouterModule } from '@angular/router'; 4 | import { AuthGuard } from '../auth/_guards/auth.guard'; 5 | 6 | // Bikes Routes Imports 7 | import { BikeDetailComponent } from './bike-detail/bike-detail.component'; 8 | import { BikeListComponent } from './bike-list/bike-list.component'; 9 | 10 | const routes: Routes = [ 11 | { 12 | path: 'bikes', 13 | children: [ 14 | { 15 | path: '', 16 | component: BikeListComponent 17 | }, 18 | { 19 | path: ':id', 20 | component: BikeDetailComponent, 21 | canActivate: [AuthGuard] 22 | } 23 | ] 24 | } 25 | ]; 26 | 27 | @NgModule({ 28 | imports: [RouterModule.forChild(routes)], 29 | exports: [RouterModule] 30 | }) 31 | export class BikesRoutingModule { } 32 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bikes.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bikes.component.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bikes.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | // App imports 5 | import { BikesComponent } from './bikes.component'; 6 | 7 | describe('BikesComponent', () => { 8 | let component: BikesComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | RouterTestingModule 15 | ], 16 | declarations: [ 17 | BikesComponent 18 | ] 19 | }) 20 | .compileComponents(); 21 | })); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(BikesComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bikes.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-bikes', 5 | templateUrl: './bikes.component.html', 6 | styleUrls: ['./bikes.component.scss'] 7 | }) 8 | export class BikesComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bikes.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { BikesModule } from './bikes.module'; 2 | 3 | describe('BikesModule', () => { 4 | let bikesModule: BikesModule; 5 | 6 | beforeEach(() => { 7 | bikesModule = new BikesModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(bikesModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /Client/src/app/pages/bikes/bikes.module.ts: -------------------------------------------------------------------------------- 1 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 2 | import { NgModule } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | 6 | 7 | import { BikesRoutingModule } from './bikes-routing.module'; 8 | import { BikesComponent } from './bikes.component'; 9 | import { BikeDetailComponent } from './bike-detail/bike-detail.component'; 10 | import { BikeListComponent } from './bike-list/bike-list.component'; 11 | import { HttpClientModule } from '@angular/common/http'; 12 | import { BikeSearchPipe } from './_pipes/bike-search.pipe'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | CommonModule, 17 | BikesRoutingModule, 18 | HttpClientModule, 19 | FormsModule, 20 | ReactiveFormsModule, 21 | NgbModule 22 | ], 23 | declarations: [BikesComponent, BikeDetailComponent, BikeListComponent, BikeSearchPipe] 24 | }) 25 | export class BikesModule { } 26 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/_services/builders.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { TestBed, inject } from '@angular/core/testing'; 3 | 4 | // App imports 5 | import { BuildersService } from './builders.service'; 6 | import { HttpErrorHandler } from './../../../shared/_services/http-handle-error.service'; 7 | 8 | 9 | describe('BuildersService', () => { 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | HttpClientModule 14 | ], 15 | providers: [ 16 | BuildersService, 17 | HttpErrorHandler 18 | ] 19 | }); 20 | }); 21 | 22 | it('should be created', inject([BuildersService], (service: BuildersService) => { 23 | expect(service).toBeTruthy(); 24 | })); 25 | }); 26 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/_services/builders.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { catchError } from 'rxjs/operators'; 5 | 6 | // App import 7 | import { environment } from '../../../../environments/environment'; 8 | import { Builder } from '../builder'; 9 | import { HttpErrorHandler, HandleError } from '../../../shared/_services/http-handle-error.service'; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class BuildersService { 15 | private readonly apiUrl = environment.apiUrl; 16 | private buildersUrl = this.apiUrl + '/builders'; 17 | private handleError: HandleError; 18 | 19 | constructor( 20 | private http: HttpClient, 21 | httpErrorHandler: HttpErrorHandler ) { 22 | this.handleError = httpErrorHandler.createHandleError('BuildersService'); 23 | } 24 | 25 | /** GET builders from builders endpoint */ 26 | getBuilders (): Observable { 27 | return this.http.get(this.buildersUrl) 28 | .pipe( 29 | catchError(this.handleError('getBuilders', [])) 30 | ); 31 | } 32 | 33 | /** GET builder detail from builder-detail endpoint */ 34 | getBuilderDetail (id: number): Observable { 35 | return this.http.get(this.buildersUrl + `/${id}`) 36 | .pipe( 37 | catchError(this.handleError('getBuilderDetail', [])) 38 | ); 39 | } 40 | 41 | /** POST builder to builders endpoint */ 42 | addBuilder (builder: Builder): Observable { 43 | return this.http.post(this.buildersUrl, builder) 44 | .pipe( 45 | catchError(this.handleError('addBuilder', builder)) 46 | ); 47 | } 48 | 49 | /** PUT builder to builders endpoint */ 50 | updateBuilder (builder: Builder, id: number): Observable { 51 | return this.http.put(this.buildersUrl + `/${id}`, builder) 52 | .pipe( 53 | catchError(this.handleError('updateBuilder', builder)) 54 | ); 55 | } 56 | 57 | /** DELETE builder builder endpoint */ 58 | deleteBuilder (id: number): Observable { 59 | return this.http.delete(this.buildersUrl + `/${id}`) 60 | .pipe( 61 | catchError(this.handleError('deleteBuilder')) 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder-detail/builder-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
{{ builder?.name }}
13 |

{{ builder?.description }}

14 |
15 |
16 | Featured Bike: 17 |
18 |
19 | 20 |
21 |
    22 |
  • Make: {{ builder?.bike.make }} | 23 | Year: {{ builder?.bike.year }} 24 |
  • 25 |
  • Model: {{ builder?.bike.model }}
  • 26 |
  • Mods: {{ builder?.bike.mods }}
  • 27 |
  • 28 | Bike Details 29 |
  • 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder-detail/builder-detail.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/pages/builders/builder-detail/builder-detail.component.scss -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder-detail/builder-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 4 | 5 | import { BuilderDetailComponent } from './builder-detail.component'; 6 | import { HttpErrorHandler } from '../../../shared/_services/http-handle-error.service'; 7 | 8 | 9 | describe('BuilderDetailComponent', () => { 10 | let component: BuilderDetailComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | RouterTestingModule, 17 | HttpClientModule 18 | ], 19 | declarations: [ 20 | BuilderDetailComponent 21 | ], 22 | providers: [HttpErrorHandler] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(BuilderDetailComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder-detail/builder-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | // App imports 5 | import { Builder } from './../builder'; 6 | import { BuildersService } from '../_services/builders.service'; 7 | 8 | @Component({ 9 | selector: 'app-builder-detail', 10 | templateUrl: './builder-detail.component.html', 11 | styleUrls: ['./builder-detail.component.scss'] 12 | }) 13 | export class BuilderDetailComponent implements OnInit { 14 | 15 | builder: Builder; 16 | isLoading: Boolean = false; 17 | 18 | constructor( 19 | private buildersService: BuildersService, 20 | private route: ActivatedRoute) { } 21 | 22 | ngOnInit() { 23 | // Get builder detail 24 | this.getBuilderDetail(); 25 | } 26 | 27 | getBuilderDetail(): void { 28 | this.isLoading = true; 29 | const id = +this.route.snapshot.paramMap.get('id'); 30 | this.buildersService.getBuilderDetail(id) 31 | .subscribe(builder => { 32 | this.isLoading = false; 33 | this.builder = builder['data']; 34 | }); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder-list/builder-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |

{{ builder?.name }}

13 |
14 |
15 |

16 | {{ builder?.description }} 17 |

18 | 19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder-list/builder-list.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/pages/builders/builder-list/builder-list.component.scss -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder-list/builder-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 4 | 5 | // App imports 6 | import { BuilderListComponent } from './builder-list.component'; 7 | import { HttpErrorHandler } from '../../../shared/_services/http-handle-error.service'; 8 | 9 | describe('BuilderListComponent', () => { 10 | let component: BuilderListComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | RouterTestingModule, 17 | HttpClientModule 18 | ], 19 | declarations: [ 20 | BuilderListComponent 21 | ], 22 | providers: [HttpErrorHandler] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(BuilderListComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder-list/builder-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | // App imports 4 | import { Builder } from './../builder'; 5 | import { BuildersService } from '../_services/builders.service'; 6 | 7 | @Component({ 8 | selector: 'app-builder-list', 9 | templateUrl: './builder-list.component.html', 10 | styleUrls: ['./builder-list.component.scss'] 11 | }) 12 | export class BuilderListComponent implements OnInit { 13 | // Using Builder Model class 14 | builders: Builder[]; 15 | isLoading: Boolean = false; 16 | 17 | constructor(private builderService: BuildersService) { } 18 | 19 | ngOnInit() { 20 | // Get builder detail 21 | this.getBuilders(); 22 | } 23 | 24 | getBuilders(): void { 25 | this.isLoading = true; 26 | this.builderService.getBuilders() 27 | .subscribe( 28 | response => this.handleResponse(response), 29 | error => this.handleError(error)); 30 | } 31 | 32 | protected handleResponse(response: Builder[]) { 33 | this.isLoading = false, 34 | this.builders = response; 35 | } 36 | protected handleError(error: any) { 37 | this.isLoading = false, 38 | console.error(error); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builder.ts: -------------------------------------------------------------------------------- 1 | import { Bike } from '../bikes/bike'; 2 | 3 | export class Builder { 4 | id: number; 5 | name: string; 6 | description: string; 7 | location: string; 8 | bike?: Bike; 9 | 10 | constructor() {} 11 | } 12 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builders-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | // Builder Routes Imports 5 | import { BuilderDetailComponent } from './builder-detail/builder-detail.component'; 6 | import { BuilderListComponent } from './builder-list/builder-list.component'; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: 'builders', 11 | children: [ 12 | { 13 | path: '', 14 | component: BuilderListComponent 15 | }, 16 | { 17 | path: ':id', 18 | component: BuilderDetailComponent 19 | } 20 | ] 21 | } 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [RouterModule.forChild(routes)], 26 | exports: [RouterModule] 27 | }) 28 | export class BuildersRoutingModule { } 29 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builders.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builders.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/pages/builders/builders.component.scss -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builders.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | // App imports 5 | import { BuildersComponent } from './builders.component'; 6 | 7 | describe('BuildersComponent', () => { 8 | let component: BuildersComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | RouterTestingModule 15 | ], 16 | declarations: [ 17 | BuildersComponent 18 | ] 19 | }) 20 | .compileComponents(); 21 | })); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(BuildersComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builders.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-builders', 5 | templateUrl: './builders.component.html', 6 | styleUrls: ['./builders.component.scss'] 7 | }) 8 | export class BuildersComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builders.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { BuildersModule } from './builders.module'; 2 | 3 | describe('BuildersModule', () => { 4 | let buildersModule: BuildersModule; 5 | 6 | beforeEach(() => { 7 | buildersModule = new BuildersModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(buildersModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /Client/src/app/pages/builders/builders.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { BuildersRoutingModule } from './builders-routing.module'; 5 | import { BuildersComponent } from './builders.component'; 6 | import { BuilderDetailComponent } from './builder-detail/builder-detail.component'; 7 | import { BuilderListComponent } from './builder-list/builder-list.component'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | BuildersRoutingModule 13 | ], 14 | declarations: [BuildersComponent, BuilderDetailComponent, BuilderListComponent] 15 | }) 16 | export class BuildersModule { } 17 | -------------------------------------------------------------------------------- /Client/src/app/pages/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | // Home Routes Imports 5 | import { HomeComponent } from './home.component'; 6 | 7 | const routes: Routes = [ 8 | { path: '', component: HomeComponent } 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [RouterModule.forChild(routes)], 13 | exports: [RouterModule] 14 | }) 15 | export class HomeRoutingModule { } 16 | -------------------------------------------------------------------------------- /Client/src/app/pages/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Custom Bikes Garage

5 |

Motorcycle builders and road lovers

6 |

7 | Register 8 |

9 |
10 |
11 |
12 |
13 |
14 |

Heading

15 |

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum 16 | nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. 17 |

18 |

19 | View details » 20 |

21 |
22 |
23 |

Heading

24 |

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum 25 | nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. 26 |

27 |

28 | View details » 29 |

30 |
31 |
32 |

Heading

33 |

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod 34 | semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet 35 | risus.

36 |

37 | View details » 38 |

39 |
40 |
41 |
42 |
43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Client/src/app/pages/home/home.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/app/pages/home/home.component.scss -------------------------------------------------------------------------------- /Client/src/app/pages/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed , async, ComponentFixture } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | // App imports 5 | import { HomeComponent } from './home.component'; 6 | 7 | describe('HomeComponent', () => { 8 | let component: HomeComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | RouterTestingModule 15 | ], 16 | declarations: [ 17 | HomeComponent 18 | ] 19 | }).compileComponents(); 20 | })); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(HomeComponent); 24 | component = fixture.componentInstance; 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('should create', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /Client/src/app/pages/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Client/src/app/pages/home/home.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { HomeModule } from './home.module'; 2 | 3 | describe('HomeModule', () => { 4 | let homeModule: HomeModule; 5 | 6 | beforeEach(() => { 7 | homeModule = new HomeModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(homeModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /Client/src/app/pages/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { HomeRoutingModule } from './home-routing.module'; 5 | import { HomeComponent } from './home.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | HomeRoutingModule, 11 | ], 12 | declarations: [HomeComponent] 13 | }) 14 | export class HomeModule { } 15 | -------------------------------------------------------------------------------- /Client/src/app/shared/_services/app-http-interceptor.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { TestBed, inject } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | 5 | // App imports 6 | import { AppHttpInterceptorService } from './app-http-interceptor.service'; 7 | 8 | describe('AppHttpInterceptorService', () => { 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [ 12 | RouterTestingModule, 13 | HttpClientModule 14 | ], 15 | providers: [AppHttpInterceptorService] 16 | }); 17 | }); 18 | 19 | it('should be created', inject([AppHttpInterceptorService], (service: AppHttpInterceptorService) => { 20 | expect(service).toBeTruthy(); 21 | })); 22 | }); 23 | -------------------------------------------------------------------------------- /Client/src/app/shared/_services/app-http-interceptor.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse, HttpResponse } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { tap } from 'rxjs/operators'; 5 | import { Router } from '@angular/router'; 6 | // App import 7 | import { AuthService } from '../../pages/auth/_services/auth.service'; 8 | 9 | @Injectable() 10 | export class AppHttpInterceptorService implements HttpInterceptor { 11 | 12 | constructor(public auth: AuthService, private router: Router ) { } 13 | 14 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 15 | 16 | console.log('interceptor running'); 17 | // Get the token from auth service. 18 | const authToken = this.auth.getToken(); 19 | 20 | if (authToken) { 21 | // Clone the request to add the new header. 22 | const authReq = req.clone( 23 | { headers: req.headers.set('Authorization', `Bearer ${authToken}`)} 24 | ); 25 | console.log('interceptor running with new headers'); 26 | 27 | // send the newly created request 28 | return next.handle(authReq).pipe( 29 | tap((event: HttpEvent) => { 30 | if (event instanceof HttpResponse) { 31 | // Response wiht HttpResponse type 32 | console.log('TAP function', event); 33 | 34 | } 35 | }, (err: any) => { 36 | console.log(err); 37 | if (err instanceof HttpErrorResponse) { 38 | if (err.status === 401) { 39 | localStorage.removeItem('token'); 40 | this.router.navigate(['/']); 41 | } 42 | } 43 | }) 44 | ); 45 | 46 | } else { 47 | console.log('interceptor without changes'); 48 | return next.handle(req); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Client/src/app/shared/_services/http-handle-error.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { HttpErrorHandler } from './http-handle-error.service'; 4 | 5 | describe('HttpErrorHandler', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [HttpErrorHandler] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([HttpErrorHandler], (service: HttpErrorHandler) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /Client/src/app/shared/_services/http-handle-error.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpErrorResponse } from '@angular/common/http'; 3 | import { Observable, of } from 'rxjs'; 4 | 5 | /** HandleError Type */ 6 | export type HandleError = 7 | (operation?: string, result?: T) => (error: HttpErrorResponse) => Observable; 8 | 9 | @Injectable() 10 | export class HttpErrorHandler { 11 | 12 | constructor() { } 13 | 14 | /** Pass the service name to map errors */ 15 | createHandleError = (serviceName = '') => 16 | (operation = 'operation', result = {} as T) => this.handleError(serviceName, operation, result) 17 | 18 | handleError (serviceName = '', operation = 'operation', result = {} as T) { 19 | 20 | return (response: HttpErrorResponse): Observable => { 21 | // Optionally send the error to a third part error logging service 22 | console.error(response); 23 | 24 | // Show a simple alert if error 25 | const message = (response.error instanceof ErrorEvent) ? 26 | response.error.message : 27 | `server returned code ${response.status} with body "${response.error.error}"`; 28 | 29 | // We are using alert just for example, on real world avoid this pratice 30 | alert(message); 31 | 32 | // Keep running and returning a safe result. 33 | return of( result ); 34 | }; 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Client/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/.gitkeep -------------------------------------------------------------------------------- /Client/src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /Client/src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /Client/src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /Client/src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /Client/src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /Client/src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /Client/src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /Client/src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /Client/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /Client/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | apiUrl: 'https://hostname/api' 4 | }; 5 | -------------------------------------------------------------------------------- /Client/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 | apiUrl: 'http://localhost:8081/api' 8 | }; 9 | 10 | /* 11 | * In development mode, to ignore zone related error stack frames such as 12 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 13 | * import the following file, but please comment it out in production mode 14 | * because it will have performance impact when throw error 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /Client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Client/src/favicon.ico -------------------------------------------------------------------------------- /Client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Client/src/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'), 20 | reports: ['html', 'lcovonly'], 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 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /Client/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.log(err)); 13 | -------------------------------------------------------------------------------- /Client/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Client", 3 | "short_name": "Client", 4 | "theme_color": "#1976d2", 5 | "background_color": "#fafafa", 6 | "display": "standalone", 7 | "scope": "/", 8 | "start_url": "/", 9 | "icons": [ 10 | { 11 | "src": "assets/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "assets/icons/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "assets/icons/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "assets/icons/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "assets/icons/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "assets/icons/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "assets/icons/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "assets/icons/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /Client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Web Animations `@angular/platform-browser/animations` 51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 53 | **/ 54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 55 | 56 | /** 57 | * By default, zone.js will patch all possible macroTask and DomEvents 58 | * user can disable parts of macroTask/DomEvents patch by setting following flags 59 | */ 60 | 61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 64 | 65 | /* 66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 68 | */ 69 | // (window as any).__Zone_enable_cross_context_check = true; 70 | 71 | /*************************************************************************************************** 72 | * Zone JS is required by default for Angular itself. 73 | */ 74 | import 'zone.js/dist/zone'; // Included with Angular CLI. 75 | 76 | 77 | 78 | /*************************************************************************************************** 79 | * APPLICATION IMPORTS 80 | */ 81 | -------------------------------------------------------------------------------- /Client/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: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /Client/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Client/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /Client/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tslint.json", "../node_modules/tslint-angular"], 3 | "rules": { 4 | "angular-whitespace": [true, "check-interpolation", "check-semicolon"], 5 | "no-unused-variable": true, 6 | "no-unused-css": true, 7 | "banana-in-box": true, 8 | "use-view-encapsulation": true, 9 | "contextual-life-cycle": true, 10 | "directive-selector": [ 11 | true, 12 | "attribute", 13 | "app", 14 | "camelCase" 15 | ], 16 | "component-selector": [ 17 | true, 18 | "element", 19 | "app", 20 | "kebab-case" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Laravel Docker Starter-kit 2 | 3 | This project is based on Hands-On-Full-Stack-Web-Development-with-Angular-6-and-Laravel-5 book. 4 | 5 | # How to run # 6 | 7 | Dependencies: 8 | 9 | * Docker engine v1.13 or higher. Your OS provided package might be a little old, if you encounter problems, do upgrade. See [https://docs.docker.com/engine/installation](https://docs.docker.com/engine/installation) 10 | * Docker compose v1.12 or higher. See [docs.docker.com/compose/install](https://docs.docker.com/compose/install/) 11 | 12 | Once you're done, simply `cd` to your project and run `docker-compose up -d`. This will initialise and start all the containers, then leave them running in the background. 13 | 14 | ## Services exposed outside your environment ## 15 | 16 | You can access your application via **`localhost`**, if you're running the containers directly, or through **``** when run on a vm. nginx and mailhog both respond to any hostname, in case you want to add your own hostname on your `/etc/hosts` 17 | 18 | Service|Address outside containers 19 | ------|---------|----------- 20 | Webserver|[localhost:8081](http://localhost:8081) 21 | MySQL|**host:** `localhost`; **port:** `8083` 22 | 23 | ## Hosts within your environment ## 24 | 25 | You'll need to configure your application to use any services you enabled: 26 | 27 | Service|Hostname|Port number 28 | ------|---------|----------- 29 | php-fpm|php-fpm|9000 30 | MySQL|mysql|3306 (default) 31 | 32 | # Docker compose cheatsheet # 33 | 34 | **Note:** you need to cd first to where your docker-compose.yml file lives. 35 | 36 | * Start containers in the background: `docker-compose up -d` 37 | * Start containers on the foreground: `docker-compose up`. You will see a stream of logs for every container running. 38 | * Stop containers: `docker-compose stop` 39 | * Kill containers: `docker-compose kill` 40 | * View container logs: `docker-compose logs` 41 | * Execute command inside of container: `docker-compose exec SERVICE_NAME COMMAND` where `COMMAND` is whatever you want to run. Examples: 42 | * Shell into the PHP container, `docker-compose exec php-fpm bash` 43 | * Run symfony console, `docker-compose exec php-fpm bin/console` 44 | * Open a mysql shell, `docker-compose exec mysql mysql -uroot -pCHOSEN_ROOT_PASSWORD` 45 | 46 | # Recommendations # 47 | 48 | It's hard to avoid file permission issues when fiddling about with containers due to the fact that, from your OS point of view, any files created within the container are owned by the process that runs the docker engine (this is usually root). Different OS will also have different problems, for instance you can run stuff in containers using `docker exec -it -u $(id -u):$(id -g) CONTAINER_NAME COMMAND` to force your current user ID into the process, but this will only work if your host OS is Linux, not mac. Follow a couple of simple rules and save yourself a world of hurt. 49 | 50 | * Run composer outside of the php container, as doing so would install all your dependencies owned by `root` within your vendor folder. 51 | * Run commands (ie Symfony's console, or Laravel's artisan) straight inside of your container. You can easily open a shell as described above and do your thing from there. 52 | 53 | *README file generated with PHPDocker.io* 54 | 55 | # Application Ports: 56 | 57 | http://localhost:8081/api/documentation API Docs 58 | 59 | http://localhost:3000/ Front-end application -------------------------------------------------------------------------------- /Server/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY=base64:kV/9t001IT1fs3Ha2eaRQVXSXZHTpAF5c64NcAniNytY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=stack 8 | 9 | DB_CONNECTION=mysql 10 | DB_HOST=mysql 11 | DB_PORT=3306 12 | DB_DATABASE=chapter-10 13 | DB_USERNAME=chapter-10 14 | DB_PASSWORD=123456 15 | 16 | BROADCAST_DRIVER=log 17 | CACHE_DRIVER=file 18 | SESSION_DRIVER=file 19 | SESSION_LIFETIME=120 20 | QUEUE_DRIVER=sync 21 | 22 | REDIS_HOST=127.0.0.1 23 | REDIS_PASSWORD=null 24 | REDIS_PORT=6379 25 | 26 | MAIL_DRIVER=smtp 27 | MAIL_HOST=smtp.mailtrap.io 28 | MAIL_PORT=2525 29 | MAIL_USERNAME=null 30 | MAIL_PASSWORD=null 31 | MAIL_ENCRYPTION=null 32 | 33 | PUSHER_APP_ID= 34 | PUSHER_APP_KEY= 35 | PUSHER_APP_SECRET= 36 | PUSHER_APP_CLUSTER=mt1 37 | 38 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 39 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 40 | 41 | JWT_SECRET=McR1It4Bw9G8jU1b4XJhDMeZs4Q5Z9nM 42 | # Override default token expires 43 | JWT_TTL=120 44 | # JWT_REFRESH_TTL=21600 45 | # JWT_BLACKLIST_GRACE_PERIOD=30 -------------------------------------------------------------------------------- /Server/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /Server/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /storage/*.key 5 | /vendor 6 | /.idea 7 | /.vscode 8 | /.vagrant 9 | Homestead.json 10 | Homestead.yaml 11 | npm-debug.log 12 | yarn-error.log 13 | .env 14 | -------------------------------------------------------------------------------- /Server/app/Bike.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Builder'); 75 | } 76 | 77 | public function items() { 78 | return $this->hasMany('App\Item'); 79 | } 80 | 81 | public function garages() { 82 | return $this->belongsToMany('App\Garage'); 83 | } 84 | 85 | public function user() { 86 | return $this->belongsTo(User::class); 87 | } 88 | 89 | public function ratings() { 90 | return $this->hasMany(Rating::class); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Server/app/Builder.php: -------------------------------------------------------------------------------- 1 | hasOne('App\Bike'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Server/app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 28 | // ->hourly(); 29 | } 30 | 31 | /** 32 | * Register the commands for the application. 33 | * 34 | * @return void 35 | */ 36 | protected function commands() 37 | { 38 | $this->load(__DIR__.'/Commands'); 39 | 40 | require base_path('routes/console.php'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Server/app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | wantsJson() // Enable header Accept: application/json to see the proper error msg 61 | ) { 62 | return response()->json(['error' => 'Resource not found'], 404); 63 | } 64 | 65 | 66 | if ($exception instanceof MethodNotAllowedHttpException) { 67 | return response()->json(['error' => 'Method Not Allowed'], 405); 68 | } 69 | 70 | if ($exception instanceof UnauthorizedHttpException) { 71 | return response()->json(['error' => 'Token not provided'], 401); 72 | } 73 | 74 | // JWT Auth related errors 75 | if ($exception instanceof JWTException) { 76 | return response()->json(['error' => $exception], $exception->getStatusCode()); 77 | } 78 | 79 | if ($exception instanceof TokenExpiredException) { 80 | return response()->json(['error' => 'token_expired'], $exception->getStatusCode()); 81 | 82 | } else if ($exception instanceof TokenInvalidException) { 83 | return response()->json(['error' => 'token_invalid'], $exception->getStatusCode()); 84 | } 85 | 86 | return parent::render($request, $exception); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Server/app/Garage.php: -------------------------------------------------------------------------------- 1 | belongsToMany('App\Bike', 'bike_garage', 'bike_id', 'garage_id'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Server/app/Http/Controllers/API/RatingController.php: -------------------------------------------------------------------------------- 1 | $request->user()->id, 69 | 'bike_id' => $bike->id, 70 | ], 71 | ['rating' => $request->rating] 72 | ); 73 | 74 | return new RatingResource($rating); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Server/app/Http/Controllers/ApiController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Server/app/Http/Controllers/Auth/LoginController.php: -------------------------------------------------------------------------------- 1 | middleware('guest')->except('logout'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Server/app/Http/Controllers/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 41 | } 42 | 43 | /** 44 | * Get a validator for an incoming registration request. 45 | * 46 | * @param array $data 47 | * @return \Illuminate\Contracts\Validation\Validator 48 | */ 49 | protected function validator(array $data) 50 | { 51 | return Validator::make($data, [ 52 | 'name' => 'required|string|max:255', 53 | 'email' => 'required|string|email|max:255|unique:users', 54 | 'password' => 'required|string|min:6|confirmed', 55 | ]); 56 | } 57 | 58 | /** 59 | * Create a new user instance after a valid registration. 60 | * 61 | * @param array $data 62 | * @return \App\User 63 | */ 64 | protected function create(array $data) 65 | { 66 | return User::create([ 67 | 'name' => $data['name'], 68 | 'email' => $data['email'], 69 | 'password' => Hash::make($data['password']), 70 | ]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Server/app/Http/Controllers/Auth/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Server/app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | [ 31 | \App\Http\Middleware\EncryptCookies::class, 32 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 33 | \Illuminate\Session\Middleware\StartSession::class, 34 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 35 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 36 | \App\Http\Middleware\VerifyCsrfToken::class, 37 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 38 | ], 39 | 40 | 'api' => [ 41 | \Barryvdh\Cors\HandleCors::class, 42 | 'throttle:60,1', 43 | 'bindings', 44 | 45 | ], 46 | ]; 47 | 48 | /** 49 | * The application's route middleware. 50 | * 51 | * These middleware may be assigned to groups or used individually. 52 | * 53 | * @var array 54 | */ 55 | protected $routeMiddleware = [ 56 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 57 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 58 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 59 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 60 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 61 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 62 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 63 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 64 | ]; 65 | } 66 | -------------------------------------------------------------------------------- /Server/app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/home'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Server/app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | $this->id, 20 | 'make' => $this->make, 21 | 'model' => $this->model, 22 | 'year' => $this->year, 23 | 'mods' => $this->mods, 24 | 'picture' => $this->picture, 25 | 'garages' => $this->garages, 26 | 'items' => $this->items, 27 | 'builder' => $this->builder, 28 | 'user' => $this->user, 29 | 'ratings' => $this->ratings, 30 | 'average_rating' => $this->ratings->avg('rating'), 31 | // Casting objects to string, to avoid receive create_at and update_at as object 32 | 'created_at' => (string) $this->created_at, 33 | 'updated_at' => (string) $this->updated_at 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Server/app/Http/Resources/BuildersResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'name' => $this->name, 20 | 'description' => $this->description, 21 | 'location' => $this->location, 22 | 'bike' => $this->bike, 23 | // Casting objects to string, to avoid receive create_at and update_at as object 24 | 'created_at' => (string) $this->created_at, 25 | 'updated_at' => (string) $this->updated_at, 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Server/app/Http/Resources/ItemsResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'type' => $this->type, 20 | 'name' => $this->name, 21 | 'company' => $this->company, 22 | 'bike_id' => $this->bike_id, 23 | // Casting objects to string, to avoid receive create_at and update_at as object 24 | 'created_at' => (string) $this->created_at, 25 | 'updated_at' => (string) $this->updated_at 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Server/app/Http/Resources/RatingResource.php: -------------------------------------------------------------------------------- 1 | $this->user_id, 20 | 'bike_id' => $this->bike_id, 21 | 'rating' => $this->rating, 22 | 'bike' => $this->bike, 23 | 'average_rating' => $this->bike->ratings->avg('rating'), 24 | // Casting objects to string, to avoid receive create_at and update_at as object 25 | 'created_at' => (string) $this->created_at, 26 | 'updated_at' => (string) $this->updated_at 27 | ]; 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Server/app/Item.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Bike'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Server/app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Server/app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'App\Listeners\EventListener', 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any events for your application. 23 | * 24 | * @return void 25 | */ 26 | public function boot() 27 | { 28 | parent::boot(); 29 | 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Server/app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 39 | 40 | $this->mapWebRoutes(); 41 | 42 | // 43 | } 44 | 45 | /** 46 | * Define the "web" routes for the application. 47 | * 48 | * These routes all receive session state, CSRF protection, etc. 49 | * 50 | * @return void 51 | */ 52 | protected function mapWebRoutes() 53 | { 54 | Route::middleware('web') 55 | ->namespace($this->namespace) 56 | ->group(base_path('routes/web.php')); 57 | } 58 | 59 | /** 60 | * Define the "api" routes for the application. 61 | * 62 | * These routes are typically stateless. 63 | * 64 | * @return void 65 | */ 66 | protected function mapApiRoutes() 67 | { 68 | Route::prefix('api') 69 | ->middleware('api') 70 | ->namespace($this->namespace) 71 | ->group(base_path('routes/api.php')); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Server/app/Rating.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Bike'); 53 | } 54 | } -------------------------------------------------------------------------------- /Server/app/User.php: -------------------------------------------------------------------------------- 1 | getKey(); 65 | } 66 | 67 | public function getJWTCustomClaims() 68 | { 69 | return []; 70 | } 71 | 72 | /** 73 | * Relationship. 74 | * 75 | * @var string 76 | */ 77 | 78 | public function bikes() 79 | { 80 | return $this->hasMany(App\Bike); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Server/artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /Server/bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /Server/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /Server/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "description": "The Laravel Framework.", 4 | "keywords": ["framework", "laravel"], 5 | "license": "MIT", 6 | "type": "project", 7 | "require": { 8 | "php": "^7.1.3", 9 | "barryvdh/laravel-cors": "^0.11.0", 10 | "darkaonline/l5-swagger": "^5.6", 11 | "fideloper/proxy": "^4.0", 12 | "laravel/framework": "5.6.*", 13 | "laravel/tinker": "^1.0", 14 | "tymon/jwt-auth": "1.0.*" 15 | }, 16 | "require-dev": { 17 | "filp/whoops": "^2.0", 18 | "fzaninotto/faker": "^1.4", 19 | "mockery/mockery": "^1.0", 20 | "nunomaduro/collision": "^2.0", 21 | "phpunit/phpunit": "^7.0" 22 | }, 23 | "autoload": { 24 | "classmap": [ 25 | "database/seeds", 26 | "database/factories" 27 | ], 28 | "psr-4": { 29 | "App\\": "app/" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "Tests\\": "tests/" 35 | } 36 | }, 37 | "extra": { 38 | "laravel": { 39 | "dont-discover": [ 40 | ] 41 | } 42 | }, 43 | "scripts": { 44 | "post-root-package-install": [ 45 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 46 | ], 47 | "post-create-project-cmd": [ 48 | "@php artisan key:generate" 49 | ], 50 | "post-autoload-dump": [ 51 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 52 | "@php artisan package:discover" 53 | ] 54 | }, 55 | "config": { 56 | "preferred-install": "dist", 57 | "sort-packages": true, 58 | "optimize-autoloader": true 59 | }, 60 | "minimum-stability": "dev", 61 | "prefer-stable": true 62 | } 63 | -------------------------------------------------------------------------------- /Server/config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'api', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'api' => [ 45 | 'driver' => 'jwt', 46 | 'provider' => 'users', 47 | ], 48 | ], 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | User Providers 53 | |-------------------------------------------------------------------------- 54 | | 55 | | All authentication drivers have a user provider. This defines how the 56 | | users are actually retrieved out of your database or other storage 57 | | mechanisms used by this application to persist your user's data. 58 | | 59 | | If you have multiple user tables or models you may configure multiple 60 | | sources which represent each model / table. These sources may then 61 | | be assigned to any extra authentication guards you have defined. 62 | | 63 | | Supported: "database", "eloquent" 64 | | 65 | */ 66 | 67 | 'providers' => [ 68 | 'users' => [ 69 | 'driver' => 'eloquent', 70 | 'model' => App\User::class, 71 | ], 72 | 73 | // 'users' => [ 74 | // 'driver' => 'database', 75 | // 'table' => 'users', 76 | // ], 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Resetting Passwords 82 | |-------------------------------------------------------------------------- 83 | | 84 | | You may specify multiple password reset configurations if you have more 85 | | than one user table or model in the application and you want to have 86 | | separate password reset settings based on the specific user types. 87 | | 88 | | The expire time is the number of minutes that the reset token should be 89 | | considered valid. This security feature keeps tokens short-lived so 90 | | they have less time to be guessed. You may change this as needed. 91 | | 92 | */ 93 | 94 | 'passwords' => [ 95 | 'users' => [ 96 | 'provider' => 'users', 97 | 'table' => 'password_resets', 98 | 'expire' => 60, 99 | ], 100 | ], 101 | 102 | ]; 103 | -------------------------------------------------------------------------------- /Server/config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'encrypted' => true, 41 | ], 42 | ], 43 | 44 | 'redis' => [ 45 | 'driver' => 'redis', 46 | 'connection' => 'default', 47 | ], 48 | 49 | 'log' => [ 50 | 'driver' => 'log', 51 | ], 52 | 53 | 'null' => [ 54 | 'driver' => 'null', 55 | ], 56 | 57 | ], 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /Server/config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | */ 30 | 31 | 'stores' => [ 32 | 33 | 'apc' => [ 34 | 'driver' => 'apc', 35 | ], 36 | 37 | 'array' => [ 38 | 'driver' => 'array', 39 | ], 40 | 41 | 'database' => [ 42 | 'driver' => 'database', 43 | 'table' => 'cache', 44 | 'connection' => null, 45 | ], 46 | 47 | 'file' => [ 48 | 'driver' => 'file', 49 | 'path' => storage_path('framework/cache/data'), 50 | ], 51 | 52 | 'memcached' => [ 53 | 'driver' => 'memcached', 54 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 55 | 'sasl' => [ 56 | env('MEMCACHED_USERNAME'), 57 | env('MEMCACHED_PASSWORD'), 58 | ], 59 | 'options' => [ 60 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 61 | ], 62 | 'servers' => [ 63 | [ 64 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 65 | 'port' => env('MEMCACHED_PORT', 11211), 66 | 'weight' => 100, 67 | ], 68 | ], 69 | ], 70 | 71 | 'redis' => [ 72 | 'driver' => 'redis', 73 | 'connection' => 'default', 74 | ], 75 | 76 | ], 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Cache Key Prefix 81 | |-------------------------------------------------------------------------- 82 | | 83 | | When utilizing a RAM based store such as APC or Memcached, there might 84 | | be other applications utilizing the same cache. So, we'll specify a 85 | | value to get prefixed to all our keys so we can avoid collisions. 86 | | 87 | */ 88 | 89 | 'prefix' => env( 90 | 'CACHE_PREFIX', 91 | str_slug(env('APP_NAME', 'laravel'), '_').'_cache' 92 | ), 93 | 94 | ]; 95 | -------------------------------------------------------------------------------- /Server/config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DRIVER', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Cloud Filesystem Disk 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Many applications store files both locally and in the cloud. For this 24 | | reason, you may specify a default "cloud" driver here. This driver 25 | | will be bound as the Cloud disk implementation in the container. 26 | | 27 | */ 28 | 29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Filesystem Disks 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here you may configure as many filesystem "disks" as you wish, and you 37 | | may even configure multiple disks of the same driver. Defaults have 38 | | been setup for each driver as an example of the required options. 39 | | 40 | | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace" 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'url' => env('APP_URL').'/storage', 55 | 'visibility' => 'public', 56 | ], 57 | 58 | 's3' => [ 59 | 'driver' => 's3', 60 | 'key' => env('AWS_ACCESS_KEY_ID'), 61 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 62 | 'region' => env('AWS_DEFAULT_REGION'), 63 | 'bucket' => env('AWS_BUCKET'), 64 | 'url' => env('AWS_URL'), 65 | ], 66 | 67 | ], 68 | 69 | ]; 70 | -------------------------------------------------------------------------------- /Server/config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /Server/config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Log Channels 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the log channels for your application. Out of 24 | | the box, Laravel uses the Monolog PHP logging library. This gives 25 | | you a variety of powerful log handlers / formatters to utilize. 26 | | 27 | | Available Drivers: "single", "daily", "slack", "syslog", 28 | | "errorlog", "custom", "stack" 29 | | 30 | */ 31 | 32 | 'channels' => [ 33 | 'stack' => [ 34 | 'driver' => 'stack', 35 | 'channels' => ['single'], 36 | ], 37 | 38 | 'single' => [ 39 | 'driver' => 'single', 40 | 'path' => storage_path('logs/laravel.log'), 41 | 'level' => 'debug', 42 | ], 43 | 44 | 'daily' => [ 45 | 'driver' => 'daily', 46 | 'path' => storage_path('logs/laravel.log'), 47 | 'level' => 'debug', 48 | 'days' => 7, 49 | ], 50 | 51 | 'slack' => [ 52 | 'driver' => 'slack', 53 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 54 | 'username' => 'Laravel Log', 55 | 'emoji' => ':boom:', 56 | 'level' => 'critical', 57 | ], 58 | 59 | 'syslog' => [ 60 | 'driver' => 'syslog', 61 | 'level' => 'debug', 62 | ], 63 | 64 | 'errorlog' => [ 65 | 'driver' => 'errorlog', 66 | 'level' => 'debug', 67 | ], 68 | ], 69 | 70 | ]; 71 | -------------------------------------------------------------------------------- /Server/config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection information for each server that 24 | | is used by your application. A default configuration has been added 25 | | for each back-end shipped with Laravel. You are free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'retry_after' => 90, 49 | ], 50 | 51 | 'sqs' => [ 52 | 'driver' => 'sqs', 53 | 'key' => env('SQS_KEY', 'your-public-key'), 54 | 'secret' => env('SQS_SECRET', 'your-secret-key'), 55 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 56 | 'queue' => env('SQS_QUEUE', 'your-queue-name'), 57 | 'region' => env('SQS_REGION', 'us-east-1'), 58 | ], 59 | 60 | 'redis' => [ 61 | 'driver' => 'redis', 62 | 'connection' => 'default', 63 | 'queue' => 'default', 64 | 'retry_after' => 90, 65 | 'block_for' => null, 66 | ], 67 | 68 | ], 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Failed Queue Jobs 73 | |-------------------------------------------------------------------------- 74 | | 75 | | These options configure the behavior of failed queue job logging so you 76 | | can control which database and table are used to store the jobs that 77 | | have failed. You may change them to any database / table you wish. 78 | | 79 | */ 80 | 81 | 'failed' => [ 82 | 'database' => env('DB_CONNECTION', 'mysql'), 83 | 'table' => 'failed_jobs', 84 | ], 85 | 86 | ]; 87 | -------------------------------------------------------------------------------- /Server/config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'ses' => [ 23 | 'key' => env('SES_KEY'), 24 | 'secret' => env('SES_SECRET'), 25 | 'region' => 'us-east-1', 26 | ], 27 | 28 | 'sparkpost' => [ 29 | 'secret' => env('SPARKPOST_SECRET'), 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => App\User::class, 34 | 'key' => env('STRIPE_KEY'), 35 | 'secret' => env('STRIPE_SECRET'), 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /Server/config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /Server/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /Server/database/data-sample/bikes.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 1, 3 | "make": "Harley Davidson", 4 | "model": "XL1200 Nightster", 5 | "year": "2009", 6 | "mods": "Nobis vero sint non eius. Laboriosam sed odit hic quia doloribus. Numquam laboriosam numquam quas quis.", 7 | "picture": "https://dummyimage.com/640x480/717171/fff.jpg&text=placeholder-image", 8 | "user_id": 2, 9 | "builder_id": 1 10 | }, { 11 | "id": 2, 12 | "make": "Harley Davidson", 13 | "model": "Blackline", 14 | "year": "2008", 15 | "mods": "Nobis vero sint non eius. Laboriosam sed odit hic quia doloribus. Numquam laboriosam numquam quas quis.", 16 | "picture": "https://dummyimage.com/640x480/717171/fff.jpg&text=placeholder-image", 17 | "user_id": 1, 18 | "builder_id": 2 19 | }, { 20 | "id": 3, 21 | "make": "Harley Davidson", 22 | "model": "Dyna Switchback", 23 | "year": "2009", 24 | "mods": "Nobis vero sint non eius. Laboriosam sed odit hic quia doloribus. Numquam laboriosam numquam quas quis.", 25 | "picture": "https://dummyimage.com/640x480/717171/fff.jpg&text=placeholder-image", 26 | "user_id": 2, 27 | "builder_id": 3 28 | }, { 29 | "id": 4, 30 | "make": "Harley Davidson", 31 | "model": "Dyna Super Glide", 32 | "year": "2009", 33 | "mods": "Nobis vero sint non eius. Laboriosam sed odit hic quia doloribus. Numquam laboriosam numquam quas quis.", 34 | "picture": "https://dummyimage.com/640x480/717171/fff.jpg&text=placeholder-image", 35 | "user_id": 4, 36 | "builder_id": 4 37 | },{ 38 | "id": 5, 39 | "make": "Harley Davidson", 40 | "model": "Dyna Wild Glide", 41 | "year": "2005", 42 | "mods": "Nobis vero sint non eius. Laboriosam sed odit hic quia doloribus. Numquam laboriosam numquam quas quis.", 43 | "picture": "https://dummyimage.com/640x480/717171/fff.jpg&text=placeholder-image", 44 | "user_id": 5, 45 | "builder_id": 5 46 | }] -------------------------------------------------------------------------------- /Server/database/data-sample/bikes_garages.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "bike_id": 1, 3 | "garage_id": 2 4 | },{ 5 | "bike_id": 4, 6 | "garage_id": 2 7 | }] -------------------------------------------------------------------------------- /Server/database/data-sample/builders.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 1, 3 | "name": "Diamond Atelier", 4 | "description": "Diamond Atelier was founded by two fellow riders who grew tired of the same played-out custom bike look and feel they and their friends had grown accustomed to witnessing.", 5 | "location": "Munich, Germany" 6 | },{ 7 | "id": 2, 8 | "name": "Deus Ex Machina's", 9 | "description": "Established in Australia back in 2006. And what started on the East Coast of Australia has spread across the world, building an empire of cafe racers.", 10 | "location": "Sydney, Australia" 11 | },{ 12 | "id": 3, 13 | "name": "Rough Crafts", 14 | "description": "A true testament to how far the custom bike world has come since the introduction of motorcycles in the early 20th century, Taiwan-based Rough Crafts is a design powerhouse.", 15 | "location": "Taiwan" 16 | },{ 17 | "id": 4, 18 | "name": "Roldand Sands", 19 | "description": "Is an American motorcycle racer and designer of custom high-performance motorcycles.", 20 | "location": "California, USA" 21 | },{ 22 | "id": 5, 23 | "name": "Chopper Dave", 24 | "description": "An artist, a biker, a builder and an innovator among other things, but what it comes down to is David “ChopperDave” Freston is a motorcycle builder and fabricator that is passionate about motorcycles", 25 | "location": "California, USA" 26 | }] -------------------------------------------------------------------------------- /Server/database/data-sample/garages.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 1, 3 | "name": "Martin Smith", 4 | "customer_level": 8 5 | }, { 6 | "id": 2, 7 | "name": "Collin James", 8 | "customer_level": 9 9 | }] -------------------------------------------------------------------------------- /Server/database/data-sample/items.json: -------------------------------------------------------------------------------- 1 | 2 | [{ 3 | "id": 1, 4 | "type": "Handlebars", 5 | "name": "Apes Hanger 16 ", 6 | "company": "TC Bros", 7 | "bike_id": 2 8 | },{ 9 | "id": 2, 10 | "type": "Seat", 11 | "name": "Challenger", 12 | "company": "Biltwell Inc", 13 | "bike_id": 3 14 | },{ 15 | "id": 3, 16 | "type": "Exhaust", 17 | "name": "Side Shots", 18 | "company": "Vance and Hines", 19 | "bike_id": 3 20 | }] -------------------------------------------------------------------------------- /Server/database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function (Faker $faker) { 17 | return [ 18 | 'name' => $faker->name, 19 | 'email' => $faker->unique()->safeEmail, 20 | 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 21 | 'remember_token' => str_random(10), 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /Server/database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->string('password'); 21 | $table->rememberToken(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('users'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Server/database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Server/database/migrations/2018_04_08_141302_create_bikes_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('make'); 19 | $table->string('model'); 20 | $table->string('year'); 21 | $table->text('mods'); 22 | $table->string('picture'); 23 | $table->unsignedInteger('user_id'); 24 | $table->unsignedInteger('builder_id'); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('bikes'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Server/database/migrations/2018_04_15_145832_create_builders_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->text('description'); 20 | $table->string('location'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('builders'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Server/database/migrations/2018_04_15_150139_create_items_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('type'); 19 | $table->string('name'); 20 | $table->text('company'); 21 | $table->integer('bike_id'); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('items'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Server/database/migrations/2018_04_15_150327_create_garages_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->integer('customer_level'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('garages'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Server/database/migrations/2018_04_18_011906_create_bike_garage_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('bike_id'); 19 | $table->integer('garage_id'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('bike_garage'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Server/database/migrations/2018_05_01_134024_create_ratings_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('user_id'); 19 | $table->unsignedInteger('bike_id'); 20 | $table->unsignedInteger('rating'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('ratings'); 33 | } 34 | } -------------------------------------------------------------------------------- /Server/database/seeds/BikeGarageTableSeeder.php: -------------------------------------------------------------------------------- 1 | insert([ 15 | 'bike_id' => 1, 16 | 'garage_id' => 2 17 | ]); 18 | DB::table('bike_garage')->insert([ 19 | 'bike_id' => 2, 20 | 'garage_id' => 2 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Server/database/seeds/BikesTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 16 | $json = File::get("database/data-sample/bikes.json"); 17 | $data = json_decode($json); 18 | foreach ($data as $obj) { 19 | Bike::create(array( 20 | 'id' => $obj->id, 21 | 'make' => $obj->make, 22 | 'model' => $obj->model, 23 | 'year' => $obj->year, 24 | 'mods' => $obj->mods, 25 | 'picture'=> $obj->picture, 26 | 'user_id' => $obj->user_id, 27 | 'builder_id' => $obj->builder_id 28 | )); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Server/database/seeds/BuildersTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 16 | $json = File::get("database/data-sample/builders.json"); 17 | $data = json_decode($json); 18 | foreach ($data as $obj) { 19 | Builder::create(array( 20 | 'id' => $obj->id, 21 | 'name' => $obj->name, 22 | 'description' => $obj->description, 23 | 'location' => $obj->location 24 | )); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Server/database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 15 | $this->call(BikesTableSeeder::class); 16 | $this->call(BuildersTableSeeder::class); 17 | $this->call(ItemsTableSeeder::class); 18 | $this->call(GaragesTableSeeder::class); 19 | $this->call(BikeGarageTableSeeder::class); 20 | $this->call(RatingsTableSeeder::class); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Server/database/seeds/GaragesTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 16 | $json = File::get("database/data-sample/garages.json"); 17 | $data = json_decode($json); 18 | foreach ($data as $obj) { 19 | Garage::create(array( 20 | 'id' => $obj->id, 21 | 'name' => $obj->name, 22 | 'customer_level' => $obj->customer_level 23 | )); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Server/database/seeds/ItemsTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 16 | $json = File::get("database/data-sample/items.json"); 17 | $data = json_decode($json); 18 | foreach ($data as $obj) { 19 | Item::create(array( 20 | 'id' => $obj->id, 21 | 'type' => $obj->type, 22 | 'name' => $obj->name, 23 | 'company' => $obj->company, 24 | 'bike_id' => $obj->bike_id 25 | )); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Server/database/seeds/RatingsTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 15 | DB::table('ratings')->insert([ 16 | 'user_id' => '1', 17 | 'bike_id' => '3', 18 | 'rating' => '3' 19 | ]); 20 | DB::table('ratings')->insert([ 21 | 'user_id' => '2', 22 | 'bike_id' => '1', 23 | 'rating' => '3' 24 | ]); 25 | } 26 | } -------------------------------------------------------------------------------- /Server/database/seeds/UsersTableSeeder.php: -------------------------------------------------------------------------------- 1 | insert([ 15 | 'name' => 'Johnny Cash', 16 | 'email' => 'johnny@cash.com', 17 | 'password' => bcrypt('123456') 18 | ]); 19 | DB::table('users')->insert([ 20 | 'name' => 'Frank Sinatra', 21 | 'email' => 'frank@sinatra.com', 22 | 'password' => bcrypt('123456') 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.18", 14 | "bootstrap": "^4.0.0", 15 | "popper.js": "^1.12", 16 | "cross-env": "^5.1", 17 | "jquery": "^3.2", 18 | "laravel-mix": "^2.0", 19 | "lodash": "^4.17.4", 20 | "vue": "^2.5.7" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Server/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/Feature 14 | 15 | 16 | 17 | ./tests/Unit 18 | 19 | 20 | 21 | 22 | ./app 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Server/public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /Server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Server/public/favicon.ico -------------------------------------------------------------------------------- /Server/public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | define('LARAVEL_START', microtime(true)); 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Register The Auto Loader 15 | |-------------------------------------------------------------------------- 16 | | 17 | | Composer provides a convenient, automatically generated class loader for 18 | | our application. We just need to utilize it! We'll simply require it 19 | | into the script here so that we don't have to worry about manual 20 | | loading any of our classes later on. It feels great to relax. 21 | | 22 | */ 23 | 24 | require __DIR__.'/../vendor/autoload.php'; 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Turn On The Lights 29 | |-------------------------------------------------------------------------- 30 | | 31 | | We need to illuminate PHP development, so let us turn on the lights. 32 | | This bootstraps the framework and gets it ready for use, then it 33 | | will load up this application so that we can run it and send 34 | | the responses back to the browser and delight our users. 35 | | 36 | */ 37 | 38 | $app = require_once __DIR__.'/../bootstrap/app.php'; 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Run The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once we have the application, we can handle the incoming request 46 | | through the kernel, and send the associated response back to 47 | | the client's browser allowing them to enjoy the creative 48 | | and wonderful application we have prepared for them. 49 | | 50 | */ 51 | 52 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 53 | 54 | $response = $kernel->handle( 55 | $request = Illuminate\Http\Request::capture() 56 | ); 57 | 58 | $response->send(); 59 | 60 | $kernel->terminate($request, $response); 61 | -------------------------------------------------------------------------------- /Server/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /Server/public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Server/resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * First we will load all of this project's JavaScript dependencies which 4 | * includes Vue and other libraries. It is a great starting point when 5 | * building robust, powerful web applications using Vue and Laravel. 6 | */ 7 | 8 | require('./bootstrap'); 9 | 10 | window.Vue = require('vue'); 11 | 12 | /** 13 | * Next, we will create a fresh Vue application instance and attach it to 14 | * the page. Then, you may begin adding components to this application 15 | * or customize the JavaScript scaffolding to fit your unique needs. 16 | */ 17 | 18 | Vue.component('example-component', require('./components/ExampleComponent.vue')); 19 | 20 | const app = new Vue({ 21 | el: '#app' 22 | }); 23 | -------------------------------------------------------------------------------- /Server/resources/assets/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | 2 | window._ = require('lodash'); 3 | window.Popper = require('popper.js').default; 4 | 5 | /** 6 | * We'll load jQuery and the Bootstrap jQuery plugin which provides support 7 | * for JavaScript based Bootstrap features such as modals and tabs. This 8 | * code may be modified to fit the specific needs of your application. 9 | */ 10 | 11 | try { 12 | window.$ = window.jQuery = require('jquery'); 13 | 14 | require('bootstrap'); 15 | } catch (e) {} 16 | 17 | /** 18 | * We'll load the axios HTTP library which allows us to easily issue requests 19 | * to our Laravel back-end. This library automatically handles sending the 20 | * CSRF token as a header based on the value of the "XSRF" token cookie. 21 | */ 22 | 23 | window.axios = require('axios'); 24 | 25 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 26 | 27 | /** 28 | * Next we will register the CSRF Token as a common header with Axios so that 29 | * all outgoing HTTP requests automatically have it attached. This is just 30 | * a simple convenience so we don't have to attach every token manually. 31 | */ 32 | 33 | let token = document.head.querySelector('meta[name="csrf-token"]'); 34 | 35 | if (token) { 36 | window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; 37 | } else { 38 | console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); 39 | } 40 | 41 | /** 42 | * Echo exposes an expressive API for subscribing to channels and listening 43 | * for events that are broadcast by Laravel. Echo and event broadcasting 44 | * allows your team to easily build robust real-time web applications. 45 | */ 46 | 47 | // import Echo from 'laravel-echo' 48 | 49 | // window.Pusher = require('pusher-js'); 50 | 51 | // window.Echo = new Echo({ 52 | // broadcaster: 'pusher', 53 | // key: process.env.MIX_PUSHER_APP_KEY, 54 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 55 | // encrypted: true 56 | // }); 57 | -------------------------------------------------------------------------------- /Server/resources/assets/js/components/ExampleComponent.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /Server/resources/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Body 3 | $body-bg: #f5f8fa; 4 | 5 | // Typography 6 | $font-family-sans-serif: "Raleway", sans-serif; 7 | $font-size-base: 0.9rem; 8 | $line-height-base: 1.6; 9 | -------------------------------------------------------------------------------- /Server/resources/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | 2 | // Fonts 3 | @import url("https://fonts.googleapis.com/css?family=Raleway:300,400,600"); 4 | 5 | // Variables 6 | @import "variables"; 7 | 8 | // Bootstrap 9 | @import '~bootstrap/scss/bootstrap'; 10 | 11 | .navbar-laravel { 12 | background-color: #fff; 13 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04); 14 | } 15 | -------------------------------------------------------------------------------- /Server/resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /Server/resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /Server/resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /Server/resources/views/vendor/l5-swagger/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newaeonweb/angular-laravel-docker-starter/b824d0441f603919c2848972ca25a9d6c0eae08f/Server/resources/views/vendor/l5-swagger/.gitkeep -------------------------------------------------------------------------------- /Server/resources/views/welcome.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Laravel 9 | 10 | 11 | 12 | 13 | 14 | 66 | 67 | 68 |
69 | @if (Route::has('login')) 70 | 78 | @endif 79 | 80 |
81 |
82 | Laravel 83 |
84 | 85 | 92 |
93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /Server/routes/api.php: -------------------------------------------------------------------------------- 1 | 'API\BikeController', 25 | 'builders' => 'API\BuilderController', 26 | 'items' => 'API\ItemController', 27 | 'bikes/{bike}/ratings' => 'API\RatingController' 28 | ]); 29 | 30 | Route::middleware('jwt.auth')->get('me', function(Request $request) { 31 | return auth()->user(); 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /Server/routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 16 | }); 17 | -------------------------------------------------------------------------------- /Server/routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | -------------------------------------------------------------------------------- /Server/routes/web.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /Server/storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /Server/storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /Server/storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /Server/storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /Server/storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /Server/storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /Server/storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /Server/storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /Server/tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 20 | 21 | Hash::driver('bcrypt')->setRounds(4); 22 | 23 | return $app; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Server/tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Server/tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Server/webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/assets/js/app.js', 'public/js') 15 | .sass('resources/assets/sass/app.scss', 'public/css'); 16 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | services: 3 | 4 | mysql: 5 | image: mysql:5.7 6 | container_name: chapter-10-mysql 7 | working_dir: /application 8 | volumes: 9 | - .:/application 10 | - ./storage-db:/var/lib/mysql 11 | environment: 12 | - MYSQL_ROOT_PASSWORD=123456 13 | - MYSQL_DATABASE=chapter-10 14 | - MYSQL_USER=chapter-10 15 | - MYSQL_PASSWORD=123456 16 | ports: 17 | - "8083:3306" 18 | 19 | webserver: 20 | image: nginx:alpine 21 | container_name: chapter-10-webserver 22 | working_dir: /application 23 | volumes: 24 | - .:/application 25 | - ./docker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf 26 | ports: 27 | - "8081:80" 28 | 29 | php-fpm: 30 | build: docker/php-fpm 31 | container_name: chapter-10-php-fpm 32 | working_dir: /application 33 | volumes: 34 | - ./Server:/application 35 | - ./docker/php-fpm/php-ini-overrides.ini:/etc/php/7.2/fpm/conf.d/99-overrides.ini 36 | 37 | appserver: 38 | image: 'angular-laravel-book' 39 | container_name: chapter-10-appserver 40 | # Build the image if don't exist 41 | build: './Client' 42 | ports: 43 | - 3000:81 -------------------------------------------------------------------------------- /docker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default; 3 | 4 | client_max_body_size 308M; 5 | 6 | access_log /var/log/nginx/application.access.log; 7 | 8 | 9 | root /application/public; 10 | index index.php; 11 | 12 | if (!-e $request_filename) { 13 | rewrite ^.*$ /index.php last; 14 | } 15 | 16 | location ~ \.php$ { 17 | fastcgi_pass php-fpm:9000; 18 | fastcgi_index index.php; 19 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 20 | fastcgi_param PHP_VALUE "error_log=/var/log/nginx/application_php_errors.log"; 21 | fastcgi_buffers 16 16k; 22 | fastcgi_buffer_size 32k; 23 | include fastcgi_params; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /docker/php-fpm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM phpdockerio/php72-fpm:latest 2 | WORKDIR "/application" 3 | 4 | # Install selected extensions and other stuff 5 | RUN apt-get update \ 6 | && apt-get -y --no-install-recommends install php7.2-mysql php-xdebug libmcrypt-dev \ 7 | && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* -------------------------------------------------------------------------------- /docker/php-fpm/php-ini-overrides.ini: -------------------------------------------------------------------------------- 1 | upload_max_filesize = 300M 2 | post_max_size = 308M 3 | 4 | [Xdebug] 5 | zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so 6 | xdebug.remote_enable=1 7 | xdebug.remote_autostart=1 8 | xdebug.remote_host=192.168.25.6 # you must use your own IP address here 9 | xdebug.remote_port=9009 --------------------------------------------------------------------------------