├── .gitignore ├── Readme.md ├── angular-frontend ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.e2e.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── todo-list.component.html │ │ ├── todo-list.component.ts │ │ ├── todo.service.ts │ │ └── todo.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json └── spring-boot-backend ├── .gitignore ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── example │ │ └── todoapp │ │ ├── TodoappApplication.java │ │ ├── controllers │ │ └── TodoController.java │ │ ├── models │ │ └── Todo.java │ │ └── repositories │ │ └── TodoRepository.java └── resources │ └── application.properties └── test └── java └── com └── example └── todoapp └── TodoappApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | .idea 3 | *.iml 4 | target 5 | .DS_Store 6 | .bowerrc 7 | *.iml 8 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Spring Boot, MongoDB, Angular Restful API Tutorial 2 | 3 | Build a Fully-Fledged Todo App with Spring Boot & MongoDB in the Backend and Angular in the frontend. 4 | 5 | ## Requirements 6 | 7 | 1. Java - 1.8.x 8 | 9 | 2. Maven - 3.x.x 10 | 11 | 3. MongoDB - 3.x.x 12 | 13 | ## Steps to Setup 14 | 15 | **1. Clone the application** 16 | 17 | ```bash 18 | git clone https://github.com/callicoder/spring-boot-mongodb-angular-todo-app.git 19 | ``` 20 | 21 | **2. Build and run the backend app using maven** 22 | 23 | ```bash 24 | cd spring-boot-backend 25 | mvn package 26 | java -jar target/todoapp-1.0.0.jar 27 | ``` 28 | 29 | Alternatively, you can run the app without packaging it using - 30 | 31 | ```bash 32 | mvn spring-boot:run 33 | ``` 34 | 35 | The backend server will start at . 36 | 37 | **3. Run the frontend app using npm** 38 | 39 | ```bash 40 | cd angular-frontend 41 | npm install 42 | ``` 43 | 44 | ```bash 45 | npm start 46 | ``` 47 | 48 | Frontend server will run on 49 | 50 | ## Learn more 51 | 52 | You can find the tutorial for this application on my blog - 53 | 54 | 55 | -------------------------------------------------------------------------------- /angular-frontend/.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 | -------------------------------------------------------------------------------- /angular-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /angular-frontend/README.md: -------------------------------------------------------------------------------- 1 | # AngularFrontend 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.1.3. 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|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | Before running the tests make sure you are serving the app via `ng serve`. 25 | 26 | ## Further help 27 | 28 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 29 | -------------------------------------------------------------------------------- /angular-frontend/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "my-app": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:application": { 10 | "strict": true 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/my-app", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets" 28 | ], 29 | "styles": [ 30 | "src/styles.css" 31 | ], 32 | "scripts": [] 33 | }, 34 | "configurations": { 35 | "production": { 36 | "budgets": [ 37 | { 38 | "type": "initial", 39 | "maximumWarning": "500kb", 40 | "maximumError": "1mb" 41 | }, 42 | { 43 | "type": "anyComponentStyle", 44 | "maximumWarning": "2kb", 45 | "maximumError": "4kb" 46 | } 47 | ], 48 | "fileReplacements": [ 49 | { 50 | "replace": "src/environments/environment.ts", 51 | "with": "src/environments/environment.prod.ts" 52 | } 53 | ], 54 | "outputHashing": "all" 55 | }, 56 | "development": { 57 | "buildOptimizer": false, 58 | "optimization": false, 59 | "vendorChunk": true, 60 | "extractLicenses": false, 61 | "sourceMap": true, 62 | "namedChunks": true 63 | } 64 | }, 65 | "defaultConfiguration": "production" 66 | }, 67 | "serve": { 68 | "builder": "@angular-devkit/build-angular:dev-server", 69 | "configurations": { 70 | "production": { 71 | "browserTarget": "my-app:build:production" 72 | }, 73 | "development": { 74 | "browserTarget": "my-app:build:development" 75 | } 76 | }, 77 | "defaultConfiguration": "development" 78 | }, 79 | "extract-i18n": { 80 | "builder": "@angular-devkit/build-angular:extract-i18n", 81 | "options": { 82 | "browserTarget": "my-app:build" 83 | } 84 | }, 85 | "test": { 86 | "builder": "@angular-devkit/build-angular:karma", 87 | "options": { 88 | "main": "src/test.ts", 89 | "polyfills": "src/polyfills.ts", 90 | "tsConfig": "tsconfig.spec.json", 91 | "karmaConfig": "karma.conf.js", 92 | "assets": [ 93 | "src/favicon.ico", 94 | "src/assets" 95 | ], 96 | "styles": [ 97 | "src/styles.css" 98 | ], 99 | "scripts": [] 100 | } 101 | } 102 | } 103 | } 104 | }, 105 | "defaultProject": "my-app" 106 | } 107 | -------------------------------------------------------------------------------- /angular-frontend/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AngularFrontendPage } from './app.po'; 2 | 3 | describe('angular-frontend App', () => { 4 | let page: AngularFrontendPage; 5 | 6 | beforeEach(() => { 7 | page = new AngularFrontendPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /angular-frontend/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AngularFrontendPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /angular-frontend/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /angular-frontend/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'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/my-app'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /angular-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-frontend", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build", 9 | "watch": "ng build --watch --configuration development", 10 | "test": "ng test" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~12.2.0", 15 | "@angular/common": "~12.2.0", 16 | "@angular/compiler": "~12.2.0", 17 | "@angular/core": "~12.2.0", 18 | "@angular/forms": "~12.2.0", 19 | "@angular/platform-browser": "~12.2.0", 20 | "@angular/platform-browser-dynamic": "~12.2.0", 21 | "@angular/router": "~12.2.0", 22 | "rxjs": "~6.6.0", 23 | "tslib": "^2.3.0", 24 | "zone.js": "~0.11.4" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "~12.2.9", 28 | "@angular/cli": "~12.2.9", 29 | "@angular/compiler-cli": "~12.2.0", 30 | "@types/jasmine": "~3.8.0", 31 | "@types/node": "^12.11.1", 32 | "jasmine-core": "~3.8.0", 33 | "karma": "~6.3.0", 34 | "karma-chrome-launcher": "~3.1.0", 35 | "karma-coverage": "~2.0.3", 36 | "karma-jasmine": "~4.0.0", 37 | "karma-jasmine-html-reporter": "~1.7.0", 38 | "typescript": "~4.3.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /angular-frontend/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callicoder/spring-boot-mongodb-angular-todo-app/f90977ebd4581dddc9cc3d088bdaa3d2d331f37e/angular-frontend/src/app/app.component.css -------------------------------------------------------------------------------- /angular-frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /angular-frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | }); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'my-app'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('my-app'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement as HTMLElement; 33 | expect(compiled.querySelector('.content span')?.textContent).toContain('my-app app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /angular-frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'app'; 10 | } 11 | -------------------------------------------------------------------------------- /angular-frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { FormsModule } from '@angular/forms'; 5 | import { HttpClientModule } from '@angular/common/http'; 6 | 7 | import { AppComponent } from './app.component'; 8 | 9 | import { TodoListComponent } from './todo-list.component'; 10 | import { TodoService } from './todo.service'; 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent, 14 | TodoListComponent 15 | ], 16 | imports: [ 17 | BrowserModule, 18 | FormsModule, 19 | HttpClientModule 20 | ], 21 | providers: [TodoService], 22 | bootstrap: [AppComponent] 23 | }) 24 | export class AppModule { } -------------------------------------------------------------------------------- /angular-frontend/src/app/todo-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |

My Todos

3 |
4 |
5 | 9 |
11 |
12 | Title is required. 13 |
14 |
15 |
16 |
17 | 49 |
50 |

No Todos Found!

51 |
52 |
-------------------------------------------------------------------------------- /angular-frontend/src/app/todo-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { TodoService } from './todo.service'; 3 | import { Todo } from './todo'; 4 | import {NgForm} from '@angular/forms'; 5 | 6 | @Component({ 7 | selector: 'todo-list', 8 | templateUrl: './todo-list.component.html' 9 | }) 10 | 11 | export class TodoListComponent implements OnInit { 12 | todos: Todo[] = []; 13 | newTodo: Todo = new Todo(); 14 | editing: boolean = false; 15 | editingTodo: Todo = new Todo(); 16 | 17 | constructor( 18 | private todoService: TodoService, 19 | ) {} 20 | 21 | ngOnInit(): void { 22 | this.getTodos(); 23 | } 24 | 25 | getTodos(): void { 26 | this.todoService.getTodos() 27 | .subscribe(todos => this.todos = todos ); 28 | } 29 | 30 | createTodo(todoForm: NgForm): void { 31 | this.todoService.createTodo(this.newTodo) 32 | .subscribe(createTodo => { 33 | todoForm.reset(); 34 | this.newTodo = new Todo(); 35 | this.todos.unshift(createTodo) 36 | }); 37 | } 38 | 39 | deleteTodo(id: string): void { 40 | this.todoService.deleteTodo(id) 41 | .subscribe(() => { 42 | this.todos = this.todos.filter(todo => todo.id != id); 43 | }); 44 | } 45 | 46 | updateTodo(todoData: Todo): void { 47 | console.log(todoData); 48 | this.todoService.updateTodo(todoData) 49 | .subscribe(updatedTodo => { 50 | let existingTodo = this.todos.find(todo => todo.id === updatedTodo.id); 51 | Object.assign(existingTodo, updatedTodo); 52 | this.clearEditing(); 53 | }); 54 | } 55 | 56 | toggleCompleted(todoData: Todo): void { 57 | todoData.completed = !todoData.completed; 58 | this.todoService.updateTodo(todoData) 59 | .subscribe(updatedTodo => { 60 | let existingTodo = this.todos.find(todo => todo.id === updatedTodo.id); 61 | Object.assign(existingTodo, updatedTodo); 62 | }); 63 | } 64 | 65 | editTodo(todoData: Todo): void { 66 | this.editing = true; 67 | Object.assign(this.editingTodo, todoData); 68 | } 69 | 70 | clearEditing(): void { 71 | this.editingTodo = new Todo(); 72 | this.editing = false; 73 | } 74 | } -------------------------------------------------------------------------------- /angular-frontend/src/app/todo.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Todo } from './todo'; 3 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 4 | import { Observable, throwError } from 'rxjs'; 5 | import { catchError } from 'rxjs/operators'; 6 | 7 | @Injectable() 8 | export class TodoService { 9 | private baseUrl = 'http://localhost:8080'; 10 | 11 | constructor(private http: HttpClient) { } 12 | 13 | getTodos(): Observable { 14 | return this.http.get(this.baseUrl + '/api/todos/') 15 | .pipe( 16 | catchError(this.handleError) 17 | ); 18 | } 19 | 20 | createTodo(todoData: Todo): Observable { 21 | return this.http.post(this.baseUrl + '/api/todos/', todoData) 22 | .pipe( 23 | catchError(this.handleError) 24 | ); 25 | } 26 | 27 | updateTodo(todoData: Todo): Observable { 28 | return this.http.put(this.baseUrl + '/api/todos/' + todoData.id, todoData) 29 | .pipe( 30 | catchError(this.handleError) 31 | ); 32 | } 33 | 34 | deleteTodo(id: string): Observable { 35 | return this.http.delete(this.baseUrl + '/api/todos/' + id) 36 | .pipe( 37 | catchError(this.handleError) 38 | ); 39 | } 40 | 41 | private handleError(error: HttpErrorResponse) { 42 | if (error.status === 0) { 43 | // A client-side or network error occurred. Handle it accordingly. 44 | console.error('An error occurred:', error.error); 45 | } else { 46 | // The backend returned an unsuccessful response code. 47 | // The response body may contain clues as to what went wrong. 48 | console.error( 49 | `Backend returned code ${error.status}, body was: `, error.error); 50 | } 51 | // Return an observable with a user-facing error message. 52 | return throwError( 53 | 'Something bad happened; please try again later.'); 54 | } 55 | } -------------------------------------------------------------------------------- /angular-frontend/src/app/todo.ts: -------------------------------------------------------------------------------- 1 | export class Todo { 2 | id: string = ''; 3 | title: string = ''; 4 | completed: boolean = false; 5 | createdAt: Date = new Date(); 6 | } 7 | -------------------------------------------------------------------------------- /angular-frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callicoder/spring-boot-mongodb-angular-todo-app/f90977ebd4581dddc9cc3d088bdaa3d2d331f37e/angular-frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /angular-frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /angular-frontend/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 | -------------------------------------------------------------------------------- /angular-frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callicoder/spring-boot-mongodb-angular-todo-app/f90977ebd4581dddc9cc3d088bdaa3d2d331f37e/angular-frontend/src/favicon.ico -------------------------------------------------------------------------------- /angular-frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MyApp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /angular-frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); -------------------------------------------------------------------------------- /angular-frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | 63 | /*************************************************************************************************** 64 | * APPLICATION IMPORTS 65 | */ 66 | -------------------------------------------------------------------------------- /angular-frontend/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | body { 4 | font-size: 18px; 5 | line-height: 1.58; 6 | background: #d53369; 7 | background: -webkit-linear-gradient(to left, #cbad6d, #d53369); 8 | background: linear-gradient(to left, #cbad6d, #d53369); 9 | color: #333; 10 | } 11 | 12 | h1 { 13 | font-size: 36px; 14 | } 15 | 16 | * { 17 | box-sizing: border-box; 18 | } 19 | 20 | i { 21 | vertical-align: middle; 22 | color: #626262; 23 | } 24 | 25 | input { 26 | border: 1px solid #E8E8E8; 27 | } 28 | 29 | .page-title { 30 | text-align: center; 31 | } 32 | 33 | .todo-content { 34 | max-width: 650px; 35 | width: 100%; 36 | margin: 0 auto; 37 | margin-top: 60px; 38 | background-color: #fff; 39 | padding: 15px; 40 | box-shadow: 0 0 4px rgba(0,0,0,.14), 0 4px 8px rgba(0,0,0,.28); 41 | } 42 | 43 | .form-control { 44 | font-size: 16px; 45 | padding-left: 15px; 46 | outline: none; 47 | border: 1px solid #E8E8E8; 48 | } 49 | 50 | .form-control:focus { 51 | border: 1px solid #626262; 52 | } 53 | 54 | .todo-content .form-control { 55 | width: 100%; 56 | height: 50px; 57 | } 58 | 59 | .todo-content .todo-create { 60 | padding-bottom: 30px; 61 | border-bottom: 1px solid #e8e8e8; 62 | } 63 | 64 | .todo-content .alert-danger { 65 | padding-left: 15px; 66 | font-size: 14px; 67 | color: red; 68 | padding-top: 5px; 69 | } 70 | 71 | .todo-content ul { 72 | list-style: none; 73 | margin: 0; 74 | padding: 0; 75 | max-height: 450px; 76 | padding-left: 15px; 77 | padding-right: 15px; 78 | margin-left: -15px; 79 | margin-right: -15px; 80 | overflow-y: scroll; 81 | } 82 | 83 | .todo-content ul li { 84 | padding-top: 10px; 85 | padding-bottom: 10px; 86 | border-bottom: 1px solid #E8E8E8; 87 | } 88 | 89 | .todo-content ul li span { 90 | display: inline-block; 91 | vertical-align: middle; 92 | } 93 | 94 | .todo-content .todo-title { 95 | width: calc(100% - 160px); 96 | margin-left: 10px; 97 | overflow: hidden; 98 | text-overflow: ellipsis; 99 | } 100 | 101 | .todo-content .todo-completed { 102 | display: inline-block; 103 | text-align: center; 104 | width:35px; 105 | height:35px; 106 | cursor: pointer; 107 | } 108 | 109 | .todo-content .todo-completed i { 110 | font-size: 20px; 111 | } 112 | 113 | .todo-content .todo-actions, .todo-content .edit-actions { 114 | float: right; 115 | } 116 | 117 | .todo-content .todo-actions i, .todo-content .edit-actions i { 118 | font-size: 17px; 119 | } 120 | 121 | .todo-content .todo-actions a, .todo-content .edit-actions a { 122 | display: inline-block; 123 | text-align: center; 124 | width: 35px; 125 | height: 35px; 126 | cursor: pointer; 127 | } 128 | 129 | .todo-content .todo-actions a:hover, .todo-content .edit-actions a:hover { 130 | background-color: #f4f4f4; 131 | } 132 | 133 | .todo-content .todo-edit input { 134 | width: calc(100% - 80px); 135 | height: 35px; 136 | } 137 | .todo-content .edit-actions { 138 | text-align: right; 139 | } 140 | 141 | .no-todos { 142 | text-align: center; 143 | } 144 | 145 | .toggle-completed-checkbox:before { 146 | content: 'check_box_outline_blank'; 147 | } 148 | 149 | li.completed .toggle-completed-checkbox:before { 150 | content: 'check_box'; 151 | } 152 | 153 | li.completed .todo-title { 154 | text-decoration: line-through; 155 | color: #757575; 156 | } 157 | 158 | li.completed i { 159 | color: #757575; 160 | } -------------------------------------------------------------------------------- /angular-frontend/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/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | { teardown: { destroyAfterEach: true }}, 22 | ); 23 | 24 | // Then we find all the tests. 25 | const context = require.context('./', true, /\.spec\.ts$/); 26 | // And load the modules. 27 | context.keys().map(context); 28 | -------------------------------------------------------------------------------- /angular-frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /angular-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "sourceMap": true, 12 | "declaration": false, 13 | "downlevelIteration": true, 14 | "experimentalDecorators": true, 15 | "moduleResolution": "node", 16 | "importHelpers": true, 17 | "target": "es2017", 18 | "module": "es2020", 19 | "lib": [ 20 | "es2018", 21 | "dom" 22 | ] 23 | }, 24 | "angularCompilerOptions": { 25 | "enableI18nLegacyMessageIdFormat": false, 26 | "strictInjectionParameters": true, 27 | "strictInputAccessModifiers": true, 28 | "strictTemplates": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /angular-frontend/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /angular-frontend/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" 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 | "static-before-instance", 35 | "variables-before-functions" 36 | ], 37 | "no-arg": true, 38 | "no-bitwise": true, 39 | "no-console": [ 40 | true, 41 | "debug", 42 | "info", 43 | "time", 44 | "timeEnd", 45 | "trace" 46 | ], 47 | "no-construct": true, 48 | "no-debugger": true, 49 | "no-duplicate-super": true, 50 | "no-empty": false, 51 | "no-empty-interface": true, 52 | "no-eval": true, 53 | "no-inferrable-types": [ 54 | true, 55 | "ignore-params" 56 | ], 57 | "no-misused-new": true, 58 | "no-non-null-assertion": true, 59 | "no-shadowed-variable": true, 60 | "no-string-literal": false, 61 | "no-string-throw": true, 62 | "no-switch-case-fall-through": true, 63 | "no-trailing-whitespace": true, 64 | "no-unnecessary-initializer": true, 65 | "no-unused-expression": true, 66 | "no-use-before-declare": true, 67 | "no-var-keyword": true, 68 | "object-literal-sort-keys": false, 69 | "one-line": [ 70 | true, 71 | "check-open-brace", 72 | "check-catch", 73 | "check-else", 74 | "check-whitespace" 75 | ], 76 | "prefer-const": true, 77 | "quotemark": [ 78 | true, 79 | "single" 80 | ], 81 | "radix": true, 82 | "semicolon": [ 83 | "always" 84 | ], 85 | "triple-equals": [ 86 | true, 87 | "allow-null-check" 88 | ], 89 | "typedef-whitespace": [ 90 | true, 91 | { 92 | "call-signature": "nospace", 93 | "index-signature": "nospace", 94 | "parameter": "nospace", 95 | "property-declaration": "nospace", 96 | "variable-declaration": "nospace" 97 | } 98 | ], 99 | "typeof-compare": true, 100 | "unified-signatures": true, 101 | "variable-name": false, 102 | "whitespace": [ 103 | true, 104 | "check-branch", 105 | "check-decl", 106 | "check-operator", 107 | "check-separator", 108 | "check-type" 109 | ], 110 | "directive-selector": [ 111 | true, 112 | "attribute", 113 | "app", 114 | "camelCase" 115 | ], 116 | "component-selector": [ 117 | true, 118 | "element", 119 | "app", 120 | "kebab-case" 121 | ], 122 | "use-input-property-decorator": true, 123 | "use-output-property-decorator": true, 124 | "use-host-property-decorator": true, 125 | "no-input-rename": true, 126 | "no-output-rename": true, 127 | "use-life-cycle-interface": true, 128 | "use-pipe-transform-interface": true, 129 | "component-class-suffix": true, 130 | "directive-class-suffix": true, 131 | "no-access-missing-member": true, 132 | "templates-use-public": true, 133 | "invoke-injectable": true 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /spring-boot-backend/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /spring-boot-backend/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /spring-boot-backend/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /spring-boot-backend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.example 7 | todoapp 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | todoapp 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.5.5 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 11 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-mongodb 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-validation 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /spring-boot-backend/src/main/java/com/example/todoapp/TodoappApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.todoapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class TodoappApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(TodoappApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-backend/src/main/java/com/example/todoapp/controllers/TodoController.java: -------------------------------------------------------------------------------- 1 | package com.example.todoapp.controllers; 2 | 3 | import javax.validation.Valid; 4 | import com.example.todoapp.models.Todo; 5 | import com.example.todoapp.repositories.TodoRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.domain.Sort; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | import java.util.List; 11 | 12 | @RestController 13 | @RequestMapping("/api") 14 | @CrossOrigin("*") 15 | public class TodoController { 16 | 17 | @Autowired 18 | TodoRepository todoRepository; 19 | 20 | @GetMapping("/todos") 21 | public List getAllTodos() { 22 | Sort sortByCreatedAtDesc = Sort.by(Sort.Direction.DESC, "createdAt"); 23 | return todoRepository.findAll(sortByCreatedAtDesc); 24 | } 25 | 26 | @PostMapping("/todos") 27 | public Todo createTodo(@Valid @RequestBody Todo todo) { 28 | todo.setCompleted(false); 29 | return todoRepository.save(todo); 30 | } 31 | 32 | @GetMapping(value="/todos/{id}") 33 | public ResponseEntity getTodoById(@PathVariable("id") String id) { 34 | return todoRepository.findById(id) 35 | .map(todo -> ResponseEntity.ok().body(todo)) 36 | .orElse(ResponseEntity.notFound().build()); 37 | } 38 | 39 | @PutMapping(value="/todos/{id}") 40 | public ResponseEntity updateTodo(@PathVariable("id") String id, 41 | @Valid @RequestBody Todo todo) { 42 | return todoRepository.findById(id) 43 | .map(todoData -> { 44 | todoData.setTitle(todo.getTitle()); 45 | todoData.setCompleted(todo.getCompleted()); 46 | Todo updatedTodo = todoRepository.save(todoData); 47 | return ResponseEntity.ok().body(updatedTodo); 48 | }).orElse(ResponseEntity.notFound().build()); 49 | } 50 | 51 | @DeleteMapping(value="/todos/{id}") 52 | public ResponseEntity deleteTodo(@PathVariable("id") String id) { 53 | return todoRepository.findById(id) 54 | .map(todo -> { 55 | todoRepository.deleteById(id); 56 | return ResponseEntity.ok().build(); 57 | }).orElse(ResponseEntity.notFound().build()); 58 | } 59 | } -------------------------------------------------------------------------------- /spring-boot-backend/src/main/java/com/example/todoapp/models/Todo.java: -------------------------------------------------------------------------------- 1 | package com.example.todoapp.models; 2 | 3 | import java.util.Date; 4 | import javax.validation.constraints.NotBlank; 5 | import javax.validation.constraints.Size; 6 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 7 | import org.springframework.data.annotation.Id; 8 | import org.springframework.data.mongodb.core.index.Indexed; 9 | import org.springframework.data.mongodb.core.mapping.Document; 10 | 11 | @Document(collection="todos") 12 | @JsonIgnoreProperties(value = {"createdAt"}, allowGetters = true) 13 | public class Todo { 14 | @Id 15 | private String id; 16 | 17 | @NotBlank 18 | @Size(max=100) 19 | @Indexed(unique=true) 20 | private String title; 21 | 22 | private Boolean completed = false; 23 | 24 | private Date createdAt = new Date(); 25 | 26 | public Todo() { 27 | super(); 28 | } 29 | 30 | public Todo(String title) { 31 | this.title = title; 32 | } 33 | 34 | public String getId() { 35 | return id; 36 | } 37 | 38 | public void setId(String id) { 39 | this.id = id; 40 | } 41 | 42 | public String getTitle() { 43 | return title; 44 | } 45 | 46 | public void setTitle(String title) { 47 | this.title = title; 48 | } 49 | 50 | public Boolean getCompleted() { 51 | return completed; 52 | } 53 | 54 | public void setCompleted(Boolean completed) { 55 | this.completed = completed; 56 | } 57 | 58 | public Date getCreatedAt() { 59 | return createdAt; 60 | } 61 | 62 | public void setCreatedAt(Date createdAt) { 63 | this.createdAt = createdAt; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return String.format( 69 | "Todo[id=%s, title='%s', completed='%s']", 70 | id, title, completed); 71 | } 72 | } -------------------------------------------------------------------------------- /spring-boot-backend/src/main/java/com/example/todoapp/repositories/TodoRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.todoapp.repositories; 2 | 3 | import com.example.todoapp.models.Todo; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface TodoRepository extends MongoRepository { 9 | 10 | } -------------------------------------------------------------------------------- /spring-boot-backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # MONGODB (MongoProperties) 2 | spring.data.mongodb.uri=mongodb://localhost:27017/todoapp 3 | -------------------------------------------------------------------------------- /spring-boot-backend/src/test/java/com/example/todoapp/TodoappApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.todoapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | public class TodoappApplicationTests { 8 | 9 | @Test 10 | public void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------