Repository: bhaidar/nestjs-todo-app Branch: master Commit: 9b9c01f4f0a9 Files: 149 Total size: 108.3 KB Directory structure: gitextract_f_7atx7c/ ├── .gitignore ├── .vscode/ │ └── launch.json ├── README.md ├── server/ │ ├── .gitignore │ ├── .prettierrc │ ├── 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 └── 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.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 │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/node,windows,visualstudiocode # Edit at https://www.gitignore.io/?templates=node,windows,visualstudiocode ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ ### VisualStudioCode ### .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ### VisualStudioCode Patch ### # Ignore all local history of files .history ### Windows ### # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # End of https://www.gitignore.io/api/node,windows,visualstudiocode dist/ .DS_Store ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch Program", "program": "${workspaceFolder}/server/src/auth/auth.module.ts", "outFiles": [ "${workspaceFolder}/**/*.js" ] } ] } ================================================ FILE: README.md ================================================ # nestjs-todo-app A full stack application written in Nest.js, Angular and PostgreSQL. ================================================ FILE: server/.gitignore ================================================ .idea/ .vscode/ .DS_Store node_modules/ build/ tmp/ temp/ ================================================ FILE: server/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: server/README.md ================================================

Nest Logo

[travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master [travis-url]: https://travis-ci.org/nestjs/nest [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux [linux-url]: https://travis-ci.org/nestjs/nest

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

NPM Version Package License NPM Downloads Travis Linux Coverage Gitter Backers on Open Collective Sponsors on Open Collective

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

Login

Username is required
Password is required
{{ error | json }}
================================================ FILE: todo-client/projects/auth/src/lib/components/login/login.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { first } from 'rxjs/operators'; import { AuthService } from '../../services/auth.service'; @Component({ templateUrl: 'login.component.html', styleUrls: ['login.component.css'] }) export class LoginComponent implements OnInit { loginForm: FormGroup; submitted = false; returnUrl: string; error: string; constructor( private formBuilder: FormBuilder, private route: ActivatedRoute, private router: Router, private authService: AuthService ) {} ngOnInit() { this.loginForm = this.formBuilder.group({ username: ['bhaidar', Validators.required], password: ['@dF%^hGb03W~', Validators.required] }); // reset login status this.authService.logout(); // get return url from route parameters or default to '/' this.returnUrl = this.route.snapshot.queryParams.returnUrl || '/'; } get f() { return this.loginForm.controls; } onSubmit() { this.submitted = true; // stop here if form is invalid if (this.loginForm.invalid) { return; } this.authService .login(this.f.username.value, this.f.password.value) .pipe(first()) .subscribe( data => { this.error = ''; this.router.navigate([this.returnUrl]); }, error => { this.error = error; } ); } } ================================================ FILE: todo-client/projects/auth/src/lib/services/auth.service.ts ================================================ /** * Based on * https://github.com/cornflourblue/angular-7-jwt-authentication-example */ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { map } from 'rxjs/operators'; export interface ApplicationUser { accessToken: string; expiresIn: Date; username: string; } @Injectable({ providedIn: 'root' }) export class AuthService { private currentUserSubject: BehaviorSubject; public currentUser: Observable; constructor(private readonly http: HttpClient) { this.currentUserSubject = new BehaviorSubject( JSON.parse(localStorage.getItem('currentUser')) ); this.currentUser = this.currentUserSubject.asObservable(); } public get currentUserValue(): ApplicationUser { return this.currentUserSubject.value; } login(username: string, password: string) { return this.http.post('/auth/login', { username, password }).pipe( map(user => { // login successful if there's a jwt token in the response if (user && user.accessToken) { // store; user; details; and; jwt; token in local // storage; to; keep; user; logged in between; page; refreshes; localStorage.setItem('currentUser', JSON.stringify(user)); this.currentUserSubject.next(user); } return user; }) ); } logout() { // remove user from local storage to log user out localStorage.removeItem('currentUser'); this.currentUserSubject.next(null); } } ================================================ FILE: todo-client/projects/auth/src/lib/services/error.interceptor.ts ================================================ import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { AuthService } from './auth.service'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { constructor(private authService: AuthService) {} intercept( request: HttpRequest, next: HttpHandler ): Observable> { return next.handle(request).pipe( catchError((err: HttpErrorResponse) => { if (err.status === 401 && !window.location.href.includes('/login')) { // auto logout if 401 response returned from api this.authService.logout(); location.reload(); } const error = err.error.error || err.error.message || err.statusText; alert(error); return throwError(error); }) ); } } export const errorInterceptorProvider = { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }; ================================================ FILE: todo-client/projects/auth/src/lib/services/jwt-interceptor.ts ================================================ import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HTTP_INTERCEPTORS } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Injectable } from '@angular/core'; import { AuthService } from './auth.service'; @Injectable() export class JwtInterceptor implements HttpInterceptor { constructor(private authService: AuthService) {} intercept( request: HttpRequest, next: HttpHandler ): Observable> { // add authorization header with jwt token if available const currentUser = this.authService.currentUserValue; if (currentUser && currentUser.accessToken) { request = request.clone({ setHeaders: { Authorization: `Bearer ${currentUser.accessToken}` } }); } return next.handle(request); } } export const jwtInterceptorProvider = { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }; ================================================ FILE: todo-client/projects/auth/src/public-api.ts ================================================ /* * Public API Surface of auth */ export * from './lib/services/auth.service'; export * from './lib/components/login/login.component'; export * from './lib/services/jwt-interceptor'; export * from './lib/auth.module'; ================================================ FILE: todo-client/projects/auth/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: todo-client/projects/auth/tsconfig.lib.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/lib", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": [ "dom", "es2018" ] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": [ "src/test.ts", "**/*.spec.ts" ] } ================================================ FILE: todo-client/projects/auth/tsconfig.spec.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/spec", "types": [ "jasmine", "node" ] }, "files": [ "src/test.ts" ], "include": [ "**/*.spec.ts", "**/*.d.ts" ] } ================================================ FILE: todo-client/projects/auth/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [ true, "attribute", "lib", "camelCase" ], "component-selector": [ true, "element", "lib", "kebab-case" ] } } ================================================ FILE: todo-client/projects/todo/README.md ================================================ # Todo This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.3. ## Code scaffolding 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`. > Note: Don't forget to add `--project todo` or else it will be added to the default project in your `angular.json` file. ## Build Run `ng build todo` to build the project. The build artifacts will be stored in the `dist/` directory. ## Publishing After building your library with `ng build todo`, go to the dist folder `cd dist/todo` and run `npm publish`. ## Running unit tests Run `ng test todo` to execute the unit tests via [Karma](https://karma-runner.github.io). ## Further help 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). ================================================ FILE: todo-client/projects/todo/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '../../coverage/todo'), reports: ['html', 'lcovonly', 'text-summary'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false, restartOnFileChange: true }); }; ================================================ FILE: todo-client/projects/todo/ng-package.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/todo", "lib": { "entryFile": "src/public-api.ts" } } ================================================ FILE: todo-client/projects/todo/package.json ================================================ { "name": "todo", "version": "0.0.1", "peerDependencies": { "@angular/common": "^8.1.3", "@angular/core": "^8.1.3" } } ================================================ FILE: todo-client/projects/todo/src/lib/components/task-create/task-create.component.ts ================================================ import { Component, OnInit, EventEmitter, Output } from '@angular/core'; import { DoAction } from 'projects/app-common/src/public-api'; @Component({ selector: 'lib-task-create', template: `
` }) export class TaskCreateComponent implements OnInit { public task = ''; @Output() public action: EventEmitter = new EventEmitter(); constructor() {} ngOnInit() {} public OnEnter() { this.action.emit({ type: 'add-task', payload: this.task }); this.task = ''; } } ================================================ FILE: todo-client/projects/todo/src/lib/components/task-list/task-list.component.ts ================================================ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { DoAction } from 'projects/app-common/src/public-api'; import { Task } from '../../models/task.model'; @Component({ selector: 'lib-task-list', template: `

No tasks yet!

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

Welcome to Todozz App!

Here you can manage your Todo Lists in a breeze!

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