├── .gitignore ├── .vscode └── launch.json ├── README.md ├── server ├── .gitignore ├── .prettierrc ├── .vscode │ └── settings.json ├── README.md ├── db │ └── initdb.d │ │ └── init-users-db.sh ├── docker-compose.yml ├── nest-cli.json ├── nodemon-debug.json ├── nodemon.json ├── ormconfig.json ├── package.json ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── auth │ │ ├── auth.controller.spec.ts │ │ ├── auth.controller.ts │ │ ├── auth.module.ts │ │ ├── auth.service.spec.ts │ │ ├── auth.service.ts │ │ ├── interfaces │ │ │ ├── login-status.interface.ts │ │ │ ├── payload.interface.ts │ │ │ └── regisration-status.interface.ts │ │ └── jwt.strategy.ts │ ├── core │ │ ├── core.module.ts │ │ └── http-exception.filter.ts │ ├── main.ts │ ├── migration │ │ ├── 1551865385236-InitialCreate.ts │ │ ├── 1552392671960-AddUpdatedOnFieldToTodoEntity.ts │ │ ├── 1555148302681-AddUser.ts │ │ ├── 1555166680617-AddOwnerFieldToTodoEntity.ts │ │ └── 1565812987671-SeedUserRecord.ts │ ├── mock │ │ └── todos.mock.ts │ ├── shared │ │ ├── mapper.ts │ │ └── utils.ts │ ├── todo │ │ ├── dto │ │ │ ├── task.create.dto.ts │ │ │ ├── task.dto.ts │ │ │ ├── task.list.dto.ts │ │ │ ├── todo.create.dto.ts │ │ │ ├── todo.dto.ts │ │ │ └── todo.list.dto.ts │ │ ├── entity │ │ │ ├── task.entity.ts │ │ │ └── todo.entity.ts │ │ ├── task │ │ │ ├── task.controller.spec.ts │ │ │ ├── task.controller.ts │ │ │ ├── task.service.spec.ts │ │ │ └── task.service.ts │ │ ├── todo.controller.spec.ts │ │ ├── todo.controller.ts │ │ ├── todo.module.ts │ │ ├── todo.service.spec.ts │ │ └── todo.service.ts │ └── users │ │ ├── dto │ │ ├── user-login.dto.ts │ │ ├── user.create.dto.ts │ │ └── user.dto.ts │ │ ├── entity │ │ └── user.entity.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json ├── tslint.json └── yarn.lock └── todo-client ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── browserslist ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── projects ├── app-common │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── action.ts │ │ │ └── app-common.module.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── auth │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── auth.guard.ts │ │ │ ├── auth.module.ts │ │ │ ├── components │ │ │ │ └── login │ │ │ │ │ ├── login.component.css │ │ │ │ │ ├── login.component.html │ │ │ │ │ └── login.component.ts │ │ │ └── services │ │ │ │ ├── auth.service.ts │ │ │ │ ├── error.interceptor.ts │ │ │ │ └── jwt-interceptor.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json └── todo │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── components │ │ │ ├── task-create │ │ │ │ └── task-create.component.ts │ │ │ ├── task-list │ │ │ │ └── task-list.component.ts │ │ │ ├── task.component.ts │ │ │ ├── todo-create │ │ │ │ └── todo-create.component.ts │ │ │ ├── todo-list │ │ │ │ └── todo-list.component.ts │ │ │ └── todo.component.ts │ │ ├── models │ │ │ ├── task.model.ts │ │ │ └── todo.model.ts │ │ ├── services │ │ │ ├── task.service.ts │ │ │ └── todo.service.ts │ │ ├── todo-home.component.ts │ │ ├── todo.module.ts │ │ └── todo.service.spec.ts │ ├── public-api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── proxy.conf.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ └── shared │ │ ├── home │ │ └── home.component.ts │ │ └── master │ │ ├── master.component.html │ │ └── master.component.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,windows,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=node,windows,visualstudiocode 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | .env.test 65 | 66 | # parcel-bundler cache (https://parceljs.org/) 67 | .cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless/ 80 | 81 | # FuseBox cache 82 | .fusebox/ 83 | 84 | # DynamoDB Local files 85 | .dynamodb/ 86 | 87 | ### VisualStudioCode ### 88 | .vscode/* 89 | !.vscode/settings.json 90 | !.vscode/tasks.json 91 | !.vscode/launch.json 92 | !.vscode/extensions.json 93 | 94 | ### VisualStudioCode Patch ### 95 | # Ignore all local history of files 96 | .history 97 | 98 | ### Windows ### 99 | # Windows thumbnail cache files 100 | Thumbs.db 101 | ehthumbs.db 102 | ehthumbs_vista.db 103 | 104 | # Dump file 105 | *.stackdump 106 | 107 | # Folder config file 108 | [Dd]esktop.ini 109 | 110 | # Recycle Bin used on file shares 111 | $RECYCLE.BIN/ 112 | 113 | # Windows Installer files 114 | *.cab 115 | *.msi 116 | *.msix 117 | *.msm 118 | *.msp 119 | 120 | # Windows shortcuts 121 | *.lnk 122 | 123 | # End of https://www.gitignore.io/api/node,windows,visualstudiocode 124 | 125 | dist/ 126 | 127 | .DS_Store -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/server/src/auth/auth.module.ts", 12 | "outFiles": [ 13 | "${workspaceFolder}/**/*.js" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nestjs-todo-app 2 | A full stack application written in Nest.js, Angular and PostgreSQL. 3 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .DS_Store 4 | node_modules/ 5 | build/ 6 | tmp/ 7 | temp/ -------------------------------------------------------------------------------- /server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /server/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /server/db/initdb.d/init-users-db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL 6 | CREATE USER todo; 7 | CREATE DATABASE todo_db ENCODING UTF8; 8 | GRANT ALL PRIVILEGES ON DATABASE todo_db TO todo; 9 | 10 | ALTER USER todo WITH PASSWORD 'password123'; 11 | ALTER USER todo WITH SUPERUSER; 12 | EOSQL 13 | -------------------------------------------------------------------------------- /server/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | container_name: todo_db 5 | image: postgres:10.7 6 | volumes: 7 | - ./db/initdb.d:/docker-entrypoint-initdb.d 8 | ports: 9 | - '5445:5432' 10 | -------------------------------------------------------------------------------- /server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /server/nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /server/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /server/ormconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "production", 4 | "type": "postgres", 5 | "synchronize": false, 6 | "dropSchema": false, 7 | "logging": true 8 | }, 9 | { 10 | "name": "development", 11 | "type": "postgres", 12 | "host": "localhost", 13 | "port": 5445, 14 | "username": "todo", 15 | "password": "password123", 16 | "database": "todo_db", 17 | "synchronize": false, 18 | "logging": true, 19 | "entities": ["src/**/*.entity.ts"], 20 | "migrations": ["src/migration/**/*.ts"], 21 | "subscribers": ["src/subscriber/**/*.ts"], 22 | "cli": { 23 | "entitiesDir": "src/**/*.entity.ts", 24 | "migrationsDir": "src/migration", 25 | "subscribersDir": "src/subscriber" 26 | } 27 | }, 28 | { 29 | "type": "postgres", 30 | "host": "localhost", 31 | "port": 5445, 32 | "username": "todo", 33 | "password": "password123", 34 | "database": "todo_db", 35 | "synchronize": false, 36 | "logging": true, 37 | "entities": ["src/**/*.entity.ts"], 38 | "migrations": ["src/migration/**/*.ts"], 39 | "subscribers": ["src/subscriber/**/*.ts"], 40 | "cli": { 41 | "entitiesDir": "src", 42 | "migrationsDir": "src/migration", 43 | "subscribersDir": "src/subscriber" 44 | } 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "A nest todo app", 5 | "author": "Bilal Haidar", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc -p tsconfig.build.json", 9 | "format": "prettier --write \"src/**/*.ts\"", 10 | "run:services": "docker-compose up -d && exit 0", 11 | "stop:services": "docker-compose kill", 12 | "start": "ts-node src/index.ts", 13 | "start:dev": "nodemon", 14 | "start:debug": "nodemon --config nodemon-debug.json", 15 | "typeorm:cli": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js -f ormconfig.json", 16 | "db:migration:generate": "npm-run-all -s -l clean build:server && yarn typeorm:cli migration:generate", 17 | "db:migration:create": "yarn typeorm:cli migration:create", 18 | "db:migrate": "npm-run-all -s -l clean build:server && yarn typeorm:cli migration:run", 19 | "query": "yarn typeorm:cli query", 20 | "prestart:prod": "rimraf dist && npm run build", 21 | "start:prod": "node dist/main.js", 22 | "lint": "tslint -p tsconfig.json -c tslint.json", 23 | "test": "jest", 24 | "test:watch": "jest --watch", 25 | "test:cov": "jest --coverage", 26 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 27 | "test:e2e": "jest --config ./test/jest-e2e.json" 28 | }, 29 | "dependencies": { 30 | "@nestjs/common": "6.2.3", 31 | "@nestjs/core": "6.2.3", 32 | "@nestjs/jwt": "6.1.0", 33 | "@nestjs/passport": "6.1.0", 34 | "@nestjs/platform-express": "6.2.3", 35 | "@nestjs/typeorm": "^6.1.1", 36 | "bcrypt": "^3.0.6", 37 | "class-transformer": "0.2.3", 38 | "class-validator": "0.9.1", 39 | "cookie-parser": "^1.4.4", 40 | "cors": "^2.8.5", 41 | "csurf": "^1.9.0", 42 | "dotenv": "7.0.0", 43 | "express-rate-limit": "^3.4.0", 44 | "global": "^4.3.2", 45 | "helmet": "^3.16.0", 46 | "passport": "^0.4.0", 47 | "passport-http-bearer": "^1.0.1", 48 | "passport-jwt": "^4.0.0", 49 | "pg": "7.9.0", 50 | "reflect-metadata": "0.1.12", 51 | "rxjs": "6.4.0", 52 | "typeorm": "0.2.16", 53 | "uuid": "3.3.2" 54 | }, 55 | "devDependencies": { 56 | "@nestjs/testing": "6.2.4", 57 | "@types/bcrypt": "^3.0.0", 58 | "@types/cookie-parser": "^1.4.1", 59 | "@types/cors": "^2.8.4", 60 | "@types/csurf": "^1.9.35", 61 | "@types/dotenv": "6.1.1", 62 | "@types/express": "4.16.1", 63 | "@types/express-rate-limit": "^3.3.0", 64 | "@types/helmet": "^0.0.43", 65 | "@types/jest": "24.0.11", 66 | "@types/node": "10.14.4", 67 | "@types/passport": "^1.0.0", 68 | "@types/passport-jwt": "^3.0.1", 69 | "@types/supertest": "2.0.7", 70 | "@types/uuid": "3.4.4", 71 | "jest": "^23.5.0", 72 | "nodemon": "1.18.11", 73 | "prettier": "1.17.0", 74 | "supertest": "^3.1.0", 75 | "ts-jest": "^23.1.3", 76 | "ts-loader": "5.3.3", 77 | "ts-node": "8.0.3", 78 | "tsconfig-paths": "3.8.0", 79 | "tslint": "5.15.0", 80 | "typescript": "3.4.3" 81 | }, 82 | "jest": { 83 | "moduleFileExtensions": [ 84 | "js", 85 | "json", 86 | "ts" 87 | ], 88 | "rootDir": "src", 89 | "testRegex": ".spec.ts$", 90 | "transform": { 91 | "^.+\\.(t|j)s$": "ts-jest" 92 | }, 93 | "coverageDirectory": "../coverage", 94 | "testEnvironment": "node" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /server/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /server/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, DynamicModule } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | import { TodoModule } from './todo/todo.module'; 6 | import { ConnectionOptions } from 'typeorm'; 7 | import { UsersModule } from './users/users.module'; 8 | import { CoreModule } from './core/core.module'; 9 | import { AuthModule } from './auth/auth.module'; 10 | 11 | @Module({}) 12 | export class AppModule { 13 | static forRoot(connOptions: ConnectionOptions): DynamicModule { 14 | return { 15 | module: AppModule, 16 | controllers: [AppController], 17 | imports: [ 18 | AuthModule, 19 | TodoModule, 20 | UsersModule, 21 | CoreModule, 22 | TypeOrmModule.forRoot(connOptions), 23 | ], 24 | providers: [AppService], 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /server/src/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthController } from './auth.controller'; 3 | 4 | describe('Auth Controller', () => { 5 | let controller: AuthController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [AuthController], 10 | }).compile(); 11 | 12 | controller = module.get(AuthController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Body, 4 | Post, 5 | HttpException, 6 | HttpStatus, 7 | UsePipes, 8 | Get, 9 | Req, 10 | UseGuards, 11 | } from '@nestjs/common'; 12 | import { CreateUserDto } from '@user/dto/user.create.dto'; 13 | import { RegistrationStatus } from './interfaces/regisration-status.interface'; 14 | import { AuthService } from './auth.service'; 15 | import { LoginStatus } from './interfaces/login-status.interface'; 16 | import { LoginUserDto } from '../users/dto/user-login.dto'; 17 | import { JwtPayload } from './interfaces/payload.interface'; 18 | import { AuthGuard } from '@nestjs/passport'; 19 | 20 | @Controller('auth') 21 | export class AuthController { 22 | constructor(private readonly authService: AuthService) {} 23 | 24 | @Post('register') 25 | public async register( 26 | @Body() createUserDto: CreateUserDto, 27 | ): Promise { 28 | const result: RegistrationStatus = await this.authService.register( 29 | createUserDto, 30 | ); 31 | 32 | if (!result.success) { 33 | throw new HttpException(result.message, HttpStatus.BAD_REQUEST); 34 | } 35 | 36 | return result; 37 | } 38 | 39 | @Post('login') 40 | public async login(@Body() loginUserDto: LoginUserDto): Promise { 41 | return await this.authService.login(loginUserDto); 42 | } 43 | 44 | @Get('whoami') 45 | @UseGuards(AuthGuard()) 46 | public async testAuth(@Req() req: any): Promise { 47 | return req.user; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthController } from './auth.controller'; 3 | import { AuthService } from './auth.service'; 4 | import { UsersModule } from '@user/users.module'; 5 | import { JwtModule } from '@nestjs/jwt'; 6 | import { PassportModule } from '@nestjs/passport'; 7 | import { JwtStrategy } from './jwt.strategy'; 8 | 9 | @Module({ 10 | imports: [ 11 | UsersModule, 12 | PassportModule.register({ 13 | defaultStrategy: 'jwt', 14 | property: 'user', 15 | session: false, 16 | }), 17 | JwtModule.register({ 18 | secret: process.env.SECRETKEY, 19 | signOptions: { 20 | expiresIn: process.env.EXPIRESIN, 21 | }, 22 | }), 23 | ], 24 | controllers: [AuthController], 25 | providers: [AuthService, JwtStrategy], 26 | exports: [PassportModule, JwtModule], 27 | }) 28 | export class AuthModule {} 29 | -------------------------------------------------------------------------------- /server/src/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthService } from './auth.service'; 3 | 4 | describe('AuthService', () => { 5 | let service: AuthService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AuthService], 10 | }).compile(); 11 | 12 | service = module.get(AuthService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; 2 | import { CreateUserDto } from '@user/dto/user.create.dto'; 3 | import { RegistrationStatus } from './interfaces/regisration-status.interface'; 4 | import { UsersService } from '@user/users.service'; 5 | import { LoginStatus } from './interfaces/login-status.interface'; 6 | import { LoginUserDto } from '../users/dto/user-login.dto'; 7 | import { UserDto } from '@user/dto/user.dto'; 8 | import { JwtPayload } from './interfaces/payload.interface'; 9 | import { JwtService } from '@nestjs/jwt'; 10 | 11 | @Injectable() 12 | export class AuthService { 13 | constructor( 14 | private readonly usersService: UsersService, 15 | private readonly jwtService: JwtService, 16 | ) {} 17 | 18 | async register(userDto: CreateUserDto): Promise { 19 | let status: RegistrationStatus = { 20 | success: true, 21 | message: 'user registered', 22 | }; 23 | 24 | try { 25 | await this.usersService.create(userDto); 26 | } catch (err) { 27 | status = { 28 | success: false, 29 | message: err, 30 | }; 31 | } 32 | 33 | return status; 34 | } 35 | 36 | async login(loginUserDto: LoginUserDto): Promise { 37 | // find user in db 38 | const user = await this.usersService.findByLogin(loginUserDto); 39 | 40 | // generate and sign token 41 | const token = this._createToken(user); 42 | 43 | return { 44 | username: user.username, 45 | ...token, 46 | }; 47 | } 48 | 49 | async validateUser(payload: JwtPayload): Promise { 50 | const user = await this.usersService.findByPayload(payload); 51 | if (!user) { 52 | throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); 53 | } 54 | return user; 55 | } 56 | 57 | private _createToken({ username }: UserDto): any { 58 | const expiresIn = process.env.EXPIRESIN; 59 | 60 | const user: JwtPayload = { username }; 61 | const accessToken = this.jwtService.sign(user); 62 | return { 63 | expiresIn, 64 | accessToken, 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /server/src/auth/interfaces/login-status.interface.ts: -------------------------------------------------------------------------------- 1 | import { UserDto } from '@user/dto/user.dto'; 2 | 3 | export interface LoginStatus { 4 | username: string; 5 | accessToken: any; 6 | expiresIn: any; 7 | } 8 | -------------------------------------------------------------------------------- /server/src/auth/interfaces/payload.interface.ts: -------------------------------------------------------------------------------- 1 | export interface JwtPayload { 2 | username: string; 3 | } 4 | -------------------------------------------------------------------------------- /server/src/auth/interfaces/regisration-status.interface.ts: -------------------------------------------------------------------------------- 1 | export interface RegistrationStatus { 2 | success: boolean; 3 | message: string; 4 | } 5 | -------------------------------------------------------------------------------- /server/src/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { PassportStrategy } from '@nestjs/passport'; 2 | import { ExtractJwt, Strategy } from 'passport-jwt'; 3 | import { AuthService } from './auth.service'; 4 | import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; 5 | import { JwtPayload } from './interfaces/payload.interface'; 6 | import { UserDto } from '@user/dto/user.dto'; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy) { 10 | constructor(private readonly authService: AuthService) { 11 | super({ 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | secretOrKey: process.env.SECRETKEY, 14 | }); 15 | } 16 | 17 | async validate(payload: JwtPayload): Promise { 18 | const user = await this.authService.validateUser(payload); 19 | if (!user) { 20 | throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); 21 | } 22 | return user; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/src/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { HttpExceptionFilter } from './http-exception.filter'; 3 | import { APP_FILTER } from '@nestjs/core'; 4 | 5 | @Module({ 6 | providers: [ 7 | { 8 | provide: APP_FILTER, 9 | useClass: HttpExceptionFilter, 10 | }, 11 | ], 12 | }) 13 | export class CoreModule {} 14 | -------------------------------------------------------------------------------- /server/src/core/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExceptionFilter, 3 | HttpException, 4 | HttpStatus, 5 | ArgumentsHost, 6 | Catch, 7 | Logger, 8 | } from '@nestjs/common'; 9 | 10 | @Catch() 11 | export class HttpExceptionFilter implements ExceptionFilter { 12 | catch(exception: HttpException, host: ArgumentsHost) { 13 | const ctx = host.switchToHttp(); 14 | const response = ctx.getResponse(); 15 | const request = ctx.getRequest(); 16 | const status = exception.getStatus 17 | ? exception.getStatus() 18 | : HttpStatus.INTERNAL_SERVER_ERROR; 19 | 20 | const errorResponse = { 21 | code: status, 22 | timestamp: new Date().toLocaleDateString(), 23 | path: request.url, 24 | method: request.method, 25 | message: 26 | status !== HttpStatus.INTERNAL_SERVER_ERROR 27 | ? exception.message.error || exception.message || null 28 | : 'Internal server error', 29 | }; 30 | 31 | Logger.error( 32 | `${request.method} ${request.url}`, 33 | exception.stack, 34 | 'HttpExceptionFilter', 35 | ); 36 | 37 | response.status(status).json(errorResponse); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | 3 | import { NestFactory } from '@nestjs/core'; 4 | import { AppModule } from './app.module'; 5 | import { Logger, ValidationPipe } from '@nestjs/common'; 6 | import { getDbConnectionOptions, runDbMigrations } from '@shared/utils'; 7 | import * as helmet from 'helmet'; 8 | import * as rateLimit from 'express-rate-limit'; 9 | 10 | const port = process.env.PORT; 11 | 12 | async function bootstrap() { 13 | const app = await NestFactory.create( 14 | AppModule.forRoot(await getDbConnectionOptions(process.env.NODE_ENV)), 15 | { 16 | // logger: Boolean(process.env.ENABLELOGGING), 17 | logger: console, 18 | }, 19 | ); 20 | 21 | /** 22 | * Helmet can help protect your app from some well-known 23 | * web vulnerabilities by setting HTTP headers appropriately. 24 | * Generally, Helmet is just a collection of 12 smaller 25 | * middleware functions that set security-related HTTP headers 26 | * 27 | * https://github.com/helmetjs/helmet#how-it-works 28 | */ 29 | app.use(helmet()); 30 | 31 | app.enableCors(); 32 | 33 | // /** 34 | // * we need this because "cookie" is true in csrfProtection 35 | // */ 36 | // app.use(cookieParser()); 37 | 38 | // app.use(csurf({ cookie: true })); 39 | 40 | /** 41 | * To protect your applications from brute-force attacks 42 | */ 43 | app.use( 44 | new rateLimit({ 45 | windowMs: 15 * 60 * 1000, 46 | max: 100, 47 | }), 48 | ); 49 | 50 | /** 51 | * Apply validation for all inputs globally 52 | */ 53 | app.useGlobalPipes( 54 | new ValidationPipe({ 55 | /** 56 | * Strip away all none-object existing properties 57 | */ 58 | whitelist: true, 59 | /*** 60 | * Transform input objects to their corresponding DTO objects 61 | */ 62 | transform: true, 63 | }), 64 | ); 65 | 66 | /** 67 | * Run DB migrations 68 | */ 69 | await runDbMigrations(); 70 | 71 | await app.listen(port); 72 | 73 | Logger.log(`Server started running on http://localhost:${port}`, 'Bootstrap'); 74 | } 75 | 76 | bootstrap(); 77 | -------------------------------------------------------------------------------- /server/src/migration/1551865385236-InitialCreate.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class InitialCreate1551865385236 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query(`CREATE TABLE "todo" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "description" text, "createdOn" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_d429b7114371f6a35c5cb4776a7" PRIMARY KEY ("id"))`); 7 | await queryRunner.query(`CREATE TABLE "task" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "createdOn" TIMESTAMP NOT NULL DEFAULT now(), "todoId" uuid, CONSTRAINT "PK_fb213f79ee45060ba925ecd576e" PRIMARY KEY ("id"))`); 8 | await queryRunner.query(`ALTER TABLE "task" ADD CONSTRAINT "FK_91440d017e7b30d2ac16a27d762" FOREIGN KEY ("todoId") REFERENCES "todo"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`ALTER TABLE "task" DROP CONSTRAINT "FK_91440d017e7b30d2ac16a27d762"`); 13 | await queryRunner.query(`DROP TABLE "task"`); 14 | await queryRunner.query(`DROP TABLE "todo"`); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/src/migration/1552392671960-AddUpdatedOnFieldToTodoEntity.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class AddUpdatedOnFieldToTodoEntity1552392671960 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query(`ALTER TABLE "todo" ADD "updatedOn" TIMESTAMP NOT NULL DEFAULT now()`); 7 | } 8 | 9 | public async down(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query(`ALTER TABLE "todo" DROP COLUMN "updatedOn"`); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /server/src/migration/1555148302681-AddUser.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddUser1555148302681 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.query( 6 | // tslint:disable-next-line: max-line-length 7 | `CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "username" character varying NOT NULL, "password" character varying NOT NULL, "email" character varying NOT NULL, "createdOn" TIMESTAMP NOT NULL DEFAULT now(), "updatedOn" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`, 8 | ); 9 | } 10 | 11 | public async down(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query(`DROP TABLE "users"`); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/src/migration/1555166680617-AddOwnerFieldToTodoEntity.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class AddOwnerFieldToTodoEntity1555166680617 4 | implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query(`ALTER TABLE "todo" ADD "ownerId" uuid`); 7 | await queryRunner.query( 8 | // tslint:disable-next-line: max-line-length 9 | `ALTER TABLE "todo" ADD CONSTRAINT "FK_05552e862619dc4ad7ec8fc9cb8" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, 10 | ); 11 | } 12 | 13 | public async down(queryRunner: QueryRunner): Promise { 14 | await queryRunner.query( 15 | `ALTER TABLE "todo" DROP CONSTRAINT "FK_05552e862619dc4ad7ec8fc9cb8"`, 16 | ); 17 | await queryRunner.query(`ALTER TABLE "todo" DROP COLUMN "ownerId"`); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/src/migration/1565812987671-SeedUserRecord.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/typeorm/typeorm/blob/master/docs/transactions.md 2 | 3 | import { MigrationInterface, QueryRunner } from 'typeorm'; 4 | import { UserEntity } from '../users/entity/user.entity'; 5 | 6 | export class SeedUserRecord1565812987671 implements MigrationInterface { 7 | public async up(queryRunner: QueryRunner): Promise { 8 | const userRepo = queryRunner.manager.getRepository(UserEntity); 9 | 10 | const user = userRepo.create({ 11 | username: 'bhaidar', 12 | password: '@dF%^hGb03W~', 13 | email: 'bhaidar@gmail.com', 14 | }); 15 | 16 | await userRepo.save(user); 17 | } 18 | 19 | // tslint:disable-next-line: no-empty 20 | public async down(queryRunner: QueryRunner): Promise {} 21 | } 22 | -------------------------------------------------------------------------------- /server/src/mock/todos.mock.ts: -------------------------------------------------------------------------------- 1 | import { TodoEntity } from 'src/todo/entity/todo.entity'; 2 | 3 | export const todos: TodoEntity[] = [ 4 | { 5 | id: 'eac400ba-3c78-11e9-b210-d663bd873d93', 6 | name: 'Supermarket Todo list', 7 | tasks: [ 8 | { 9 | id: 'b91a5400-3cce-11e9-b210-d663bd873d93', 10 | name: 'Bring coffee', 11 | }, 12 | { 13 | id: 'b91a56c6-3cce-11e9-b210-d663bd873d93', 14 | name: 'Bring banana', 15 | }, 16 | ], 17 | }, 18 | { 19 | id: 'eac40736-3c78-11e9-b210-d663bd873d93', 20 | name: 'Office Todo list', 21 | tasks: [ 22 | { 23 | id: 'b91a5a90-3cce-11e9-b210-d663bd873d93', 24 | name: 'Bring chairs', 25 | }, 26 | { 27 | id: 'b91a5bf8-3cce-11e9-b210-d663bd873d93', 28 | name: 'Bring tables', 29 | }, 30 | ], 31 | }, 32 | { 33 | id: 'eac408d0-3c78-11e9-b210-d663bd873d93', 34 | name: 'Traveling Todo list', 35 | }, 36 | { 37 | id: 'eac40a7e-3c78-11e9-b210-d663bd873d93', 38 | name: 'Studying Todo list', 39 | }, 40 | { 41 | id: 'eac40c90-3c78-11e9-b210-d663bd873d93', 42 | name: 'Monday Todo list', 43 | }, 44 | { 45 | id: '5ea5f9ed-dd64-4e08-bdb6-d3d5-354fe48', 46 | name: 'My awesome todo list', 47 | description: 'A new awesome and cool todo list', 48 | }, 49 | ]; 50 | -------------------------------------------------------------------------------- /server/src/shared/mapper.ts: -------------------------------------------------------------------------------- 1 | import { TaskDto } from '@todo/dto/task.dto'; 2 | import { TodoEntity } from '@todo/entity/todo.entity'; 3 | import { TodoDto } from '@todo/dto/todo.dto'; 4 | import { TaskEntity } from '@todo/entity/task.entity'; 5 | import { UserEntity } from '@user/entity/user.entity'; 6 | import { UserDto } from '@user/dto/user.dto'; 7 | 8 | export const toTodoDto = (data: TodoEntity): TodoDto => { 9 | const { id, name, description, tasks, owner } = data; 10 | 11 | let todoDto: TodoDto = { 12 | id, 13 | name, 14 | description, 15 | owner: owner ? toUserDto(owner) : null, 16 | }; 17 | 18 | if (tasks) { 19 | todoDto = { 20 | ...todoDto, 21 | tasks: tasks.map((task: TaskEntity) => toTaskDto(task)), 22 | }; 23 | } 24 | 25 | return todoDto; 26 | }; 27 | 28 | export const toTaskDto = (data: TaskEntity): TaskDto => { 29 | const { id, name } = data; 30 | 31 | let taskDto: TaskDto = { 32 | id, 33 | name, 34 | }; 35 | 36 | return taskDto; 37 | }; 38 | 39 | export const toUserDto = (data: UserEntity): UserDto => { 40 | const { id, username, email } = data; 41 | 42 | let userDto: UserDto = { 43 | id, 44 | username, 45 | email, 46 | }; 47 | 48 | return userDto; 49 | }; 50 | -------------------------------------------------------------------------------- /server/src/shared/utils.ts: -------------------------------------------------------------------------------- 1 | import { getConnectionOptions, getConnection } from 'typeorm'; 2 | import * as bcrypt from 'bcrypt'; 3 | import { Logger } from '@nestjs/common'; 4 | 5 | export const toPromise = (data: T): Promise => { 6 | return new Promise(resolve => { 7 | resolve(data); 8 | }); 9 | }; 10 | 11 | export const getDbConnectionOptions = async ( 12 | connectionName: string = 'default', 13 | ) => { 14 | const options = await getConnectionOptions( 15 | process.env.NODE_ENV || 'development', 16 | ); 17 | return { 18 | ...options, 19 | name: connectionName, 20 | }; 21 | }; 22 | 23 | export const getDbConnection = async (connectionName: string = 'default') => { 24 | return await getConnection(connectionName); 25 | }; 26 | 27 | export const runDbMigrations = async (connectionName: string = 'default') => { 28 | const conn = await getDbConnection(connectionName); 29 | await conn.runMigrations(); 30 | }; 31 | 32 | export const comparePasswords = async (userPassword, currentPassword) => { 33 | return await bcrypt.compare(currentPassword, userPassword); 34 | }; 35 | -------------------------------------------------------------------------------- /server/src/todo/dto/task.create.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateTaskDto { 4 | @IsNotEmpty() 5 | name: string; 6 | } 7 | -------------------------------------------------------------------------------- /server/src/todo/dto/task.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class TaskDto { 4 | @IsNotEmpty() 5 | id: string; 6 | 7 | @IsNotEmpty() 8 | @IsString() 9 | name: string; 10 | 11 | createdOn?: Date; 12 | } 13 | -------------------------------------------------------------------------------- /server/src/todo/dto/task.list.dto.ts: -------------------------------------------------------------------------------- 1 | import { TaskDto } from './task.dto'; 2 | 3 | export class TaskListDto { 4 | tasks: TaskDto[]; 5 | } 6 | -------------------------------------------------------------------------------- /server/src/todo/dto/todo.create.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, MaxLength, IsOptional } from 'class-validator'; 2 | import { UserDto } from '@user/dto/user.dto'; 3 | 4 | export class CreateTodoDto { 5 | @IsNotEmpty() 6 | name: string; 7 | 8 | @IsOptional() 9 | @MaxLength(500) 10 | description?: string; 11 | } 12 | -------------------------------------------------------------------------------- /server/src/todo/dto/todo.dto.ts: -------------------------------------------------------------------------------- 1 | import { TaskDto } from './task.dto'; 2 | import { IsNotEmpty } from 'class-validator'; 3 | import { UserDto } from '@user/dto/user.dto'; 4 | 5 | export class TodoDto { 6 | @IsNotEmpty() 7 | id: string; 8 | 9 | @IsNotEmpty() 10 | name: string; 11 | 12 | createdOn?: Date; 13 | description?: string; 14 | 15 | owner: UserDto; 16 | 17 | tasks?: TaskDto[]; 18 | } 19 | -------------------------------------------------------------------------------- /server/src/todo/dto/todo.list.dto.ts: -------------------------------------------------------------------------------- 1 | import { TodoDto } from './todo.dto'; 2 | 3 | export class TodoListDto { 4 | todos: TodoDto[]; 5 | } 6 | -------------------------------------------------------------------------------- /server/src/todo/entity/task.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column, 5 | CreateDateColumn, 6 | ManyToOne, 7 | } from 'typeorm'; 8 | import { TodoEntity } from '@todo/entity/todo.entity'; 9 | 10 | @Entity('task') 11 | export class TaskEntity { 12 | @PrimaryGeneratedColumn('uuid') id: string; 13 | @Column({ type: 'varchar', nullable: false }) name: string; 14 | @CreateDateColumn() createdOn?: Date; 15 | 16 | @ManyToOne(type => TodoEntity, todo => todo.tasks) 17 | todo?: TodoEntity; 18 | } 19 | -------------------------------------------------------------------------------- /server/src/todo/entity/todo.entity.ts: -------------------------------------------------------------------------------- 1 | import { TaskEntity } from '@todo/entity/task.entity'; 2 | import { 3 | Entity, 4 | PrimaryGeneratedColumn, 5 | Column, 6 | CreateDateColumn, 7 | OneToMany, 8 | ManyToOne, 9 | JoinTable, 10 | } from 'typeorm'; 11 | import { UserEntity } from '@user/entity/user.entity'; 12 | 13 | @Entity('todo') 14 | export class TodoEntity { 15 | @PrimaryGeneratedColumn('uuid') id: string; 16 | @Column({ type: 'varchar', nullable: false }) name: string; 17 | @Column({ type: 'text', nullable: true }) description?: string; 18 | @CreateDateColumn() createdOn?: Date; 19 | @CreateDateColumn() updatedOn?: Date; 20 | 21 | @ManyToOne(type => UserEntity) 22 | owner?: UserEntity; 23 | 24 | @OneToMany(type => TaskEntity, task => task.todo) 25 | tasks?: TaskEntity[]; 26 | } 27 | -------------------------------------------------------------------------------- /server/src/todo/task/task.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TaskController } from './task.controller'; 3 | 4 | describe('Task Controller', () => { 5 | let controller: TaskController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [TaskController], 10 | }).compile(); 11 | 12 | controller = module.get(TaskController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/todo/task/task.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Param, 4 | Get, 5 | Post, 6 | Body, 7 | Delete, 8 | UsePipes, 9 | UseGuards, 10 | } from '@nestjs/common'; 11 | import { TaskService } from './task.service'; 12 | import { TaskListDto } from '../dto/task.list.dto'; 13 | import { TaskDto } from '../dto/task.dto'; 14 | import { CreateTaskDto } from '@todo/dto/task.create.dto'; 15 | import { AuthGuard } from '@nestjs/passport'; 16 | 17 | @Controller('api/tasks') 18 | export class TaskController { 19 | constructor(private taskService: TaskService) {} 20 | 21 | @Get(':id') 22 | async findOneTask(@Param('id') id: string): Promise { 23 | return await this.taskService.getTask(id); 24 | } 25 | 26 | @Get('todo/:id') 27 | async findTasksByTodo(@Param('id') id: string): Promise { 28 | const tasks = await this.taskService.getTasksByTodo(id); 29 | return { tasks }; 30 | } 31 | 32 | @Post('todo/:id') 33 | @UseGuards(AuthGuard()) 34 | async create( 35 | @Param('id') todo: string, 36 | @Body() createTaskDto: CreateTaskDto, 37 | ): Promise { 38 | return await this.taskService.createTask(todo, createTaskDto); 39 | } 40 | 41 | @Delete(':id') 42 | @UseGuards(AuthGuard()) 43 | async destory(@Param('id') id: string): Promise { 44 | return await this.taskService.destoryTask(id); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/src/todo/task/task.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TaskService } from './task.service'; 3 | 4 | describe('TaskService', () => { 5 | let service: TaskService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [TaskService], 10 | }).compile(); 11 | 12 | service = module.get(TaskService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/todo/task/task.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; 2 | import { CreateTaskDto } from '../dto/task.create.dto'; 3 | import { TaskDto } from '../dto/task.dto'; 4 | import { TaskEntity } from '@todo/entity/task.entity'; 5 | import { toTaskDto } from '@shared/mapper'; 6 | import { InjectRepository } from '@nestjs/typeorm'; 7 | import { Repository } from 'typeorm'; 8 | import { TodoEntity } from '@todo/entity/todo.entity'; 9 | 10 | @Injectable() 11 | export class TaskService { 12 | constructor( 13 | @InjectRepository(TaskEntity) 14 | private readonly taskRepo: Repository, 15 | @InjectRepository(TodoEntity) 16 | private readonly todoRepo: Repository, 17 | ) {} 18 | 19 | async getTask(id: string): Promise { 20 | const task: TaskEntity = await this.taskRepo.findOne({ where: { id } }); 21 | 22 | if (!task) { 23 | throw new HttpException(`Task doesn't exist`, HttpStatus.BAD_REQUEST); 24 | } 25 | 26 | return toTaskDto(task); 27 | } 28 | 29 | async getTasksByTodo(id: string): Promise { 30 | const tasks: TaskEntity[] = await this.taskRepo.find({ 31 | where: { todo: { id } }, 32 | relations: ['todo'], 33 | }); 34 | 35 | return tasks.map(task => toTaskDto(task)); 36 | } 37 | 38 | async createTask(todoId: string, taskDto: CreateTaskDto): Promise { 39 | const { name } = taskDto; 40 | 41 | const todo: TodoEntity = await this.todoRepo.findOne({ 42 | where: { id: todoId }, 43 | relations: ['tasks', 'owner'], 44 | }); 45 | 46 | const task: TaskEntity = await this.taskRepo.create({ 47 | name, 48 | todo, 49 | }); 50 | 51 | await this.taskRepo.save(task); 52 | 53 | return toTaskDto(task); 54 | } 55 | 56 | async destoryTask(id: string): Promise { 57 | const task: TaskEntity = await this.taskRepo.findOne({ where: { id } }); 58 | 59 | if (!task) { 60 | throw new HttpException(`Task doesn't exist`, HttpStatus.BAD_REQUEST); 61 | } 62 | 63 | await this.taskRepo.delete({ id }); 64 | 65 | return toTaskDto(task); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /server/src/todo/todo.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TodoController } from './todo.controller'; 3 | 4 | describe('Todo Controller', () => { 5 | let controller: TodoController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [TodoController], 10 | }).compile(); 11 | 12 | controller = module.get(TodoController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/todo/todo.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Param, 5 | Post, 6 | Body, 7 | Put, 8 | Delete, 9 | UsePipes, 10 | UseGuards, 11 | Req, 12 | } from '@nestjs/common'; 13 | import { TodoListDto } from './dto/todo.list.dto'; 14 | import { TodoDto } from './dto/todo.dto'; 15 | import { CreateTodoDto } from './dto/todo.create.dto'; 16 | import { TodoService } from './todo.service'; 17 | import { AuthGuard } from '@nestjs/passport'; 18 | import { UserDto } from '@user/dto/user.dto'; 19 | 20 | @Controller('api/todos') 21 | export class TodoController { 22 | constructor(private readonly todoService: TodoService) {} 23 | 24 | @Get() 25 | async findAll(@Req() req: any): Promise { 26 | const todos = await this.todoService.getAllTodo(); 27 | return { todos }; 28 | } 29 | 30 | @Get(':id') 31 | async findOne(@Param('id') id: string): Promise { 32 | return await this.todoService.getOneTodo(id); 33 | } 34 | 35 | @Post() 36 | @UseGuards(AuthGuard()) 37 | async create( 38 | @Body() createTodoDto: CreateTodoDto, 39 | @Req() req: any, 40 | ): Promise { 41 | const user = req.user as UserDto; 42 | 43 | return await this.todoService.createTodo(user, createTodoDto); 44 | } 45 | 46 | @Put(':id') 47 | @UseGuards(AuthGuard()) 48 | async update( 49 | @Param('id') id: string, 50 | @Body() todoDto: TodoDto, 51 | ): Promise { 52 | return await this.todoService.updateTodo(id, todoDto); 53 | } 54 | 55 | @Delete(':id') 56 | @UseGuards(AuthGuard()) 57 | async destory(@Param('id') id: string): Promise { 58 | return await this.todoService.destoryTodo(id); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server/src/todo/todo.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TodoController } from './todo.controller'; 3 | import { TodoService } from './todo.service'; 4 | import { TaskController } from './task/task.controller'; 5 | import { TaskService } from './task/task.service'; 6 | import { TodoEntity } from '@todo/entity/todo.entity'; 7 | import { TaskEntity } from '@todo/entity/task.entity'; 8 | import { TypeOrmModule } from '@nestjs/typeorm'; 9 | import { UserEntity } from '@user/entity/user.entity'; 10 | import { UsersModule } from '@user/users.module'; 11 | import { AuthModule } from 'src/auth/auth.module'; 12 | 13 | @Module({ 14 | imports: [ 15 | UsersModule, 16 | AuthModule, 17 | TypeOrmModule.forFeature([TodoEntity, TaskEntity, UserEntity]), 18 | ], 19 | controllers: [TodoController, TaskController], 20 | providers: [TodoService, TaskService], 21 | }) 22 | export class TodoModule {} 23 | -------------------------------------------------------------------------------- /server/src/todo/todo.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TodoService } from './todo.service'; 3 | 4 | describe('TodoService', () => { 5 | let service: TodoService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [TodoService], 10 | }).compile(); 11 | 12 | service = module.get(TodoService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/todo/todo.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | import { TodoEntity } from '@todo/entity/todo.entity'; 4 | import { TodoDto } from './dto/todo.dto'; 5 | import { toTodoDto } from '@shared/mapper'; 6 | import { CreateTodoDto } from './dto/todo.create.dto'; 7 | 8 | import { InjectRepository } from '@nestjs/typeorm'; 9 | import { Repository } from 'typeorm'; 10 | import { UserDto } from '@user/dto/user.dto'; 11 | import { UsersService } from '@user/users.service'; 12 | 13 | @Injectable() 14 | export class TodoService { 15 | constructor( 16 | @InjectRepository(TodoEntity) 17 | private readonly todoRepo: Repository, 18 | private readonly usersService: UsersService, 19 | ) {} 20 | 21 | async getAllTodo(): Promise { 22 | const todos = await this.todoRepo.find({ relations: ['tasks', 'owner'] }); 23 | return todos.map(todo => toTodoDto(todo)); 24 | } 25 | 26 | async getOneTodo(id: string): Promise { 27 | const todo = await this.todoRepo.findOne({ 28 | where: { id }, 29 | relations: ['tasks', 'owner'], 30 | }); 31 | 32 | if (!todo) { 33 | throw new HttpException( 34 | `Todo list doesn't exist`, 35 | HttpStatus.BAD_REQUEST, 36 | ); 37 | } 38 | 39 | return toTodoDto(todo); 40 | } 41 | 42 | async createTodo( 43 | { username }: UserDto, 44 | createTodoDto: CreateTodoDto, 45 | ): Promise { 46 | const { name, description } = createTodoDto; 47 | 48 | // get the user from db 49 | const owner = await this.usersService.findOne({ where: { username } }); 50 | 51 | const todo: TodoEntity = await this.todoRepo.create({ 52 | name, 53 | description, 54 | owner, 55 | }); 56 | 57 | await this.todoRepo.save(todo); 58 | 59 | return toTodoDto(todo); 60 | } 61 | 62 | async updateTodo(id: string, todoDto: TodoDto): Promise { 63 | const { name, description } = todoDto; 64 | 65 | let todo: TodoEntity = await this.todoRepo.findOne({ where: { id } }); 66 | 67 | if (!todo) { 68 | throw new HttpException( 69 | `Todo list doesn't exist`, 70 | HttpStatus.BAD_REQUEST, 71 | ); 72 | } 73 | 74 | todo = { 75 | id, 76 | name, 77 | description, 78 | }; 79 | 80 | await this.todoRepo.update({ id }, todo); // update 81 | 82 | todo = await this.todoRepo.findOne({ 83 | where: { id }, 84 | relations: ['tasks', 'owner'], 85 | }); // re-query 86 | 87 | return toTodoDto(todo); 88 | } 89 | 90 | async destoryTodo(id: string): Promise { 91 | const todo: TodoEntity = await this.todoRepo.findOne({ 92 | where: { id }, 93 | relations: ['tasks', 'owner'], 94 | }); 95 | 96 | if (!todo) { 97 | throw new HttpException( 98 | `Todo list doesn't exist`, 99 | HttpStatus.BAD_REQUEST, 100 | ); 101 | } 102 | 103 | if (todo.tasks && todo.tasks.length > 0) { 104 | throw new HttpException( 105 | `Cannot delete this Todo list, it has existing tasks`, 106 | HttpStatus.FORBIDDEN, 107 | ); 108 | } 109 | 110 | await this.todoRepo.delete({ id }); // delete todo list 111 | 112 | return toTodoDto(todo); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /server/src/users/dto/user-login.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class LoginUserDto { 4 | @IsNotEmpty() 5 | readonly username: string; 6 | 7 | @IsNotEmpty() 8 | readonly password: string; 9 | } 10 | -------------------------------------------------------------------------------- /server/src/users/dto/user.create.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsEmail } from 'class-validator'; 2 | 3 | export class CreateUserDto { 4 | @IsNotEmpty() 5 | username: string; 6 | 7 | @IsNotEmpty() 8 | password: string; 9 | 10 | @IsNotEmpty() 11 | @IsEmail() 12 | email: string; 13 | } 14 | -------------------------------------------------------------------------------- /server/src/users/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsEmail } from 'class-validator'; 2 | 3 | export class UserDto { 4 | @IsNotEmpty() 5 | id: string; 6 | 7 | @IsNotEmpty() 8 | username: string; 9 | 10 | @IsNotEmpty() 11 | @IsEmail() 12 | email: string; 13 | 14 | createdOn?: Date; 15 | } 16 | -------------------------------------------------------------------------------- /server/src/users/entity/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { TaskEntity } from '@todo/entity/task.entity'; 2 | import { 3 | Entity, 4 | PrimaryGeneratedColumn, 5 | Column, 6 | CreateDateColumn, 7 | OneToMany, 8 | BeforeInsert, 9 | } from 'typeorm'; 10 | import * as bcrypt from 'bcrypt'; 11 | 12 | @Entity('users') 13 | export class UserEntity { 14 | @PrimaryGeneratedColumn('uuid') id: string; 15 | @Column({ type: 'varchar', nullable: false, unique: true }) username: string; 16 | @Column({ type: 'varchar', nullable: false }) password: string; 17 | @Column({ type: 'varchar', nullable: false }) email: string; 18 | @CreateDateColumn() createdOn?: Date; 19 | @CreateDateColumn() updatedOn?: Date; 20 | 21 | @BeforeInsert() 22 | async hashPassword() { 23 | this.password = await bcrypt.hash(this.password, 10); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UsersService } from './users.service'; 3 | import { UserEntity } from './entity/user.entity'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | 6 | @Module({ 7 | imports: [TypeOrmModule.forFeature([UserEntity])], 8 | controllers: [], 9 | providers: [UsersService], 10 | exports: [UsersService], 11 | }) 12 | export class UsersModule {} 13 | -------------------------------------------------------------------------------- /server/src/users/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersService } from './users.service'; 3 | 4 | describe('UsersService', () => { 5 | let service: UsersService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UsersService], 10 | }).compile(); 11 | 12 | service = module.get(UsersService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { UserDto } from './dto/user.dto'; 5 | import { UserEntity } from '@user/entity/user.entity'; 6 | import { toUserDto } from '@shared/mapper'; 7 | import { CreateUserDto } from './dto/user.create.dto'; 8 | import { LoginUserDto } from './dto/user-login.dto'; 9 | import { comparePasswords } from '@shared/utils'; 10 | 11 | @Injectable() 12 | export class UsersService { 13 | constructor( 14 | @InjectRepository(UserEntity) 15 | private readonly userRepo: Repository, 16 | ) {} 17 | 18 | async findOne(options?: object): Promise { 19 | const user = await this.userRepo.findOne(options); 20 | return toUserDto(user); 21 | } 22 | 23 | async findByLogin({ username, password }: LoginUserDto): Promise { 24 | const user = await this.userRepo.findOne({ where: { username } }); 25 | 26 | if (!user) { 27 | throw new HttpException('User not found', HttpStatus.UNAUTHORIZED); 28 | } 29 | 30 | // compare passwords 31 | const areEqual = await comparePasswords(user.password, password); 32 | 33 | if (!areEqual) { 34 | throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); 35 | } 36 | 37 | return toUserDto(user); 38 | } 39 | 40 | async findByPayload({ username }: any): Promise { 41 | return await this.findOne({ where: { username } }); 42 | } 43 | 44 | async create(userDto: CreateUserDto): Promise { 45 | const { username, password, email } = userDto; 46 | 47 | // check if the user exists in the db 48 | const userInDb = await this.userRepo.findOne({ where: { username } }); 49 | if (userInDb) { 50 | throw new HttpException('User already exists', HttpStatus.BAD_REQUEST); 51 | } 52 | 53 | const user: UserEntity = await this.userRepo.create({ 54 | username, 55 | password, 56 | email, 57 | }); 58 | 59 | await this.userRepo.save(user); 60 | 61 | return toUserDto(user); 62 | } 63 | 64 | private _sanitizeUser(user: UserEntity) { 65 | delete user.password; 66 | return user; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /server/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import * as request from 'supertest'; 3 | import { AppModule } from './../src/app.module'; 4 | 5 | describe('AppController (e2e)', () => { 6 | let app; 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule], 11 | }).compile(); 12 | 13 | app = moduleFixture.createNestApplication(); 14 | await app.init(); 15 | }); 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()) 19 | .get('/') 20 | .expect(200) 21 | .expect('Hello World!'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "outDir": "./dist", 7 | "baseUrl": "./", 8 | "removeComments": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "sourceMap": true, 12 | "lib": ["es2015"], 13 | "paths": { 14 | "@todo/*": ["src/todo/*"], 15 | "@shared/*": ["src/shared/*"], 16 | "@user/*": ["src/users/*"] 17 | } 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /server/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /todo-client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /todo-client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /todo-client/README.md: -------------------------------------------------------------------------------- 1 | # TodoClient 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.1. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /todo-client/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "todo-client": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 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/todo-client", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": false, 26 | "assets": ["src/favicon.ico", "src/assets"], 27 | "styles": ["src/styles.scss"], 28 | "scripts": [ 29 | "./node_modules/jquery/dist/jquery.min.js", 30 | "./node_modules/bootstrap/dist/js/bootstrap.min.js" 31 | ] 32 | }, 33 | "configurations": { 34 | "production": { 35 | "fileReplacements": [ 36 | { 37 | "replace": "src/environments/environment.ts", 38 | "with": "src/environments/environment.prod.ts" 39 | } 40 | ], 41 | "optimization": true, 42 | "outputHashing": "all", 43 | "sourceMap": false, 44 | "extractCss": true, 45 | "namedChunks": false, 46 | "aot": true, 47 | "extractLicenses": true, 48 | "vendorChunk": false, 49 | "buildOptimizer": true, 50 | "budgets": [ 51 | { 52 | "type": "initial", 53 | "maximumWarning": "2mb", 54 | "maximumError": "5mb" 55 | } 56 | ] 57 | } 58 | } 59 | }, 60 | "serve": { 61 | "builder": "@angular-devkit/build-angular:dev-server", 62 | "options": { 63 | "browserTarget": "todo-client:build" 64 | }, 65 | "configurations": { 66 | "production": { 67 | "browserTarget": "todo-client:build:production" 68 | } 69 | } 70 | }, 71 | "extract-i18n": { 72 | "builder": "@angular-devkit/build-angular:extract-i18n", 73 | "options": { 74 | "browserTarget": "todo-client:build" 75 | } 76 | }, 77 | "test": { 78 | "builder": "@angular-devkit/build-angular:karma", 79 | "options": { 80 | "main": "src/test.ts", 81 | "polyfills": "src/polyfills.ts", 82 | "tsConfig": "tsconfig.spec.json", 83 | "karmaConfig": "karma.conf.js", 84 | "assets": ["src/favicon.ico", "src/assets"], 85 | "styles": ["src/styles.scss"], 86 | "scripts": [] 87 | } 88 | }, 89 | "lint": { 90 | "builder": "@angular-devkit/build-angular:tslint", 91 | "options": { 92 | "tsConfig": [ 93 | "tsconfig.app.json", 94 | "tsconfig.spec.json", 95 | "e2e/tsconfig.json" 96 | ], 97 | "exclude": ["**/node_modules/**"] 98 | } 99 | }, 100 | "e2e": { 101 | "builder": "@angular-devkit/build-angular:protractor", 102 | "options": { 103 | "protractorConfig": "e2e/protractor.conf.js", 104 | "devServerTarget": "todo-client:serve" 105 | }, 106 | "configurations": { 107 | "production": { 108 | "devServerTarget": "todo-client:serve:production" 109 | } 110 | } 111 | } 112 | } 113 | }, 114 | "auth": { 115 | "projectType": "library", 116 | "root": "projects/auth", 117 | "sourceRoot": "projects/auth/src", 118 | "prefix": "lib", 119 | "architect": { 120 | "build": { 121 | "builder": "@angular-devkit/build-ng-packagr:build", 122 | "options": { 123 | "tsConfig": "projects/auth/tsconfig.lib.json", 124 | "project": "projects/auth/ng-package.json" 125 | } 126 | }, 127 | "test": { 128 | "builder": "@angular-devkit/build-angular:karma", 129 | "options": { 130 | "main": "projects/auth/src/test.ts", 131 | "tsConfig": "projects/auth/tsconfig.spec.json", 132 | "karmaConfig": "projects/auth/karma.conf.js" 133 | } 134 | }, 135 | "lint": { 136 | "builder": "@angular-devkit/build-angular:tslint", 137 | "options": { 138 | "tsConfig": [ 139 | "projects/auth/tsconfig.lib.json", 140 | "projects/auth/tsconfig.spec.json" 141 | ], 142 | "exclude": ["**/node_modules/**"] 143 | } 144 | } 145 | } 146 | }, 147 | "app-common": { 148 | "projectType": "library", 149 | "root": "projects/app-common", 150 | "sourceRoot": "projects/app-common/src", 151 | "prefix": "lib", 152 | "architect": { 153 | "build": { 154 | "builder": "@angular-devkit/build-ng-packagr:build", 155 | "options": { 156 | "tsConfig": "projects/app-common/tsconfig.lib.json", 157 | "project": "projects/app-common/ng-package.json" 158 | } 159 | }, 160 | "test": { 161 | "builder": "@angular-devkit/build-angular:karma", 162 | "options": { 163 | "main": "projects/app-common/src/test.ts", 164 | "tsConfig": "projects/app-common/tsconfig.spec.json", 165 | "karmaConfig": "projects/app-common/karma.conf.js" 166 | } 167 | }, 168 | "lint": { 169 | "builder": "@angular-devkit/build-angular:tslint", 170 | "options": { 171 | "tsConfig": [ 172 | "projects/app-common/tsconfig.lib.json", 173 | "projects/app-common/tsconfig.spec.json" 174 | ], 175 | "exclude": ["**/node_modules/**"] 176 | } 177 | } 178 | } 179 | }, 180 | "todo": { 181 | "projectType": "library", 182 | "root": "projects/todo", 183 | "sourceRoot": "projects/todo/src", 184 | "prefix": "lib", 185 | "architect": { 186 | "build": { 187 | "builder": "@angular-devkit/build-ng-packagr:build", 188 | "options": { 189 | "tsConfig": "projects/todo/tsconfig.lib.json", 190 | "project": "projects/todo/ng-package.json" 191 | } 192 | }, 193 | "test": { 194 | "builder": "@angular-devkit/build-angular:karma", 195 | "options": { 196 | "main": "projects/todo/src/test.ts", 197 | "tsConfig": "projects/todo/tsconfig.spec.json", 198 | "karmaConfig": "projects/todo/karma.conf.js" 199 | } 200 | }, 201 | "lint": { 202 | "builder": "@angular-devkit/build-angular:tslint", 203 | "options": { 204 | "tsConfig": [ 205 | "projects/todo/tsconfig.lib.json", 206 | "projects/todo/tsconfig.spec.json" 207 | ], 208 | "exclude": [ 209 | "**/node_modules/**" 210 | ] 211 | } 212 | } 213 | } 214 | } 215 | }, 216 | "defaultProject": "todo-client" 217 | } 218 | -------------------------------------------------------------------------------- /todo-client/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /todo-client/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /todo-client/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to todo-client!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /todo-client/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /todo-client/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /todo-client/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/todo-client'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /todo-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --proxy-config proxy.conf.json", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~8.1.1", 15 | "@angular/common": "~8.1.1", 16 | "@angular/compiler": "~8.1.1", 17 | "@angular/core": "~8.1.1", 18 | "@angular/forms": "~8.1.1", 19 | "@angular/platform-browser": "~8.1.1", 20 | "@angular/platform-browser-dynamic": "~8.1.1", 21 | "@angular/router": "~8.1.1", 22 | "bootstrap": "^4.3.1", 23 | "jquery": "^3.4.1", 24 | "rxjs": "~6.4.0", 25 | "tslib": "^1.9.0", 26 | "zone.js": "~0.9.1" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.801.1", 30 | "@angular-devkit/build-ng-packagr": "~0.801.3", 31 | "@angular/cli": "~8.1.1", 32 | "@angular/compiler-cli": "~8.1.1", 33 | "@angular/language-service": "~8.1.1", 34 | "@types/jasmine": "~3.3.8", 35 | "@types/jasminewd2": "~2.0.3", 36 | "@types/node": "~8.9.4", 37 | "codelyzer": "^5.0.0", 38 | "jasmine-core": "~3.4.0", 39 | "jasmine-spec-reporter": "~4.2.1", 40 | "karma": "~4.1.0", 41 | "karma-chrome-launcher": "~2.2.0", 42 | "karma-coverage-istanbul-reporter": "~2.0.1", 43 | "karma-jasmine": "~2.0.1", 44 | "karma-jasmine-html-reporter": "^1.4.0", 45 | "ng-packagr": "^5.1.0", 46 | "protractor": "~5.4.0", 47 | "ts-node": "~7.0.0", 48 | "tsickle": "^0.35.0", 49 | "tslint": "~5.15.0", 50 | "typescript": "~3.4.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/README.md: -------------------------------------------------------------------------------- 1 | # AppCommon 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.3. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project app-common` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project app-common`. 8 | > Note: Don't forget to add `--project app-common` or else it will be added to the default project in your `angular.json` file. 9 | 10 | ## Build 11 | 12 | Run `ng build app-common` to build the project. The build artifacts will be stored in the `dist/` directory. 13 | 14 | ## Publishing 15 | 16 | After building your library with `ng build app-common`, go to the dist folder `cd dist/app-common` and run `npm publish`. 17 | 18 | ## Running unit tests 19 | 20 | Run `ng test app-common` to execute the unit tests via [Karma](https://karma-runner.github.io). 21 | 22 | ## Further help 23 | 24 | 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). 25 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/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/app-common'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/app-common", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /todo-client/projects/app-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-common", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^8.1.3", 6 | "@angular/core": "^8.1.3" 7 | } 8 | } -------------------------------------------------------------------------------- /todo-client/projects/app-common/src/lib/action.ts: -------------------------------------------------------------------------------- 1 | export interface DoAction { 2 | type: string; 3 | payload: any; 4 | } 5 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/src/lib/app-common.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | import { RouterModule } from '@angular/router'; 6 | 7 | @NgModule({ 8 | declarations: [], 9 | imports: [ 10 | CommonModule, 11 | FormsModule, 12 | HttpClientModule, 13 | ReactiveFormsModule, 14 | RouterModule 15 | ], 16 | exports: [ 17 | CommonModule, 18 | FormsModule, 19 | HttpClientModule, 20 | ReactiveFormsModule, 21 | RouterModule 22 | ] 23 | }) 24 | export class AppCommonModule {} 25 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of app-common 3 | */ 4 | 5 | export * from './lib/app-common.module'; 6 | export * from './lib/action'; 7 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone'; 4 | import 'zone.js/dist/zone-testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: any; 12 | 13 | // First, initialize the Angular testing environment. 14 | getTestBed().initTestEnvironment( 15 | BrowserDynamicTestingModule, 16 | platformBrowserDynamicTesting() 17 | ); 18 | // Then we find all the tests. 19 | const context = require.context('./', true, /\.spec\.ts$/); 20 | // And load the modules. 21 | context.keys().map(context); 22 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": [ 10 | "dom", 11 | "es2018" 12 | ] 13 | }, 14 | "angularCompilerOptions": { 15 | "annotateForClosureCompiler": true, 16 | "skipTemplateCodegen": true, 17 | "strictMetadataEmit": true, 18 | "fullTemplateTypeCheck": true, 19 | "strictInjectionParameters": true, 20 | "enableResourceInlining": true 21 | }, 22 | "exclude": [ 23 | "src/test.ts", 24 | "**/*.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /todo-client/projects/app-common/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /todo-client/projects/auth/README.md: -------------------------------------------------------------------------------- 1 | # Auth 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.3. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project auth` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project auth`. 8 | > Note: Don't forget to add `--project auth` or else it will be added to the default project in your `angular.json` file. 9 | 10 | ## Build 11 | 12 | Run `ng build auth` to build the project. The build artifacts will be stored in the `dist/` directory. 13 | 14 | ## Publishing 15 | 16 | After building your library with `ng build auth`, go to the dist folder `cd dist/auth` and run `npm publish`. 17 | 18 | ## Running unit tests 19 | 20 | Run `ng test auth` to execute the unit tests via [Karma](https://karma-runner.github.io). 21 | 22 | ## Further help 23 | 24 | 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). 25 | -------------------------------------------------------------------------------- /todo-client/projects/auth/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/auth'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /todo-client/projects/auth/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/auth", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /todo-client/projects/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^8.1.3", 6 | "@angular/core": "^8.1.3" 7 | } 8 | } -------------------------------------------------------------------------------- /todo-client/projects/auth/src/lib/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { 4 | ActivatedRouteSnapshot, 5 | RouterStateSnapshot, 6 | CanActivate, 7 | Router 8 | } from '@angular/router'; 9 | import { AuthService } from './services/auth.service'; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class AuthGuard implements CanActivate { 15 | constructor(private router: Router, private authService: AuthService) {} 16 | 17 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 18 | const currentUser = this.authService.currentUserValue; 19 | if (currentUser) { 20 | // logged in so return true 21 | return true; 22 | } 23 | 24 | // not logged in so redirect to login page with the return url 25 | this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/lib/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { LoginComponent } from './components/login/login.component'; 3 | import { AppCommonModule } from 'projects/app-common/src/public-api'; 4 | 5 | @NgModule({ 6 | declarations: [LoginComponent], 7 | imports: [AppCommonModule], 8 | exports: [LoginComponent] 9 | }) 10 | export class AuthModule {} 11 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/lib/components/login/login.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | justify-content: center; 4 | } 5 | 6 | .alert-danger { 7 | background-color: #f44336; /* Red */ 8 | } 9 | .alert { 10 | padding: 20px; 11 | color: white; 12 | margin-bottom: 15px; 13 | text-align: center; 14 | } 15 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/lib/components/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Login

5 |
6 |
7 | 8 |
9 |
10 |
11 |
12 | 13 | 20 |
21 | Username is required 26 |
27 |
28 |
29 | 30 | 37 |
38 | Password is required 43 |
44 |
45 |
46 | {{ error | json }} 47 |
48 | 49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/lib/components/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | import { first } from 'rxjs/operators'; 5 | import { AuthService } from '../../services/auth.service'; 6 | 7 | @Component({ 8 | templateUrl: 'login.component.html', 9 | styleUrls: ['login.component.css'] 10 | }) 11 | export class LoginComponent implements OnInit { 12 | loginForm: FormGroup; 13 | submitted = false; 14 | returnUrl: string; 15 | error: string; 16 | 17 | constructor( 18 | private formBuilder: FormBuilder, 19 | private route: ActivatedRoute, 20 | private router: Router, 21 | private authService: AuthService 22 | ) {} 23 | 24 | ngOnInit() { 25 | this.loginForm = this.formBuilder.group({ 26 | username: ['bhaidar', Validators.required], 27 | password: ['@dF%^hGb03W~', Validators.required] 28 | }); 29 | 30 | // reset login status 31 | this.authService.logout(); 32 | 33 | // get return url from route parameters or default to '/' 34 | this.returnUrl = this.route.snapshot.queryParams.returnUrl || '/'; 35 | } 36 | 37 | get f() { 38 | return this.loginForm.controls; 39 | } 40 | 41 | onSubmit() { 42 | this.submitted = true; 43 | 44 | // stop here if form is invalid 45 | if (this.loginForm.invalid) { 46 | return; 47 | } 48 | 49 | this.authService 50 | .login(this.f.username.value, this.f.password.value) 51 | .pipe(first()) 52 | .subscribe( 53 | data => { 54 | this.error = ''; 55 | this.router.navigate([this.returnUrl]); 56 | }, 57 | error => { 58 | this.error = error; 59 | } 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/lib/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on 3 | * https://github.com/cornflourblue/angular-7-jwt-authentication-example 4 | */ 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { BehaviorSubject, Observable } from 'rxjs'; 8 | import { HttpClient } from '@angular/common/http'; 9 | import { map } from 'rxjs/operators'; 10 | 11 | export interface ApplicationUser { 12 | accessToken: string; 13 | expiresIn: Date; 14 | username: string; 15 | } 16 | 17 | @Injectable({ 18 | providedIn: 'root' 19 | }) 20 | export class AuthService { 21 | private currentUserSubject: BehaviorSubject; 22 | public currentUser: Observable; 23 | 24 | constructor(private readonly http: HttpClient) { 25 | this.currentUserSubject = new BehaviorSubject( 26 | JSON.parse(localStorage.getItem('currentUser')) 27 | ); 28 | this.currentUser = this.currentUserSubject.asObservable(); 29 | } 30 | 31 | public get currentUserValue(): ApplicationUser { 32 | return this.currentUserSubject.value; 33 | } 34 | 35 | login(username: string, password: string) { 36 | return this.http.post('/auth/login', { username, password }).pipe( 37 | map(user => { 38 | // login successful if there's a jwt token in the response 39 | if (user && user.accessToken) { 40 | // store; user; details; and; jwt; token in local 41 | // storage; to; keep; user; logged in between; page; refreshes; 42 | 43 | localStorage.setItem('currentUser', JSON.stringify(user)); 44 | this.currentUserSubject.next(user); 45 | } 46 | 47 | return user; 48 | }) 49 | ); 50 | } 51 | 52 | logout() { 53 | // remove user from local storage to log user out 54 | localStorage.removeItem('currentUser'); 55 | this.currentUserSubject.next(null); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/lib/services/error.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | HttpInterceptor, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpEvent, 7 | HttpErrorResponse, 8 | HTTP_INTERCEPTORS 9 | } from '@angular/common/http'; 10 | import { Observable, throwError } from 'rxjs'; 11 | import { catchError } from 'rxjs/operators'; 12 | import { AuthService } from './auth.service'; 13 | 14 | @Injectable() 15 | export class ErrorInterceptor implements HttpInterceptor { 16 | constructor(private authService: AuthService) {} 17 | 18 | intercept( 19 | request: HttpRequest, 20 | next: HttpHandler 21 | ): Observable> { 22 | return next.handle(request).pipe( 23 | catchError((err: HttpErrorResponse) => { 24 | if (err.status === 401 && !window.location.href.includes('/login')) { 25 | // auto logout if 401 response returned from api 26 | this.authService.logout(); 27 | location.reload(); 28 | } 29 | 30 | const error = err.error.error || err.error.message || err.statusText; 31 | 32 | alert(error); 33 | 34 | return throwError(error); 35 | }) 36 | ); 37 | } 38 | } 39 | 40 | export const errorInterceptorProvider = { 41 | provide: HTTP_INTERCEPTORS, 42 | useClass: ErrorInterceptor, 43 | multi: true 44 | }; 45 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/lib/services/jwt-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpInterceptor, 3 | HttpRequest, 4 | HttpHandler, 5 | HttpEvent, 6 | HTTP_INTERCEPTORS 7 | } from '@angular/common/http'; 8 | import { Observable } from 'rxjs'; 9 | import { Injectable } from '@angular/core'; 10 | import { AuthService } from './auth.service'; 11 | 12 | @Injectable() 13 | export class JwtInterceptor implements HttpInterceptor { 14 | constructor(private authService: AuthService) {} 15 | 16 | intercept( 17 | request: HttpRequest, 18 | next: HttpHandler 19 | ): Observable> { 20 | // add authorization header with jwt token if available 21 | const currentUser = this.authService.currentUserValue; 22 | if (currentUser && currentUser.accessToken) { 23 | request = request.clone({ 24 | setHeaders: { 25 | Authorization: `Bearer ${currentUser.accessToken}` 26 | } 27 | }); 28 | } 29 | 30 | return next.handle(request); 31 | } 32 | } 33 | 34 | export const jwtInterceptorProvider = { 35 | provide: HTTP_INTERCEPTORS, 36 | useClass: JwtInterceptor, 37 | multi: true 38 | }; 39 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of auth 3 | */ 4 | 5 | export * from './lib/services/auth.service'; 6 | export * from './lib/components/login/login.component'; 7 | export * from './lib/services/jwt-interceptor'; 8 | export * from './lib/auth.module'; 9 | -------------------------------------------------------------------------------- /todo-client/projects/auth/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone'; 4 | import 'zone.js/dist/zone-testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: any; 12 | 13 | // First, initialize the Angular testing environment. 14 | getTestBed().initTestEnvironment( 15 | BrowserDynamicTestingModule, 16 | platformBrowserDynamicTesting() 17 | ); 18 | // Then we find all the tests. 19 | const context = require.context('./', true, /\.spec\.ts$/); 20 | // And load the modules. 21 | context.keys().map(context); 22 | -------------------------------------------------------------------------------- /todo-client/projects/auth/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": [ 10 | "dom", 11 | "es2018" 12 | ] 13 | }, 14 | "angularCompilerOptions": { 15 | "annotateForClosureCompiler": true, 16 | "skipTemplateCodegen": true, 17 | "strictMetadataEmit": true, 18 | "fullTemplateTypeCheck": true, 19 | "strictInjectionParameters": true, 20 | "enableResourceInlining": true 21 | }, 22 | "exclude": [ 23 | "src/test.ts", 24 | "**/*.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /todo-client/projects/auth/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /todo-client/projects/auth/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /todo-client/projects/todo/README.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.3. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project todo` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project todo`. 8 | > Note: Don't forget to add `--project todo` or else it will be added to the default project in your `angular.json` file. 9 | 10 | ## Build 11 | 12 | Run `ng build todo` to build the project. The build artifacts will be stored in the `dist/` directory. 13 | 14 | ## Publishing 15 | 16 | After building your library with `ng build todo`, go to the dist folder `cd dist/todo` and run `npm publish`. 17 | 18 | ## Running unit tests 19 | 20 | Run `ng test todo` to execute the unit tests via [Karma](https://karma-runner.github.io). 21 | 22 | ## Further help 23 | 24 | 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). 25 | -------------------------------------------------------------------------------- /todo-client/projects/todo/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/todo'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /todo-client/projects/todo/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/todo", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /todo-client/projects/todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^8.1.3", 6 | "@angular/core": "^8.1.3" 7 | } 8 | } -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/components/task-create/task-create.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter, Output } from '@angular/core'; 2 | import { DoAction } from 'projects/app-common/src/public-api'; 3 | 4 | @Component({ 5 | selector: 'lib-task-create', 6 | template: ` 7 |
8 |
9 | 15 |
16 |
17 | ` 18 | }) 19 | export class TaskCreateComponent implements OnInit { 20 | public task = ''; 21 | 22 | @Output() 23 | public action: EventEmitter = new EventEmitter(); 24 | 25 | constructor() {} 26 | 27 | ngOnInit() {} 28 | 29 | public OnEnter() { 30 | this.action.emit({ type: 'add-task', payload: this.task }); 31 | this.task = ''; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/components/task-list/task-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { DoAction } from 'projects/app-common/src/public-api'; 3 | import { Task } from '../../models/task.model'; 4 | 5 | @Component({ 6 | selector: 'lib-task-list', 7 | template: ` 8 |

No tasks yet!

9 | 10 |
11 |
15 |
16 | 23 |
24 |
25 |
({{ i + 1 }}) {{ task?.name }}
26 |
27 |
28 |
29 |
30 | `, 31 | styles: [ 32 | ` 33 | .tasks { 34 | display: flex; 35 | justify-content: center; 36 | } 37 | 38 | .tasks .task { 39 | flex-grow: 1; 40 | flex-shrink: 0; 41 | } 42 | 43 | .tasks .action { 44 | margin-right: 5px; 45 | } 46 | ` 47 | ] 48 | }) 49 | export class TaskListComponent implements OnInit { 50 | @Input() 51 | public tasks: Task[]; 52 | 53 | @Output() 54 | public action: EventEmitter = new EventEmitter(); 55 | 56 | constructor() {} 57 | 58 | ngOnInit() {} 59 | 60 | public trackByFn(index: number, item: Task) { 61 | return index; 62 | } 63 | 64 | public doAction(task: Task): void { 65 | this.action.emit({ type: 'delete-task', payload: task }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/components/task.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable, BehaviorSubject, combineLatest, Subject } from 'rxjs'; 3 | import { Task } from '../models/task.model'; 4 | import { switchMap, startWith, tap } from 'rxjs/operators'; 5 | import { DoAction } from 'projects/app-common/src/lib/action'; 6 | import { TaskService } from '../services/task.service'; 7 | import { ActivatedRoute, Params } from '@angular/router'; 8 | 9 | @Component({ 10 | selector: 'lib-task', 11 | template: ` 12 | 13 | 17 | ` 18 | }) 19 | export class TaskComponent implements OnInit { 20 | constructor( 21 | private readonly activeRoute: ActivatedRoute, 22 | private readonly taskService: TaskService 23 | ) {} 24 | public tasks$: Observable; 25 | private refresh$ = new BehaviorSubject(''); 26 | private activeRoute$: Observable; 27 | private todoId = ''; 28 | 29 | ngOnInit() { 30 | this.activeRoute$ = this.activeRoute.params; 31 | 32 | this.tasks$ = combineLatest(this.activeRoute$, this.refresh$).pipe( 33 | tap(([param, _]) => (this.todoId = param.id)), 34 | switchMap(([param, _]) => this.taskService.findAll(param.id)) 35 | ); 36 | } 37 | 38 | public doAction({ type, payload }: DoAction): void { 39 | switch (type) { 40 | case 'add-task': 41 | this.createTask(payload); 42 | break; 43 | case 'delete-task': 44 | this.deleteTask(payload); 45 | break; 46 | default: 47 | console.log('Unknown action type'); 48 | } 49 | } 50 | 51 | private createTask(task: string): void { 52 | this.taskService 53 | .create(this.todoId, { name: task }) 54 | .subscribe(() => this.refresh$.next('')); 55 | } 56 | 57 | private deleteTask(task: Task): void { 58 | if (confirm('Are you sure you want to delete this item?')) { 59 | this.taskService.delete(task.id).subscribe(() => this.refresh$.next('')); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/components/todo-create/todo-create.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter, Output } from '@angular/core'; 2 | import { DoAction } from 'projects/app-common/src/public-api'; 3 | 4 | @Component({ 5 | selector: 'lib-todo-create', 6 | template: ` 7 |
8 |
9 | 15 |
16 |
17 | ` 18 | }) 19 | export class TodoCreateComponent implements OnInit { 20 | public todo = ''; 21 | 22 | @Output() 23 | public action: EventEmitter = new EventEmitter(); 24 | 25 | constructor() {} 26 | 27 | ngOnInit() {} 28 | 29 | public OnEnter() { 30 | this.action.emit({ type: 'add-todo', payload: this.todo }); 31 | this.todo = ''; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/components/todo-list/todo-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { Todo } from '../../models/todo.model'; 3 | import { DoAction } from 'projects/app-common/src/public-api'; 4 | 5 | @Component({ 6 | selector: 'lib-todo-list', 7 | template: ` 8 |
No todos yet!
9 | 10 |
11 |
15 |
16 | 23 |
24 | 34 |
35 |
36 |
37 | `, 38 | styles: [ 39 | ` 40 | .todos { 41 | display: flex; 42 | justify-content: center; 43 | } 44 | 45 | .todos .todo { 46 | flex-grow: 1; 47 | flex-shrink: 0; 48 | max-width: 90%; 49 | } 50 | 51 | .todos .action { 52 | margin-right: 5px; 53 | } 54 | ` 55 | ] 56 | }) 57 | export class TodoListComponent implements OnInit { 58 | @Input() 59 | public todos: Todo[]; 60 | 61 | @Output() 62 | public action: EventEmitter = new EventEmitter(); 63 | 64 | constructor() {} 65 | 66 | ngOnInit() {} 67 | 68 | public trackByFn(index: number, item: Todo) { 69 | return index; 70 | } 71 | 72 | public doAction(todo: Todo): void { 73 | this.action.emit({ type: 'delete-todo', payload: todo }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/components/todo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable, BehaviorSubject } from 'rxjs'; 3 | import { switchMap, tap } from 'rxjs/operators'; 4 | import { DoAction } from 'projects/app-common/src/public-api'; 5 | import { Todo } from '../models/todo.model'; 6 | import { TodoService } from '../services/todo.service'; 7 | import { Router } from '@angular/router'; 8 | 9 | @Component({ 10 | selector: 'lib-todo', 11 | template: ` 12 | 13 | 17 | ` 18 | }) 19 | export class TodoComponent implements OnInit { 20 | public todos$: Observable; 21 | private refresh$ = new BehaviorSubject(''); 22 | 23 | constructor( 24 | private readonly router: Router, 25 | private readonly todoService: TodoService 26 | ) {} 27 | 28 | ngOnInit() { 29 | this.todos$ = this.refresh$.pipe( 30 | switchMap(() => this.todoService.findAll()) 31 | ); 32 | } 33 | 34 | public doAction({ type, payload }: DoAction): void { 35 | switch (type) { 36 | case 'add-todo': 37 | this.createTodo(payload); 38 | break; 39 | case 'delete-todo': 40 | this.deleteTodo(payload); 41 | break; 42 | default: 43 | console.log('Unknown action type'); 44 | } 45 | } 46 | 47 | private createTodo(todo: string): void { 48 | this.todoService 49 | .create({ name: todo }) 50 | .subscribe(() => this.refresh$.next('')); 51 | } 52 | 53 | private deleteTodo(todo: Todo): void { 54 | if (confirm('Are you sure you want to delete this item?')) { 55 | this.todoService.delete(todo.id).subscribe(() => { 56 | this.refresh$.next(''); 57 | this.router.navigate(['/todo']); 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/models/task.model.ts: -------------------------------------------------------------------------------- 1 | export interface Task { 2 | id?: string; 3 | name: string; 4 | createdOn?: Date; 5 | } 6 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/models/todo.model.ts: -------------------------------------------------------------------------------- 1 | export interface Todo { 2 | id?: string; 3 | name: string; 4 | createdOn?: Date; 5 | } 6 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/services/task.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | HttpHeaders, 4 | HttpClient, 5 | HttpErrorResponse 6 | } from '@angular/common/http'; 7 | import { Observable, throwError } from 'rxjs'; 8 | import { catchError, map } from 'rxjs/operators'; 9 | import { Task } from '../models/task.model'; 10 | 11 | const httpOptions = { 12 | headers: new HttpHeaders({ 13 | 'Content-Type': 'application/json', 14 | Authorization: 'my-auth-token' 15 | }) 16 | }; 17 | 18 | @Injectable({ 19 | providedIn: 'root' 20 | }) 21 | export class TaskService { 22 | private baseUrl = 'api/tasks'; // URL to web api 23 | 24 | constructor(private readonly http: HttpClient) {} 25 | 26 | public create(todoId: string, task: Task): Observable { 27 | return this.http 28 | .post(`${this.baseUrl}/todo/${todoId}`, task, httpOptions) 29 | .pipe(catchError(this.handleError)); 30 | } 31 | 32 | public findAll(todoId: string): Observable { 33 | return this.http 34 | .get(`${this.baseUrl}/todo/${todoId}`, httpOptions) 35 | .pipe( 36 | map((results: any) => results.tasks), 37 | catchError(this.handleError) 38 | ); 39 | } 40 | 41 | public delete(id: string): Observable<{}> { 42 | const url = `${this.baseUrl}/${id}`; // DELETE api/tasks/42-5c-... 43 | return this.http 44 | .delete(url, httpOptions) 45 | .pipe(catchError(this.handleError)); 46 | } 47 | 48 | private handleError(error: HttpErrorResponse) { 49 | if (error.error instanceof ErrorEvent) { 50 | // A client-side or network error occured. Handle it accordingly 51 | console.error('An error occured:', error.error.message); 52 | } else { 53 | // The backend returned an unsuccessful respone code. 54 | // The response body may contain clues as to what was wrong 55 | console.log( 56 | `Backend returned code ${error.status}, body was: ${error.status}` 57 | ); 58 | } 59 | 60 | // return an observable wuth a user-facing error message 61 | return throwError('Something bad happened; please try again later.'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/services/todo.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | HttpHeaders, 4 | HttpClient, 5 | HttpErrorResponse 6 | } from '@angular/common/http'; 7 | import { Observable, throwError } from 'rxjs'; 8 | import { Todo } from '../models/todo.model'; 9 | import { catchError, map } from 'rxjs/operators'; 10 | 11 | const httpOptions = { 12 | headers: new HttpHeaders({ 13 | 'Content-Type': 'application/json' 14 | }) 15 | }; 16 | 17 | @Injectable({ 18 | providedIn: 'root' 19 | }) 20 | export class TodoService { 21 | private baseUrl = 'api/todos'; // URL to web api 22 | 23 | constructor(private readonly http: HttpClient) {} 24 | 25 | public create(todo: Todo): Observable { 26 | return this.http 27 | .post(this.baseUrl, todo, httpOptions) 28 | .pipe(catchError(this.handleError)); 29 | } 30 | 31 | public findAll(): Observable { 32 | return this.http.get(this.baseUrl, httpOptions).pipe( 33 | map((results: any) => results.todos), 34 | catchError(this.handleError) 35 | ); 36 | } 37 | 38 | public delete(id: string): Observable<{}> { 39 | const url = `${this.baseUrl}/${id}`; // DELETE api/todos/42-5c-... 40 | return this.http 41 | .delete(url, httpOptions) 42 | .pipe(catchError(this.handleError)); 43 | } 44 | 45 | private handleError(error: HttpErrorResponse) { 46 | if (error.error instanceof ErrorEvent) { 47 | // A client-side or network error occured. Handle it accordingly 48 | console.error('An error occured:', error.error.message); 49 | } else { 50 | // The backend returned an unsuccessful respone code. 51 | // The response body may contain clues as to what was wrong 52 | console.log( 53 | `Backend returned code ${error.status}, body was: ${error.status}` 54 | ); 55 | } 56 | 57 | // return an observable wuth a user-facing error message 58 | return throwError('Something bad happened; please try again later.'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/todo-home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'lib-todo-home', 5 | template: ` 6 |
7 |
8 |
9 |
10 |
Todo Lists
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
24 |
Tasks
25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 | `, 35 | styles: [ 36 | ` 37 | .border-3 { 38 | border-width: 3px !important; 39 | } 40 | ` 41 | ] 42 | }) 43 | export class TodoHomeComponent implements OnInit { 44 | constructor() {} 45 | 46 | ngOnInit() {} 47 | } 48 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/todo.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { AppCommonModule } from 'projects/app-common/src/public-api'; 3 | import { TodoListComponent } from './components/todo-list/todo-list.component'; 4 | import { TodoCreateComponent } from './components/todo-create/todo-create.component'; 5 | import { TaskComponent } from './components/task.component'; 6 | import { TodoHomeComponent } from './todo-home.component'; 7 | import { TodoComponent } from './components/todo.component'; 8 | import { TaskCreateComponent } from './components/task-create/task-create.component'; 9 | import { TaskListComponent } from './components/task-list/task-list.component'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | TodoComponent, 14 | TodoCreateComponent, 15 | TodoListComponent, 16 | TaskComponent, 17 | TodoHomeComponent, 18 | TaskCreateComponent, 19 | TaskListComponent 20 | ], 21 | imports: [AppCommonModule], 22 | exports: [TodoHomeComponent, TaskComponent] 23 | }) 24 | export class TodoModule {} 25 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/lib/todo.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { TodoService } from './todo.service'; 4 | 5 | describe('TodoService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: TodoService = TestBed.get(TodoService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of todo 3 | */ 4 | 5 | export * from './lib/todo-home.component'; 6 | export * from './lib/components/task.component'; 7 | export * from './lib/todo.module'; 8 | -------------------------------------------------------------------------------- /todo-client/projects/todo/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone'; 4 | import 'zone.js/dist/zone-testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: any; 12 | 13 | // First, initialize the Angular testing environment. 14 | getTestBed().initTestEnvironment( 15 | BrowserDynamicTestingModule, 16 | platformBrowserDynamicTesting() 17 | ); 18 | // Then we find all the tests. 19 | const context = require.context('./', true, /\.spec\.ts$/); 20 | // And load the modules. 21 | context.keys().map(context); 22 | -------------------------------------------------------------------------------- /todo-client/projects/todo/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": [ 10 | "dom", 11 | "es2018" 12 | ] 13 | }, 14 | "angularCompilerOptions": { 15 | "annotateForClosureCompiler": true, 16 | "skipTemplateCodegen": true, 17 | "strictMetadataEmit": true, 18 | "fullTemplateTypeCheck": true, 19 | "strictInjectionParameters": true, 20 | "enableResourceInlining": true 21 | }, 22 | "exclude": [ 23 | "src/test.ts", 24 | "**/*.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /todo-client/projects/todo/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /todo-client/projects/todo/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /todo-client/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:4000", 4 | "secure": false 5 | }, 6 | "/auth": { 7 | "target": "http://localhost:4000/auth/", 8 | "secure": false, 9 | "pathRewrite": { 10 | "^/auth": "" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /todo-client/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { LoginComponent } from 'projects/auth/src/public-api'; 4 | import { MasterComponent } from './shared/master/master.component'; 5 | import { HomeComponent } from './shared/home/home.component'; 6 | import { AuthGuard } from 'projects/auth/src/lib/auth.guard'; 7 | import { TodoHomeComponent, TaskComponent } from 'projects/todo/src/public-api'; 8 | 9 | const routes: Routes = [ 10 | { 11 | path: '', 12 | component: MasterComponent, 13 | canActivate: [AuthGuard], 14 | children: [ 15 | { 16 | path: '', 17 | component: HomeComponent 18 | }, 19 | { 20 | path: 'todo', 21 | component: TodoHomeComponent, 22 | children: [ 23 | { 24 | path: 'tasks/:id', 25 | component: TaskComponent 26 | } 27 | ] 28 | } 29 | ] 30 | }, 31 | { 32 | path: '', 33 | children: [ 34 | { 35 | path: 'login', 36 | component: LoginComponent 37 | } 38 | ] 39 | }, 40 | { path: '**', redirectTo: '' } 41 | ]; 42 | 43 | @NgModule({ 44 | imports: [RouterModule.forRoot(routes)], 45 | exports: [RouterModule] 46 | }) 47 | export class AppRoutingModule {} 48 | -------------------------------------------------------------------------------- /todo-client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /todo-client/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhaidar/nestjs-todo-app/9b9c01f4f0a9d05203f85bada4cd02ef9f5cd548/todo-client/src/app/app.component.scss -------------------------------------------------------------------------------- /todo-client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } 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 | 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.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'todo-client'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('todo-client'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to todo-client!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /todo-client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'todo-client'; 10 | } 11 | -------------------------------------------------------------------------------- /todo-client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { 7 | AuthModule, 8 | jwtInterceptorProvider 9 | } from 'projects/auth/src/public-api'; 10 | import { AppCommonModule } from 'projects/app-common/src/public-api'; 11 | import { MasterComponent } from './shared/master/master.component'; 12 | import { HomeComponent } from './shared/home/home.component'; 13 | import { TodoModule } from 'projects/todo/src/public-api'; 14 | import { errorInterceptorProvider } from 'projects/auth/src/lib/services/error.interceptor'; 15 | 16 | @NgModule({ 17 | declarations: [AppComponent, MasterComponent, HomeComponent], 18 | imports: [ 19 | BrowserModule, 20 | AppRoutingModule, 21 | AppCommonModule, 22 | AuthModule, 23 | TodoModule 24 | ], 25 | providers: [jwtInterceptorProvider, errorInterceptorProvider], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule {} 29 | -------------------------------------------------------------------------------- /todo-client/src/app/shared/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | template: ` 6 |
7 |
8 |

Welcome to Todozz App!

9 | 10 |

Here you can manage your Todo Lists in a breeze!

11 |
12 |
13 | ` 14 | }) 15 | export class HomeComponent implements OnInit { 16 | constructor() {} 17 | 18 | ngOnInit() {} 19 | } 20 | -------------------------------------------------------------------------------- /todo-client/src/app/shared/master/master.component.html: -------------------------------------------------------------------------------- 1 | 34 |
35 |
36 | 37 |
38 |
39 | -------------------------------------------------------------------------------- /todo-client/src/app/shared/master/master.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AuthService } from 'projects/auth/src/public-api'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-master', 7 | template: ` 8 | 52 |
53 |
54 | 55 |
56 |
57 | ` 58 | }) 59 | export class MasterComponent implements OnInit { 60 | public loggedIn = false; 61 | 62 | constructor( 63 | private readonly authService: AuthService, 64 | private readonly router: Router 65 | ) {} 66 | 67 | ngOnInit() { 68 | this.loggedIn = !!this.authService.currentUserValue; 69 | } 70 | 71 | public logout(): void { 72 | this.authService.logout(); 73 | this.router.navigate(['/login']); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /todo-client/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhaidar/nestjs-todo-app/9b9c01f4f0a9d05203f85bada4cd02ef9f5cd548/todo-client/src/assets/.gitkeep -------------------------------------------------------------------------------- /todo-client/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /todo-client/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /todo-client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhaidar/nestjs-todo-app/9b9c01f4f0a9d05203f85bada4cd02ef9f5cd548/todo-client/src/favicon.ico -------------------------------------------------------------------------------- /todo-client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TodoClient 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /todo-client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /todo-client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /todo-client/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import '~bootstrap/dist/css/bootstrap.min.css'; -------------------------------------------------------------------------------- /todo-client/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /todo-client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "include": [ 8 | "src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "src/test.ts", 12 | "src/**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /todo-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "auth": [ 23 | "dist/auth" 24 | ], 25 | "auth/*": [ 26 | "dist/auth/*" 27 | ], 28 | "common": [ 29 | "dist/common", 30 | "dist/common", 31 | "dist/common" 32 | ], 33 | "common/*": [ 34 | "dist/common/*", 35 | "dist/common/*", 36 | "dist/common/*" 37 | ], 38 | "app-common": [ 39 | "dist/app-common" 40 | ], 41 | "app-common/*": [ 42 | "dist/app-common/*" 43 | ], 44 | "todo": [ 45 | "dist/todo" 46 | ], 47 | "todo/*": [ 48 | "dist/todo/*" 49 | ] 50 | } 51 | }, 52 | "angularCompilerOptions": { 53 | "fullTemplateTypeCheck": true, 54 | "strictInjectionParameters": true 55 | } 56 | } -------------------------------------------------------------------------------- /todo-client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 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 | -------------------------------------------------------------------------------- /todo-client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended"], 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [true, "attribute", "app", "camelCase"], 13 | "component-selector": [true, "element", "app", "kebab-case"], 14 | "import-blacklist": [true, "rxjs/Rx"], 15 | "indent": [true, "tabs", 2], 16 | "interface-name": false, 17 | "max-classes-per-file": false, 18 | "max-line-length": [true, 120], 19 | "member-access": false, 20 | "member-ordering": [ 21 | true, 22 | { 23 | "order": [ 24 | "static-field", 25 | "instance-field", 26 | "static-method", 27 | "instance-method" 28 | ] 29 | } 30 | ], 31 | "no-consecutive-blank-lines": false, 32 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 33 | "no-empty": false, 34 | "no-inferrable-types": [true, "ignore-params"], 35 | "no-non-null-assertion": true, 36 | "no-redundant-jsdoc": true, 37 | "no-switch-case-fall-through": true, 38 | "no-use-before-declare": true, 39 | "no-var-requires": false, 40 | "object-literal-key-quotes": [true, "as-needed"], 41 | "object-literal-sort-keys": false, 42 | "ordered-imports": false, 43 | "quotemark": [true, "single"], 44 | "trailing-comma": false, 45 | "no-conflicting-lifecycle": true, 46 | "no-host-metadata-property": true, 47 | "no-input-rename": true, 48 | "no-inputs-metadata-property": true, 49 | "no-output-native": true, 50 | "no-output-on-prefix": true, 51 | "no-output-rename": true, 52 | "no-outputs-metadata-property": true, 53 | "template-banana-in-box": true, 54 | "template-no-negated-async": true, 55 | "use-lifecycle-interface": true, 56 | "use-pipe-transform-interface": true 57 | }, 58 | "rulesDirectory": ["codelyzer"] 59 | } 60 | --------------------------------------------------------------------------------