├── .gitignore ├── .travis.yml ├── README.md ├── client ├── pom.xml └── src │ └── main │ └── ng │ ├── .editorconfig │ ├── .gitignore │ ├── angular.json │ ├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.e2e.json │ ├── karma.conf.js │ ├── package.json │ ├── protractor.conf.js │ ├── proxy.conf.json │ ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── core │ │ │ ├── core.module.ts │ │ │ ├── guard │ │ │ │ └── auth.guard.ts │ │ │ ├── interceptor │ │ │ │ ├── auth.interceptor.ts │ │ │ │ └── xhr.interceptor.ts │ │ │ └── service │ │ │ │ ├── abstract-service.ts │ │ │ │ ├── auth.service.ts │ │ │ │ └── echo.service.ts │ │ ├── feature │ │ │ ├── layout │ │ │ │ ├── home │ │ │ │ │ ├── home.component.html │ │ │ │ │ └── home.component.ts │ │ │ │ ├── layout.component.html │ │ │ │ ├── layout.component.ts │ │ │ │ ├── layout.module.ts │ │ │ │ ├── layout.route-module.ts │ │ │ │ └── lazy │ │ │ │ │ ├── lazy.component.ts │ │ │ │ │ └── lazy.module.ts │ │ │ ├── login │ │ │ │ ├── login.component.html │ │ │ │ ├── login.component.scss │ │ │ │ ├── login.component.ts │ │ │ │ └── login.module.ts │ │ │ └── page-not-found │ │ │ │ ├── page-not-found.component.html │ │ │ │ └── page-not-found.component.ts │ │ ├── model │ │ │ └── credentials.model.ts │ │ ├── shared │ │ │ └── shared.module.ts │ │ └── util │ │ │ └── api.const.ts │ ├── assets │ │ ├── favicon.ico │ │ ├── global.scss │ │ └── reset.scss │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── typings.d.ts │ ├── tsconfig.json │ ├── tslint.json │ └── yarn.lock ├── pom.xml └── server ├── pom.xml └── src └── main ├── kotlin └── com │ └── hiper2d │ ├── Application.kt │ ├── config │ ├── WebConfig.kt │ └── WebSecurityConfig.kt │ ├── controller │ ├── EchoController.kt │ └── UserController.kt │ └── security │ └── provider │ └── AnyAuthenticationProvider.kt └── resources ├── application.yml └── log4j2.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /**/target/ 3 | /**/*.log 4 | /**/*.iml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | notifications: 3 | email: false 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | spring-boot-angular-maven-starter 2 | ============= 3 | 4 | [![Kotlin](https://img.shields.io/badge/kotlin-1.2.x-blue.svg)](http://kotlinlang.org) [![TravisCI Build](https://travis-ci.org/hiper2d/spring-boot-angular-maven-starter.svg)](https://travis-ci.org/hiper2d/spring-boot-angular-maven-starter) 5 | 6 | An example of Spring Boot 2 and Angular 6 integration with the help of Maven, Yarn, Kotlin. 7 | 8 | Also includes `Spring Security Authentication` configured for a single page application client. The backend has no control over the frontend and only responds with 200/40x HTTP statuses. All login/logout redirects are performed on the client's side. Username and password are any Latin strings. 9 | 10 | ##### Client npm dependencies status: 11 | 12 | [![dependencies Status](https://david-dm.org/hiper2d/spring-boot-angular-maven-starter/status.svg?path=client/src/main/ng)](https://david-dm.org/hiper2d/spring-boot-angular-maven-starter?path=client/src/main/ng) 13 | [![devDependencies Status](https://david-dm.org/hiper2d/spring-boot-angular-maven-starter/dev-status.svg?path=client/src/main/ng)](https://david-dm.org/hiper2d/spring-boot-angular-maven-starter?path=client/src/main/ng&type=dev) 14 | 15 | Consists of [server](./server/) and [client](./client/) modules. 16 | 17 | In production mode, they both are compiled and built into a single WAR archive which can be deployed to a web application server or run directly as a standalone Java application. 18 | 19 | In development mode, you work with both modules separately. The server is built and run with the help of Maven from the 'server' directory. The client is operated via Yarn from the 'client/src/main/ng' directory. 20 | 21 | ### Build 22 | ##### Production mode 23 | ```bash 24 | # build both server and client 25 | mvn clean install -Pprod 26 | ``` 27 | ##### Development mode 28 | ```bash 29 | # build the server module without the client jar dependency 30 | # (can be run from the root or from the 'server' directories) 31 | mvn clean install 32 | 33 | # install client's npm dependencies (it's necessary for the first build only) 34 | # navigate to the 'client/src/main/ng' directory and run the following command 35 | yarn install 36 | ``` 37 | ### Run 38 | ##### Production mode 39 | ```bash 40 | # navigate to the 'server' directory and run the following command 41 | mvn spring-boot:run 42 | ``` 43 | > Access UI App at [http://localhost:9001](http://localhost:9001) 44 | ##### Development mode 45 | ```bash 46 | # navigate to the 'server' directory and run the following command 47 | mvn spring-boot:run 48 | 49 | # navigate to the 'client/src/main/ng' directory and run the following command 50 | yarn start 51 | ``` 52 | > Access UI App at [http://localhost:9002](http://localhost:9002) 53 | -------------------------------------------------------------------------------- /client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | client 8 | 1.0-SNAPSHOT 9 | jar 10 | 11 | 12 | com.hiper2d 13 | spring-boot-angular-maven-starter 14 | 1.0-SNAPSHOT 15 | 16 | 17 | 18 | 1.6 19 | v10.4.1 20 | v1.7.0 21 | 22 | 23 | 24 | 25 | 26 | 27 | com.github.eirslett 28 | frontend-maven-plugin 29 | ${frontend-maven-plugin-version} 30 | 31 | src/main/ng 32 | 33 | 34 | 35 | install node and npm 36 | 37 | install-node-and-yarn 38 | 39 | generate-resources 40 | 41 | ${node-version} 42 | ${yarn-version} 43 | 44 | 45 | 46 | yarn install 47 | 48 | yarn 49 | 50 | generate-resources 51 | 52 | install 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | prod 64 | 65 | 66 | 67 | ${basedir}/src/main/ng/dist 68 | 69 | ** 70 | 71 | META-INF/resources 72 | 73 | 74 | 75 | 76 | 77 | com.github.eirslett 78 | frontend-maven-plugin 79 | 80 | 81 | yarn build 82 | 83 | yarn 84 | 85 | 86 | run 87 | build 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /client/src/main/ng/.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/src/main/ng/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | 4 | # dependencies 5 | /node/ 6 | /node_modules/ 7 | 8 | # e2e 9 | /e2e/*.js 10 | /e2e/*.map 11 | -------------------------------------------------------------------------------- /client/src/main/ng/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "schematics": { 6 | "@schematics/angular:component": { 7 | "prefix": "app", 8 | "styleext": "scss" 9 | }, 10 | "@schematics/angular:directive": { 11 | "prefix": "app" 12 | } 13 | }, 14 | "projects": { 15 | "spring-boot-angular-maven-starter": { 16 | "root": "", 17 | "sourceRoot": "src", 18 | "projectType": "application", 19 | "architect": { 20 | "build": { 21 | "builder": "@angular-devkit/build-angular:browser", 22 | "options": { 23 | "outputPath": "dist", 24 | "index": "src/index.html", 25 | "main": "src/main.ts", 26 | "tsConfig": "src/tsconfig.app.json", 27 | "polyfills": "src/polyfills.ts", 28 | "assets": [ 29 | { 30 | "glob": "favicon.ico", 31 | "input": "src/assets", 32 | "output": "/" 33 | } 34 | ], 35 | "styles": [ 36 | "src/assets/global.scss", 37 | "src/assets/reset.scss" 38 | ], 39 | "scripts": [] 40 | }, 41 | "configurations": { 42 | "production": { 43 | "optimization": true, 44 | "outputHashing": "all", 45 | "sourceMap": false, 46 | "extractCss": true, 47 | "namedChunks": false, 48 | "aot": true, 49 | "extractLicenses": true, 50 | "vendorChunk": false, 51 | "buildOptimizer": true, 52 | "fileReplacements": [ 53 | { 54 | "replace": "src/environments/environment.ts", 55 | "with": "src/environments/environment.prod.ts" 56 | } 57 | ] 58 | } 59 | } 60 | }, 61 | "serve": { 62 | "builder": "@angular-devkit/build-angular:dev-server", 63 | "options": { 64 | "browserTarget": "spring-boot-angular-maven-starter:build" 65 | }, 66 | "configurations": { 67 | "production": { 68 | "browserTarget": "spring-boot-angular-maven-starter:build:production" 69 | } 70 | } 71 | }, 72 | "extract-i18n": { 73 | "builder": "@angular-devkit/build-angular:extract-i18n", 74 | "options": { 75 | "browserTarget": "spring-boot-angular-maven-starter:build" 76 | } 77 | }, 78 | "test": { 79 | "builder": "@angular-devkit/build-angular:karma", 80 | "options": { 81 | "main": "src/test.ts", 82 | "karmaConfig": "./karma.conf.js", 83 | "polyfills": "src/polyfills.ts", 84 | "tsConfig": "src/tsconfig.spec.json", 85 | "scripts": [], 86 | "styles": [ 87 | "src/assets/global.css", 88 | "src/assets/reset.css" 89 | ], 90 | "assets": [ 91 | { 92 | "glob": "favicon.ico", 93 | "input": "src/assets", 94 | "output": "/" 95 | } 96 | ] 97 | } 98 | }, 99 | "lint": { 100 | "builder": "@angular-devkit/build-angular:tslint", 101 | "options": { 102 | "tsConfig": [ 103 | "src/tsconfig.app.json", 104 | "src/tsconfig.spec.json" 105 | ], 106 | "exclude": [ 107 | "**/node_modules/**" 108 | ] 109 | } 110 | } 111 | } 112 | }, 113 | "spring-boot-angular-maven-starter-e2e": { 114 | "root": "", 115 | "sourceRoot": "", 116 | "projectType": "application", 117 | "architect": { 118 | "e2e": { 119 | "builder": "@angular-devkit/build-angular:protractor", 120 | "options": { 121 | "protractorConfig": "./protractor.conf.js", 122 | "devServerTarget": "spring-boot-angular-maven-starter:serve" 123 | } 124 | }, 125 | "lint": { 126 | "builder": "@angular-devkit/build-angular:tslint", 127 | "options": { 128 | "tsConfig": [ 129 | "e2e/tsconfig.e2e.json" 130 | ], 131 | "exclude": [ 132 | "**/node_modules/**" 133 | ] 134 | } 135 | } 136 | } 137 | } 138 | }, 139 | "defaultProject": "spring-boot-angular-maven-starter" 140 | } 141 | -------------------------------------------------------------------------------- /client/src/main/ng/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('spring-boot-angular-maven-starter App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /client/src/main/ng/e2e/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/src/main/ng/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/src/main/ng/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'), reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /client/src/main/ng/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spring-boot-angular-maven-starter", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve --proxy-config proxy.conf.json --port 9002", 8 | "prebuild": "rimraf dist", 9 | "build": "ng build --prod", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^6.0.7", 17 | "@angular/common": "^6.0.7", 18 | "@angular/compiler": "^6.0.7", 19 | "@angular/core": "^6.0.7", 20 | "@angular/forms": "^6.0.7", 21 | "@angular/http": "^6.0.7", 22 | "@angular/platform-browser": "^6.0.7", 23 | "@angular/platform-browser-dynamic": "^6.0.7", 24 | "@angular/router": "^6.0.7", 25 | "core-js": "^2.5.7", 26 | "rxjs": "^6.2.1", 27 | "zone.js": "^0.8.26" 28 | }, 29 | "devDependencies": { 30 | "@angular/compiler-cli": "^6.0.7", 31 | "@angular-devkit/build-angular": "^0.6.8", 32 | "@angular/cli": "^6.1.0-rc.0", 33 | "@angular/language-service": "^6.0.7", 34 | "@types/jasmine": "^2.8.8", 35 | "@types/jasminewd2": "^2.0.3", 36 | "@types/node": "^10.3.3", 37 | "codelyzer": "^4.3.0", 38 | "jasmine-core": "^3.1.0", 39 | "jasmine-spec-reporter": "~4.2.1", 40 | "karma": "^2.0.2", 41 | "karma-chrome-launcher": "~2.2.0", 42 | "karma-coverage-istanbul-reporter": "^2.0.1", 43 | "karma-jasmine": "~1.1.1", 44 | "karma-jasmine-html-reporter": "^1.1.0", 45 | "protractor": "~5.3.0", 46 | "ts-node": "^7.0.0", 47 | "tslint": "^5.10.0", 48 | "typescript": "~2.7.2" 49 | } 50 | } -------------------------------------------------------------------------------- /client/src/main/ng/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /client/src/main/ng/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/**": { 3 | "target": "http://localhost:9001", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import {RouterModule, Routes} from '@angular/router'; 2 | import {NgModule} from '@angular/core'; 3 | import {PageNotFoundComponent} from './feature/page-not-found/page-not-found.component'; 4 | import {LoginComponent} from './feature/login/login.component'; 5 | 6 | const routes: Routes = [ 7 | {path: 'login', component: LoginComponent}, 8 | {path: '**', component: PageNotFoundComponent}, 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [ 13 | RouterModule.forRoot(routes, {useHash: true}) 14 | ], 15 | exports: [ 16 | RouterModule 17 | ] 18 | }) 19 | export class AppRoutingModule { } 20 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import '../assets/reset.scss'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | template: '' 7 | }) 8 | export class AppComponent { 9 | } 10 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {AppRoutingModule} from './app-routing.module'; 4 | import {AppComponent} from './app.component'; 5 | import {CoreModule} from './core/core.module'; 6 | import {LayoutModule} from './feature/layout/layout.module'; 7 | import {PageNotFoundComponent} from './feature/page-not-found/page-not-found.component'; 8 | import {LoginModule} from './feature/login/login.module'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AppComponent, 13 | PageNotFoundComponent 14 | ], 15 | imports: [ 16 | BrowserModule, 17 | CoreModule, 18 | LayoutModule, 19 | LoginModule, 20 | AppRoutingModule 21 | ], 22 | bootstrap: [ 23 | AppComponent 24 | ] 25 | }) 26 | 27 | export class AppModule { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; 3 | import {AuthInterceptor} from './interceptor/auth.interceptor'; 4 | import {XhrInterceptor} from './interceptor/xhr.interceptor'; 5 | 6 | const httpInterceptorProviders = [ 7 | {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}, 8 | {provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true}, 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [ 13 | HttpClientModule 14 | ], 15 | providers: [ 16 | httpInterceptorProviders 17 | ] 18 | }) 19 | export class CoreModule { 20 | } 21 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/core/guard/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router'; 2 | import {Injectable} from '@angular/core'; 3 | import {Observable} from 'rxjs/internal/Observable'; 4 | import {AuthService} from '../service/auth.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthGuard implements CanActivate { 10 | 11 | constructor( 12 | private authService: AuthService, 13 | private router: Router 14 | ) {} 15 | 16 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean { 17 | if (!this.authService.authenticated) { 18 | this.router.navigate(['/login']); 19 | } 20 | return true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/core/interceptor/auth.interceptor.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; 3 | import {Observable} from 'rxjs/internal/Observable'; 4 | import {catchError} from 'rxjs/operators'; 5 | import {Router} from '@angular/router'; 6 | import {throwError} from 'rxjs/internal/observable/throwError'; 7 | import {EMPTY} from 'rxjs/internal/observable/empty'; 8 | 9 | @Injectable() 10 | export class AuthInterceptor implements HttpInterceptor { 11 | 12 | constructor(private router: Router) { 13 | } 14 | 15 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 16 | return next.handle(req).pipe( 17 | catchError(err => this.handleError(err)) 18 | ); 19 | } 20 | 21 | private handleError(err: any): Observable { 22 | if (err instanceof HttpErrorResponse) { 23 | const resp = err as HttpErrorResponse; 24 | if (resp.status === 401) { 25 | return this.handleUnauthorizedError(); 26 | } 27 | } 28 | return throwError(err); 29 | } 30 | 31 | private handleUnauthorizedError(): Observable { 32 | this.router.navigate(['/login']); 33 | return EMPTY; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/core/interceptor/xhr.interceptor.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; 3 | 4 | @Injectable() 5 | export class XhrInterceptor implements HttpInterceptor { 6 | 7 | intercept(req: HttpRequest, next: HttpHandler) { 8 | const xhr = req.clone({ 9 | headers: req.headers.set('X-Requested-With', 'XMLHttpRequest') 10 | }); 11 | return next.handle(xhr); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/core/service/abstract-service.ts: -------------------------------------------------------------------------------- 1 | import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; 2 | import {Observable} from 'rxjs/internal/Observable'; 3 | 4 | export abstract class AbstractService { 5 | 6 | protected constructor(private http: HttpClient) {} 7 | 8 | protected post(url: string, body: T, params?: HttpParams): Observable { 9 | return this.http.post(url, body, {params: params}); 10 | } 11 | 12 | protected textWithHeaders(url: string, headers: HttpHeaders): Observable { 13 | return this.http.get(url, {headers: headers, responseType: 'text'}); 14 | } 15 | 16 | protected text(url: string) { 17 | return this.http.get(url, {responseType: 'text'}); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/core/service/auth.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {AbstractService} from './abstract-service'; 3 | import {HttpClient, HttpHeaders} from '@angular/common/http'; 4 | import {Credentials} from '../../model/credentials.model'; 5 | import {Observable} from 'rxjs/internal/Observable'; 6 | import {ApiConst} from '../../util/api.const'; 7 | import {map, tap} from 'rxjs/operators'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class AuthService extends AbstractService { 13 | 14 | authenticated = false; 15 | username: string; 16 | 17 | constructor(http: HttpClient) { 18 | super(http); 19 | } 20 | 21 | private static createToken(credentials: Credentials) { 22 | return { 23 | authorization: 'Basic ' + btoa(credentials.username + ':' + credentials.password) 24 | }; 25 | } 26 | 27 | // Currently the user is not kept neither in Local Storage nor in Cookies. 28 | // Thereby authorization information is erased after every page update. 29 | authenticate(credentials: Credentials): Observable { 30 | const headers = new HttpHeaders(credentials ? AuthService.createToken(credentials) : {}); 31 | return this.textWithHeaders(ApiConst.USER, headers).pipe( 32 | tap(response => this.username = response), 33 | map(response => !!response), 34 | tap(isAuthenticated => this.authenticated = isAuthenticated) 35 | ); 36 | } 37 | 38 | logout(): Observable { 39 | return this.post(ApiConst.LOGOUT, null).pipe( 40 | tap(_ => { 41 | this.username = null; 42 | this.authenticated = false; 43 | }) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/core/service/echo.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpClient} from '@angular/common/http'; 3 | import {Observable} from 'rxjs'; 4 | import {AbstractService} from './abstract-service'; 5 | import {ApiConst} from '../../util/api.const'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class EchoService extends AbstractService { 11 | 12 | constructor(http: HttpClient) { 13 | super(http); 14 | } 15 | 16 | echo(): Observable { 17 | return this.text(ApiConst.ECHO); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/layout/home/home.component.html: -------------------------------------------------------------------------------- 1 |
Hi, {{ username }}
2 |
Echo from server: {{echo}}
3 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/layout/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {EchoService} from '../../../core/service/echo.service'; 3 | import {AuthService} from '../../../core/service/auth.service'; 4 | 5 | @Component({ 6 | selector: 'app-home', 7 | templateUrl: './home.component.html' 8 | }) 9 | export class HomeComponent implements OnInit { 10 | echo = 'nope'; 11 | username = ''; 12 | 13 | constructor( 14 | private echoService: EchoService, 15 | private authService: AuthService 16 | ) { 17 | } 18 | 19 | ngOnInit(): void { 20 | this.username = this.authService.username; 21 | this.echoService.echo().subscribe(result => { 22 | this.echo = result; 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/layout/layout.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/layout/layout.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {AuthService} from '../../core/service/auth.service'; 3 | import {Router} from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-layout', 7 | templateUrl: './layout.component.html' 8 | }) 9 | export class LayoutComponent { 10 | 11 | constructor( 12 | private router: Router, 13 | private authService: AuthService 14 | ) {} 15 | 16 | logout() { 17 | this.authService.logout().subscribe(_ => { 18 | this.router.navigate(['/']); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {SharedModule} from '../../shared/shared.module'; 3 | import {LayoutComponent} from './layout.component'; 4 | import {LayoutRouteModule} from './layout.route-module'; 5 | import {HomeComponent} from './home/home.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | SharedModule, 10 | LayoutRouteModule, 11 | ], 12 | declarations: [ 13 | HomeComponent, 14 | LayoutComponent 15 | ], 16 | exports: [ 17 | LayoutComponent 18 | ] 19 | }) 20 | export class LayoutModule { 21 | } 22 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/layout/layout.route-module.ts: -------------------------------------------------------------------------------- 1 | import {RouterModule, Routes} from '@angular/router'; 2 | import {HomeComponent} from './home/home.component'; 3 | import {NgModule} from '@angular/core'; 4 | import {AuthGuard} from '../../core/guard/auth.guard'; 5 | import {LayoutComponent} from './layout.component'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', component: LayoutComponent, 10 | children: [ 11 | { path: '', redirectTo: '/home', pathMatch: 'full', canActivate: [AuthGuard] }, 12 | { path: 'home', component: HomeComponent, canActivate: [AuthGuard] }, 13 | { path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule', canActivate: [AuthGuard] } 14 | ] 15 | } 16 | ]; 17 | 18 | @NgModule({ 19 | imports: [ 20 | RouterModule.forChild(routes) 21 | ], 22 | exports: [ 23 | RouterModule 24 | ] 25 | }) 26 | export class LayoutRouteModule { 27 | } 28 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/layout/lazy/lazy.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-lazy', 5 | template: `
I'm Lazy
` 6 | }) 7 | export class LazyComponent { 8 | } 9 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/layout/lazy/lazy.module.ts: -------------------------------------------------------------------------------- 1 | import {RouterModule, Routes} from '@angular/router'; 2 | import {NgModule} from '@angular/core'; 3 | import {LazyComponent} from './lazy.component'; 4 | import {SharedModule} from '../../../shared/shared.module'; 5 | 6 | const routes: Routes = [ 7 | {path: '', component: LazyComponent}, 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [SharedModule, RouterModule.forChild(routes)], 12 | exports: [LazyComponent], 13 | declarations: [LazyComponent] 14 | }) 15 | export class LazyModule {} 16 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/login/login.component.scss: -------------------------------------------------------------------------------- 1 | $element-height: 34px; 2 | $element-margin-bottom: 16px; 3 | $element-width: 400px; 4 | 5 | :host { 6 | left: 50%; 7 | margin: (-($element-height*3 + $element-margin-bottom*2)/2) 0 0 (-$element-width/2); 8 | position: absolute; 9 | top: 50%; 10 | width: $element-width; 11 | } 12 | 13 | button { 14 | background-color: #636262; 15 | border: none; 16 | color: #fff; 17 | cursor: pointer; 18 | font-size: 14px; 19 | height: $element-height; 20 | line-height: $element-height; 21 | width: 100%; 22 | } 23 | 24 | button:disabled { 25 | background-color: #bdbbbb; 26 | cursor: default; 27 | } 28 | 29 | input { 30 | box-sizing: border-box; 31 | display: inline-block; 32 | font-size: 14px; 33 | height: $element-height; 34 | line-height: $element-height; 35 | margin-bottom: $element-margin-bottom; 36 | padding: 0 10px; 37 | width: 100%; 38 | } 39 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {AuthService} from '../../core/service/auth.service'; 3 | import {FormBuilder, FormGroup} from '@angular/forms'; 4 | import {debounceTime} from 'rxjs/operators'; 5 | import {Router} from '@angular/router'; 6 | 7 | @Component({ 8 | selector: 'app-login', 9 | templateUrl: './login.component.html', 10 | styleUrls: ['./login.component.scss'] 11 | }) 12 | export class LoginComponent { 13 | 14 | form: FormGroup; 15 | 16 | constructor( 17 | formBuilder: FormBuilder, 18 | private router: Router, 19 | private authService: AuthService 20 | ) { 21 | this.form = formBuilder.group({ 22 | username: null, 23 | password: null 24 | }); 25 | } 26 | 27 | login() { 28 | this.authService.authenticate(this.form.value).pipe(debounceTime(400)).subscribe(result => { 29 | if (result) { 30 | this.router.navigate(['/']); 31 | } else { 32 | this.form.reset(); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/login/login.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {SharedModule} from '../../shared/shared.module'; 3 | import {LoginComponent} from './login.component'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | SharedModule 8 | ], 9 | declarations: [ 10 | LoginComponent 11 | ], 12 | exports: [ 13 | LoginComponent 14 | ] 15 | }) 16 | export class LoginModule { 17 | } 18 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/page-not-found/page-not-found.component.html: -------------------------------------------------------------------------------- 1 | Page not found 404 -------------------------------------------------------------------------------- /client/src/main/ng/src/app/feature/page-not-found/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-page-not-found', 5 | templateUrl: './page-not-found.component.html' 6 | }) 7 | 8 | export class PageNotFoundComponent { 9 | } 10 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/model/credentials.model.ts: -------------------------------------------------------------------------------- 1 | export interface Credentials { 2 | username: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import {CommonModule} from '@angular/common'; 2 | import {NgModule} from '@angular/core'; 3 | import {ReactiveFormsModule} from '@angular/forms'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule, 8 | ReactiveFormsModule 9 | ], 10 | declarations: [ 11 | ], 12 | exports: [ 13 | CommonModule, 14 | ReactiveFormsModule 15 | ] 16 | }) 17 | export class SharedModule { 18 | } 19 | -------------------------------------------------------------------------------- /client/src/main/ng/src/app/util/api.const.ts: -------------------------------------------------------------------------------- 1 | export class ApiConst { 2 | static readonly ECHO = '/api/echo'; 3 | static readonly LOGOUT = '/api/logout'; 4 | static readonly USER = '/api/user'; 5 | } 6 | -------------------------------------------------------------------------------- /client/src/main/ng/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiper2d/spring-boot-angular-maven-starter/38901aacbaa52c578bfb86213c1a55cd090da8b6/client/src/main/ng/src/assets/favicon.ico -------------------------------------------------------------------------------- /client/src/main/ng/src/assets/global.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiper2d/spring-boot-angular-maven-starter/38901aacbaa52c578bfb86213c1a55cd090da8b6/client/src/main/ng/src/assets/global.scss -------------------------------------------------------------------------------- /client/src/main/ng/src/assets/reset.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, 12 | main, menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | vertical-align: baseline; 18 | } 19 | /* HTML5 display-role reset for older browsers */ 20 | article, aside, details, figcaption, figure, 21 | footer, header, main, menu, nav, section { 22 | display: block; 23 | } 24 | body { 25 | line-height: 1; 26 | } 27 | ol, ul { 28 | list-style: none; 29 | } 30 | blockquote, q { 31 | quotes: none; 32 | } 33 | blockquote:before, blockquote:after, 34 | q:before, q:after { 35 | content: ''; 36 | content: none; 37 | } 38 | 39 | /* Don't kill focus outline for keyboard users: http://24ways.org/2009/dont-lose-your-focus */ 40 | a:hover, a:active { 41 | outline: none; 42 | } 43 | 44 | table { 45 | border-collapse: collapse; 46 | border-spacing: 0; 47 | } -------------------------------------------------------------------------------- /client/src/main/ng/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /client/src/main/ng/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /client/src/main/ng/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SpringBootAngularMavenStarter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/src/main/ng/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/main/ng/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 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by Angular itself. 59 | */ 60 | import 'zone.js/dist/zone'; // Included with Angular CLI. 61 | 62 | 63 | 64 | /*************************************************************************************************** 65 | * APPLICATION IMPORTS 66 | */ 67 | 68 | /** 69 | * Date, currency, decimal and percent pipes. 70 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 71 | */ 72 | // import 'intl'; // Run `npm install --save intl`. 73 | /** 74 | * Need to import at least one locale-data with intl. 75 | */ 76 | // import 'intl/locale-data/jsonp/en'; 77 | -------------------------------------------------------------------------------- /client/src/main/ng/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /client/src/main/ng/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /client/src/main/ng/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts", 15 | "polyfills.ts" 16 | ], 17 | "include": [ 18 | "**/*.spec.ts", 19 | "**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /client/src/main/ng/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /client/src/main/ng/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2017", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/src/main/ng/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 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true, 18 | "rxjs/Rx" 19 | ], 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "spaces" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | { 35 | "order": [ 36 | "static-field", 37 | "instance-field", 38 | "static-method", 39 | "instance-method" 40 | ] 41 | } 42 | ], 43 | "no-arg": true, 44 | "no-bitwise": true, 45 | "no-console": [ 46 | true, 47 | "debug", 48 | "info", 49 | "time", 50 | "timeEnd", 51 | "trace" 52 | ], 53 | "no-construct": true, 54 | "no-debugger": true, 55 | "no-duplicate-super": true, 56 | "no-empty": false, 57 | "no-empty-interface": true, 58 | "no-eval": true, 59 | "no-inferrable-types": [ 60 | true, 61 | "ignore-params" 62 | ], 63 | "no-misused-new": true, 64 | "no-non-null-assertion": true, 65 | "no-shadowed-variable": true, 66 | "no-string-literal": false, 67 | "no-string-throw": true, 68 | "no-switch-case-fall-through": true, 69 | "no-trailing-whitespace": true, 70 | "no-unnecessary-initializer": true, 71 | "no-unused-expression": true, 72 | "no-use-before-declare": true, 73 | "no-var-keyword": true, 74 | "object-literal-sort-keys": false, 75 | "one-line": [ 76 | true, 77 | "check-open-brace", 78 | "check-catch", 79 | "check-else", 80 | "check-whitespace" 81 | ], 82 | "prefer-const": true, 83 | "quotemark": [ 84 | true, 85 | "single" 86 | ], 87 | "radix": true, 88 | "semicolon": [ 89 | true, 90 | "always" 91 | ], 92 | "triple-equals": [ 93 | true, 94 | "allow-null-check" 95 | ], 96 | "typedef-whitespace": [ 97 | true, 98 | { 99 | "call-signature": "nospace", 100 | "index-signature": "nospace", 101 | "parameter": "nospace", 102 | "property-declaration": "nospace", 103 | "variable-declaration": "nospace" 104 | } 105 | ], 106 | "typeof-compare": true, 107 | "unified-signatures": true, 108 | "variable-name": false, 109 | "whitespace": [ 110 | true, 111 | "check-branch", 112 | "check-decl", 113 | "check-operator", 114 | "check-separator", 115 | "check-type" 116 | ], 117 | "directive-selector": [ 118 | true, 119 | "attribute", 120 | "app", 121 | "camelCase" 122 | ], 123 | "component-selector": [ 124 | true, 125 | "element", 126 | "app", 127 | "kebab-case" 128 | ], 129 | "use-input-property-decorator": true, 130 | "use-output-property-decorator": true, 131 | "use-host-property-decorator": true, 132 | "no-input-rename": true, 133 | "no-output-rename": true, 134 | "use-life-cycle-interface": true, 135 | "use-pipe-transform-interface": true, 136 | "component-class-suffix": true, 137 | "directive-class-suffix": true, 138 | "invoke-injectable": true 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.hiper2d 8 | spring-boot-angular-maven-starter 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | client 14 | server 15 | 16 | -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | server 8 | 1.0-SNAPSHOT 9 | war 10 | 11 | 12 | com.hiper2d 13 | spring-boot-angular-maven-starter 14 | 1.0-SNAPSHOT 15 | 16 | 17 | 18 | 2.9.4.1 19 | 1.2.50 20 | true 21 | 1.8 22 | 1.8 23 | 3.1.0 24 | 2.0.3.RELEASE 25 | com.hiper2d.Application 26 | 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-dependencies 33 | ${spring-boot-version} 34 | pom 35 | import 36 | 37 | 38 | 39 | 40 | 41 | 42 | spring-milestones 43 | Spring Milestones 44 | http://repo.spring.io/milestone 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-web 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-logging 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-security 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-starter-log4j2 68 | 69 | 70 | 71 | org.jetbrains.kotlin 72 | kotlin-stdlib-jdk8 73 | ${kotlin.version} 74 | 75 | 76 | 77 | org.jetbrains.kotlin 78 | kotlin-reflect 79 | ${kotlin.version} 80 | 81 | 82 | 83 | com.fasterxml.jackson.module 84 | jackson-module-kotlin 85 | ${jackson-module-kotlin.version} 86 | 87 | 88 | 89 | 90 | ${project.basedir}/src/main/kotlin 91 | 92 | 93 | 94 | kotlin-maven-plugin 95 | org.jetbrains.kotlin 96 | ${kotlin.version} 97 | 98 | 99 | spring 100 | 101 | 1.8 102 | 103 | 104 | 105 | compile 106 | compile 107 | 108 | compile 109 | 110 | 111 | 112 | test-compile 113 | test-compile 114 | 115 | test-compile 116 | 117 | 118 | 119 | 120 | 121 | org.jetbrains.kotlin 122 | kotlin-maven-allopen 123 | ${kotlin.version} 124 | 125 | 126 | 127 | 128 | 129 | maven-war-plugin 130 | ${maven.war.plugin.version} 131 | 132 | 133 | 134 | org.springframework.boot 135 | spring-boot-maven-plugin 136 | ${spring-boot-version} 137 | 138 | 139 | 140 | repackage 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | prod 151 | 152 | 153 | com.hiper2d 154 | client 155 | 1.0-SNAPSHOT 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /server/src/main/kotlin/com/hiper2d/Application.kt: -------------------------------------------------------------------------------- 1 | package com.hiper2d 2 | 3 | import org.springframework.boot.SpringApplication 4 | import org.springframework.boot.autoconfigure.SpringBootApplication 5 | import org.springframework.boot.builder.SpringApplicationBuilder 6 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer 7 | 8 | @SpringBootApplication 9 | class Application: SpringBootServletInitializer() 10 | 11 | fun main(args: Array) { 12 | SpringApplication.run(Application::class.java, *args) 13 | } -------------------------------------------------------------------------------- /server/src/main/kotlin/com/hiper2d/config/WebConfig.kt: -------------------------------------------------------------------------------- 1 | package com.hiper2d.config 2 | 3 | import org.springframework.context.annotation.Configuration 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer 6 | 7 | @Configuration 8 | class WebConfig: WebMvcConfigurer { 9 | 10 | // todo: Understand why it is ignored. Probably some headers should be added to a client 11 | override fun addCorsMappings(registry: CorsRegistry) { 12 | registry.addMapping("/api/**") 13 | .allowedOrigins("http://localhost:9002") 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /server/src/main/kotlin/com/hiper2d/config/WebSecurityConfig.kt: -------------------------------------------------------------------------------- 1 | package com.hiper2d.config 2 | 3 | import com.hiper2d.security.provider.AnyAuthenticationProvider 4 | import org.springframework.context.annotation.Configuration 5 | import org.springframework.http.HttpStatus 6 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity 8 | import org.springframework.security.config.annotation.web.builders.WebSecurity 9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 11 | import org.springframework.security.web.authentication.HttpStatusEntryPoint 12 | import org.springframework.security.web.csrf.CookieCsrfTokenRepository 13 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher 14 | 15 | @Configuration 16 | @EnableWebSecurity 17 | class WebSecurityConfig( 18 | private val anyAuthenticationProvider: AnyAuthenticationProvider 19 | ): WebSecurityConfigurerAdapter() { 20 | 21 | override fun configure(auth: AuthenticationManagerBuilder) { 22 | // A custom provider which allows to authenticate with any Latin username/password 23 | auth.authenticationProvider(anyAuthenticationProvider) 24 | } 25 | 26 | override fun configure(http: HttpSecurity) { 27 | http 28 | .httpBasic() 29 | .and() 30 | .authorizeRequests() 31 | // Necessary when running the application with compiled client as static resources 32 | .antMatchers("/").permitAll() 33 | .anyRequest().authenticated() 34 | .and() 35 | .logout() 36 | .logoutRequestMatcher(AntPathRequestMatcher("/api/logout", "POST")) 37 | .and() 38 | .csrf() 39 | // Allow a JavaScript client to read the XSRF-TOKEN from cookie 40 | .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 41 | .and() 42 | // Use MVC CORS configuration 43 | .cors() 44 | .and() 45 | .exceptionHandling() 46 | // An AuthenticationEntryPoint that sends a generic HttpStatus as a response 47 | .authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) 48 | } 49 | 50 | override fun configure(web: WebSecurity) { 51 | // Everything below is necessary when running the application with compiled client as static resources 52 | web.ignoring() 53 | .mvcMatchers("/favicon.ico") 54 | .mvcMatchers("/*.css") 55 | .mvcMatchers("/*.js") 56 | } 57 | } -------------------------------------------------------------------------------- /server/src/main/kotlin/com/hiper2d/controller/EchoController.kt: -------------------------------------------------------------------------------- 1 | package com.hiper2d.controller 2 | 3 | import org.springframework.web.bind.annotation.GetMapping 4 | import org.springframework.web.bind.annotation.RequestMapping 5 | import org.springframework.web.bind.annotation.RestController 6 | 7 | @RestController 8 | @RequestMapping("/api/echo") 9 | class EchoController { 10 | 11 | @GetMapping 12 | fun echo() = "hi" 13 | } -------------------------------------------------------------------------------- /server/src/main/kotlin/com/hiper2d/controller/UserController.kt: -------------------------------------------------------------------------------- 1 | package com.hiper2d.controller 2 | 3 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken 4 | import org.springframework.security.core.context.SecurityContextHolder 5 | import org.springframework.web.bind.annotation.GetMapping 6 | import org.springframework.web.bind.annotation.PostMapping 7 | import org.springframework.web.bind.annotation.RequestMapping 8 | import org.springframework.web.bind.annotation.RestController 9 | 10 | @RestController 11 | @RequestMapping("/api/user") 12 | class UserController { 13 | 14 | @GetMapping 15 | fun getUser(): String? { 16 | val auth = SecurityContextHolder.getContext().authentication 17 | return if (auth is UsernamePasswordAuthenticationToken) { 18 | auth.principal as String 19 | } else { 20 | null 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/src/main/kotlin/com/hiper2d/security/provider/AnyAuthenticationProvider.kt: -------------------------------------------------------------------------------- 1 | package com.hiper2d.security.provider 2 | 3 | import org.springframework.security.authentication.AuthenticationProvider 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken 5 | import org.springframework.security.core.Authentication 6 | import org.springframework.stereotype.Component 7 | 8 | @Component 9 | class AnyAuthenticationProvider: AuthenticationProvider { 10 | 11 | override fun authenticate(token: Authentication?): Authentication? { 12 | val name: String? = token?.name 13 | val pass: String? = token?.credentials.toString() 14 | return if (name != null && pass != null) { 15 | UsernamePasswordAuthenticationToken(name, pass, emptyList()) 16 | } else { 17 | null 18 | } 19 | } 20 | 21 | override fun supports(clazz: Class<*>?): Boolean { 22 | return true 23 | } 24 | } -------------------------------------------------------------------------------- /server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server.port: 9001 -------------------------------------------------------------------------------- /server/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | --------------------------------------------------------------------------------