├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── app ├── .editorconfig ├── .gitignore ├── env │ └── .env.dev ├── jest.config.js ├── ormconfig.json ├── package.json ├── src │ ├── Application │ │ ├── application │ │ │ ├── Readme.md │ │ │ └── auth │ │ │ │ ├── login │ │ │ │ ├── login.interface.ts │ │ │ │ └── login.service.ts │ │ │ │ └── register │ │ │ │ ├── register.interface.ts │ │ │ │ └── register.service.ts │ │ ├── domain │ │ │ ├── dtos │ │ │ │ ├── index.ts │ │ │ │ └── user.dto.ts │ │ │ ├── models │ │ │ │ └── user.model.ts │ │ │ └── repositories │ │ │ │ ├── iUser.repository.ts │ │ │ │ └── index.ts │ │ ├── infrastructure │ │ │ ├── authentication │ │ │ │ └── jwt │ │ │ │ │ ├── jwt.interface.ts │ │ │ │ │ └── jwt.service.ts │ │ │ ├── logger │ │ │ │ ├── index.ts │ │ │ │ └── log4js │ │ │ │ │ ├── config.ts │ │ │ │ │ └── index.ts │ │ │ ├── persistence │ │ │ │ └── typeorm │ │ │ │ │ ├── config │ │ │ │ │ └── connectionService.ts │ │ │ │ │ ├── entities │ │ │ │ │ ├── index.ts │ │ │ │ │ └── user.entity.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── migrations │ │ │ │ │ ├── .gitkeep │ │ │ │ │ └── 1606925285524-add_users_table.ts │ │ │ │ │ └── repositories │ │ │ │ │ ├── index.ts │ │ │ │ │ └── user.repository.ts │ │ │ └── server │ │ │ │ ├── container-io.ts │ │ │ │ ├── index.ts │ │ │ │ ├── server.ts │ │ │ │ └── types.ts │ │ └── presentation │ │ │ ├── rest │ │ │ ├── __tests__ │ │ │ │ └── app.controller.ts │ │ │ ├── app.controller.ts │ │ │ ├── auth.controller.ts │ │ │ └── index.ts │ │ │ └── www │ │ │ └── .gitkeep │ └── app.ts ├── tsconfig.json └── tslint.json └── infrastructure ├── Makefile ├── docker-compose.yml ├── init.sql └── nodeMachine └── Dockerfile /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | 25 | # Execute Docker Compose. 26 | - name: Pull Docker Compose. 27 | run: docker-compose -f infrastructure/docker-compose.yml pull 28 | 29 | # Execute Docker Build. 30 | - name: Build Docker Compose. 31 | run: satackey/action-docker-layer-caching@v0.0.8l 32 | # Ignore the failure of a step and avoid terminating the job. 33 | continue-on-error: true 34 | 35 | # Runs a set of commands using the runners shell 36 | - name: Install dependencies 37 | run: cd app && npm i 38 | 39 | # Runs a set of commands using the runners shell 40 | - name: Execute Test 41 | run: cd app && npm run test 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | db/ 2 | src/dist/ 3 | src/node_modules/ 4 | src/package-lock.json 5 | src/yarn.lock 6 | src/logs 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Typescript, Inversify, TypeORM for NodeJS 3 |

4 | 5 |

6 | 7 |

8 | 9 | Example of a Nodejs application using TypeScript, Inversify and TypeORM made with :two_hearts: 10 | 11 | ![CI](https://github.com/GeeksHubsAcademy/typescript-di-typeorm-ddd-skeletor/workflows/CI/badge.svg?branch=master) 12 | 13 | If you like it, don't forget to give us some :star::star::star: 14 | 15 | Here is a video where you can see how to start it and how it works 16 | 17 | 18 | 19 | ## 🚀 Environment Setup 20 | 21 | ### :octocat: Needed tools 22 | 23 | 1. [Install Docker and Docker-Compose] (https://www.docker.com/get-started) 24 | 2. [Makefiles] (https://www.tutorialspoint.com/makefile/index.htm) 25 | 2. Clone this project: `https://github.com/GeeksHubsAcademy/typescript-di-typeorm-ddd-skeletor.git` 26 | 3. Move to the project folder: `cd infraestructure` 27 | 28 | ## :arrow_forward: Start Application 29 | 1. Run in a terminal :computer: `$> make docker-up` 30 | 2. Run in Other terminal :computer: `$>make docker-bootstrap` 31 | 3. Finally run: `$>make docker-watch` 32 | 33 | ## :battery: Testing Application 34 | 1. Run Jest in a terminal :computer: `$>make docker-test` 35 | 36 | ## :superhero_woman: Contribute. 37 | Feel free to make as many pull requests as you think fit, because there are so many things to do, all help is welcome. 38 | 39 | Here is a guide if you want to take a look(https://github.com/GeeksHubsAcademy/2020-geekshubs-convenio/blob/master/contributing.md) 40 | 41 | If you find a bug, let us know here . 42 | 43 | If you request a new feature. 44 | -------------------------------------------------------------------------------- /app/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | package-lock.json 4 | yarn.lock 5 | logs 6 | -------------------------------------------------------------------------------- /app/env/.env.dev: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | 3 | SERVER_PORT=3600 4 | 5 | LOGGER_LEVEL="all" 6 | 7 | MONGO_USER='tsdev' 8 | MONGO_PASS='tsdev' 9 | MONGO_HOST='localhost' 10 | MONGO_PORT='23032' 11 | MONGO_DB='ts-course-dev' 12 | 13 | BCRYPT_SALT=3 14 | 15 | JWT_KEY='d3v3l0pm3ntk3y' 16 | JWT_ALGORITHM='HS512' 17 | JWT_EXPIRING_TIME_IN_SECONDS=3600 18 | -------------------------------------------------------------------------------- /app/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ["ts", "tsx", "js", "json"], 3 | transform: { 4 | "^.+\\.(ts|tsx)$": "ts-jest" 5 | }, 6 | globals: { 7 | "ts-jest": { 8 | tsConfigFile: "tsconfig.json" 9 | } 10 | }, 11 | testMatch: ["**/__tests__/**/*.+(ts|tsx|js)"], 12 | testPathIgnorePatterns: ["/node_modules/", "/dist/", "/lib/"], 13 | verbose: true, 14 | testURL: "http://localhost/" 15 | }; 16 | -------------------------------------------------------------------------------- /app/ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "mysql", 3 | "host": "mariadb", 4 | "port": 3306, 5 | "username": "root", 6 | "password": "password", 7 | "database": "agenda", 8 | "synchronize": false, 9 | "logging": true, 10 | "entities": ["dist/**/*.entity.js"], 11 | "migrations": ["dist/Application/infrastructure/persistence/typeorm/migrations/*.js"], 12 | "cli":{ 13 | "migrationsDir": "src/Application/infrastructure/persistence/typeorm/migrations/" 14 | } 15 | } -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agenda", 3 | "version": "1.0.0", 4 | "description": "Project integration NODE TypeScript Inversify", 5 | "main": "dist/src/app.js", 6 | "scripts": { 7 | "start": "nodemon ./dist/app.js", 8 | "build": "npm run clean; tsc", 9 | "build:watch": "npm run build -- -w", 10 | "clean": "rm -r dist", 11 | "test": "jest", 12 | "test:yarn": "yarn lint:types && jest --no-cache", 13 | "test:watch": "yarn lint && jest --watchAll --no-cache", 14 | "lint": "yarn lint:types && yarn lint:ci", 15 | "lint:types": "yarn tsc --noEmit -p .", 16 | "lint:ci": "eslint . --ext .tsx,.ts" 17 | }, 18 | "dependencies": { 19 | "@types/jest": "^25.2.3", 20 | "bcrypt": "^5.0.0", 21 | "body-parser": "^1.19.0", 22 | "compression": "^1.7.4", 23 | "cookie-parser": "^1.4.4", 24 | "cors": "^2.8.5", 25 | "dotenv": "^8.2.0", 26 | "express": "^4.17.1", 27 | "express-session": "^1.16.2", 28 | "inversify": "^5.0.1", 29 | "inversify-binding-decorators": "^4.0.0", 30 | "inversify-express-utils": "^6.3.2", 31 | "jsonwebtoken": "^8.5.1", 32 | "log4js": "^6.3.0", 33 | "method-override": "^3.0.0", 34 | "mysql2": "^2.2.5", 35 | "reflect-metadata": "^0.1.13", 36 | "ts-node": "^9.0.0", 37 | "typeorm": "^0.2.19", 38 | "uuid": "^8.3.1", 39 | "uuid-validate": "0.0.3" 40 | }, 41 | "devDependencies": { 42 | "@types/express": "^4.17.1", 43 | "@types/jest": "^25.2.3", 44 | "@types/node": "^14.14.10", 45 | "@types/supertest": "^2.0.10", 46 | "@typescript-eslint/eslint-plugin": "^3.2.0", 47 | "@typescript-eslint/parser": "^3.2.0", 48 | "concurrently": "^4.1.2", 49 | "eslint": "^7.2.0", 50 | "eslint-plugin-import": "^2.21.1", 51 | "jest": "^26.0.1", 52 | "nodemon": "^1.19.2", 53 | "supertest": "^6.0.1", 54 | "ts-jest": "^26.1.0", 55 | "tslint": "^5.20.0", 56 | "typescript": "^3.6.3", 57 | "wait-on": "^3.3.0" 58 | }, 59 | "engines": { 60 | "node": ">=10.0.0" 61 | }, 62 | "jest": { 63 | "moduleFileExtensions": [ 64 | "ts", 65 | "tsx", 66 | "js" 67 | ], 68 | "transform": { 69 | "^.+\\.(ts|tsx)$": "ts-jest" 70 | }, 71 | "globals": { 72 | "ts-jest": { 73 | "tsConfigFile": "tsconfig.json" 74 | } 75 | }, 76 | "testMatch": [ 77 | "**/__tests__/*.+(ts|tsx|js)" 78 | ] 79 | }, 80 | "author": "Geekshubs", 81 | "license": "ISC" 82 | } 83 | -------------------------------------------------------------------------------- /app/src/Application/application/Readme.md: -------------------------------------------------------------------------------- 1 | Adding user stories :raising_hand_man: 2 | -------------------------------------------------------------------------------- /app/src/Application/application/auth/login/login.interface.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../../domain/models/user.model' 2 | 3 | export interface ILoginService{ 4 | loginUser(user:User):Promise; 5 | } -------------------------------------------------------------------------------- /app/src/Application/application/auth/login/login.service.ts: -------------------------------------------------------------------------------- 1 | import { User } from "../../../domain/models/user.model"; 2 | import { ILoginService } from "./login.interface"; 3 | import { inject, injectable } from "inversify"; 4 | import { createLogger} from '../../../infrastructure/logger'; 5 | import Types from '../../../infrastructure/server/types' 6 | import { IUserRepository } from "../../../domain/repositories"; 7 | import { compare} from 'bcrypt'; 8 | import { JwtService } from "../../../infrastructure/authentication/jwt/jwt.service"; 9 | 10 | 11 | 12 | @injectable() 13 | export class LoginService implements ILoginService{ 14 | 15 | private logger = createLogger('RegisterService'); 16 | @inject (Types.IJwtService) private jwtService : JwtService; 17 | 18 | constructor( @inject( Types.IUserRepository) private userRepo:IUserRepository){} 19 | 20 | async loginUser(user: User): Promise { 21 | this.logger.info("Init Login User ->"+user.getEmail); 22 | const User = await this.userRepo.findUser(user.getEmail); 23 | if (User === null) throw new Error("Error in Login"); 24 | let result = await this.checkPassword(user.getPassword, User[0].password); 25 | if (!result) throw new Error("Error in Login"); 26 | return this.jwtService.encodeJWT(User); 27 | } 28 | 29 | async checkPassword(plainPassword:string, hashedPassword:string):Promise { 30 | try{ 31 | return await compare(plainPassword, hashedPassword); 32 | }catch(error){ 33 | this.logger.error(error); 34 | } 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /app/src/Application/application/auth/register/register.interface.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../../domain/models/user.model' 2 | 3 | export interface IRegisterService{ 4 | registerUser(user:User):Promise; 5 | } -------------------------------------------------------------------------------- /app/src/Application/application/auth/register/register.service.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from "inversify"; 2 | import { createLogger } from '../../../infrastructure/logger'; 3 | import { v4 as uuidv4 } from 'uuid'; 4 | import { UserDto } from "../../../domain/dtos"; 5 | import { User } from "../../../domain/models/user.model"; 6 | import { IUserRepository } from "../../../domain/repositories"; 7 | import Types from "../../../infrastructure/server/types"; 8 | import { IRegisterService } from "./register.interface"; 9 | 10 | 11 | @injectable() 12 | export class RegisterService implements IRegisterService{ 13 | private logger = createLogger('RegisterService'); 14 | 15 | constructor( @inject( Types.IUserRepository) private userRepo:IUserRepository){} 16 | 17 | async registerUser(user: User): Promise { 18 | this.logger.info("Init register User"); 19 | const userFind = await this.userRepo.findUser(user.getEmail); 20 | if ( userFind !== null ) throw new Error("Email exist!!"); 21 | user.hashPassword(); 22 | const userDto = this.toUserDTO(user); 23 | const createUserDTO = await this.userRepo.create(userDto); 24 | return this.toUser(createUserDTO); 25 | } 26 | 27 | 28 | private toUser(userDTO: UserDto): User { 29 | return new User( 30 | userDTO.name, 31 | userDTO.email, 32 | null, 33 | userDTO.created_at, 34 | userDTO.update_at, 35 | userDTO._id 36 | ) 37 | } 38 | 39 | private toUserDTO(user:User):UserDto{ 40 | let uuid = uuidv4(); 41 | return { 42 | _id: uuid, 43 | email: user.getEmail, 44 | name: user.getName, 45 | password: user.getPassword, 46 | created_at: new Date(), 47 | update_at: new Date() 48 | } 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/Application/domain/dtos/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.dto'; -------------------------------------------------------------------------------- /app/src/Application/domain/dtos/user.dto.ts: -------------------------------------------------------------------------------- 1 | export interface UserDto{ 2 | _id: string; 3 | name: string; 4 | email: string; 5 | password: string; 6 | created_at?: Date; 7 | update_at?: Date; 8 | } -------------------------------------------------------------------------------- /app/src/Application/domain/models/user.model.ts: -------------------------------------------------------------------------------- 1 | import * as bcrypt from 'bcrypt'; 2 | 3 | export class User{ 4 | 5 | private name: string; 6 | private email:string; 7 | private password?: string; 8 | 9 | constructor( name: string, 10 | email: string, 11 | password?: string, 12 | private createdAt?: Date, 13 | private updatedAt?: Date, 14 | private id?: string,){ 15 | this.name = name; 16 | this.email = email; 17 | this.password = password; 18 | } 19 | 20 | get getName(): string { 21 | return this.name; 22 | } 23 | 24 | get getEmail(): string { 25 | return this.email; 26 | } 27 | 28 | get getCreatedAtDate(): Date { 29 | return this.createdAt; 30 | } 31 | 32 | get getUpdatedAtDate(): Date { 33 | return this.updatedAt; 34 | } 35 | 36 | get getPassword(): string { 37 | return this.password; 38 | } 39 | 40 | get getId(): string { 41 | return this.id; 42 | } 43 | 44 | hashPassword() { 45 | this.password = bcrypt.hashSync(this.password, 8); 46 | } 47 | 48 | }; -------------------------------------------------------------------------------- /app/src/Application/domain/repositories/iUser.repository.ts: -------------------------------------------------------------------------------- 1 | import { UserDto } from '../dtos/user.dto'; 2 | 3 | export interface IUserRepository { 4 | create(user:UserDto):Promise; 5 | findAll():Promise; 6 | findUser(email:String):Promise; 7 | } -------------------------------------------------------------------------------- /app/src/Application/domain/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export { IUserRepository } from './iUser.repository'; -------------------------------------------------------------------------------- /app/src/Application/infrastructure/authentication/jwt/jwt.interface.ts: -------------------------------------------------------------------------------- 1 | import { UserDto } from "../../../domain/dtos"; 2 | 3 | export interface IJwtAuthentication{ 4 | encodeJWT(user:UserDto):Promise; 5 | } -------------------------------------------------------------------------------- /app/src/Application/infrastructure/authentication/jwt/jwt.service.ts: -------------------------------------------------------------------------------- 1 | import { IJwtAuthentication } from "./jwt.interface"; 2 | import { sign, Secret , Algorithm, SigningOptions } from 'jsonwebtoken'; 3 | import { injectable } from "inversify"; 4 | import { createLogger } from '../../../infrastructure/logger'; 5 | import { UserDto } from "../../../domain/dtos"; 6 | 7 | 8 | export interface JwtPayload{ 9 | sub: string 10 | } 11 | 12 | @injectable() 13 | export class JwtService implements IJwtAuthentication{ 14 | private logger = createLogger('JwtService'); 15 | async encodeJWT(user: UserDto): Promise{ 16 | 17 | try{ 18 | const payload:JwtPayload = {sub : user._id}; 19 | const secret: Secret = process.env.JWT_KEY ||'geekshubs'; 20 | const options: SigningOptions = { 21 | algorithm :process.env.JWT_ALGORITHM as Algorithm ?? 'HS512', 22 | expiresIn: parseInt(process.env.JWT_EXPIRING_TIME_IN_SECONDS ?? '3600',10)} 23 | return await sign(payload,secret,options) 24 | }catch (Exception){ 25 | this.logger.error(Exception.message); 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /app/src/Application/infrastructure/logger/index.ts: -------------------------------------------------------------------------------- 1 | export * from './log4js'; -------------------------------------------------------------------------------- /app/src/Application/infrastructure/logger/log4js/config.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | appenders: { 3 | console: { 4 | type: 'console', 5 | layout: { 6 | type: 'pattern', 7 | pattern: '[%r] (%20.20c) - [%[%5.5p%]] - %m%' 8 | } 9 | } 10 | }, 11 | categories: { 12 | default: { 13 | appenders: ['console'], 14 | level: process.env.LOGGER_LEVEL || 'all' 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/Application/infrastructure/logger/log4js/index.ts: -------------------------------------------------------------------------------- 1 | import { configure, getLogger } from 'log4js' 2 | import { config } from './config' 3 | 4 | configure(config) 5 | 6 | export const createLogger = (section: string | undefined) => getLogger(section) 7 | -------------------------------------------------------------------------------- /app/src/Application/infrastructure/persistence/typeorm/config/connectionService.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Connection, getConnection, Repository } from "typeorm"; 3 | import {createLogger} from '../../../logger'; 4 | 5 | @injectable() 6 | export class ConnectionService{ 7 | 8 | private logger = createLogger('TypeORM'); 9 | 10 | private _conn:Connection; 11 | 12 | async getRepo(target:string | (new () =>{})):Promise> { 13 | try{ 14 | const connection = await this._getConn(); 15 | return await connection.getRepository(target); 16 | }catch(error){ 17 | this.logger.error("Error en la recuperación del respositorio "+ error.message) 18 | } 19 | } 20 | 21 | private async _getConn(){ 22 | if(!this._conn){ 23 | this.logger.info("Devolviendo conexión ya creada"); 24 | return getConnection(); 25 | } 26 | this.logger.error("No connection"); 27 | 28 | 29 | } 30 | 31 | async closeConnection(){ 32 | this._conn.close(); 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/Application/infrastructure/persistence/typeorm/entities/index.ts: -------------------------------------------------------------------------------- 1 | export { UserEntity } from './user.entity'; -------------------------------------------------------------------------------- /app/src/Application/infrastructure/persistence/typeorm/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryColumn} from "typeorm"; 2 | import { UserDto } from '../../../../domain/dtos'; 3 | 4 | @Entity({ name: "users" }) 5 | export class UserEntity implements UserDto{ 6 | 7 | @PrimaryColumn() 8 | _id: string; 9 | 10 | @Column() 11 | name: string; 12 | 13 | @Column() 14 | email: string; 15 | 16 | @Column() 17 | password: string; 18 | 19 | @Column() 20 | created_at?: Date; 21 | 22 | @Column() 23 | update_at?: Date; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/Application/infrastructure/persistence/typeorm/index.ts: -------------------------------------------------------------------------------- 1 | import { createConnection } from "typeorm"; 2 | import {createLogger} from './../../logger'; 3 | 4 | export const initOrm = async()=>{ 5 | const logger = createLogger('TypeORM'); 6 | 7 | await createConnection() 8 | .then((connection)=>logger.info("Init Connection")) 9 | .catch(error=>logger.error(error.message)); 10 | 11 | } -------------------------------------------------------------------------------- /app/src/Application/infrastructure/persistence/typeorm/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeeksHubs/typescript-di-typeorm-ddd-skeletor/a8b07283a19ef7e6f6589c9c65e52a82cec6f8bf/app/src/Application/infrastructure/persistence/typeorm/migrations/.gitkeep -------------------------------------------------------------------------------- /app/src/Application/infrastructure/persistence/typeorm/migrations/1606925285524-add_users_table.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class addUsersTable1606925285524 implements MigrationInterface { 4 | name = 'addUsersTable1606925285524' 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query("CREATE TABLE `users` (`_id` varchar(255) NOT NULL, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `created_at` datetime NOT NULL, `update_at` datetime NOT NULL, PRIMARY KEY (`_id`)) ENGINE=InnoDB"); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.query("DROP TABLE `users`"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/Application/infrastructure/persistence/typeorm/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export {UserRepository} from './user.repository'; -------------------------------------------------------------------------------- /app/src/Application/infrastructure/persistence/typeorm/repositories/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { UserDto } from '../../../../domain/dtos'; 3 | import { IUserRepository } from '../../../../domain/repositories'; 4 | import { UserEntity } from '../entities'; 5 | import { ConnectionService } from '../config/connectionService'; 6 | import { createLogger } from '../../../logger' 7 | 8 | 9 | 10 | @injectable() 11 | export class UserRepository implements IUserRepository{ 12 | 13 | private conn; 14 | private logger = createLogger('UserRepository'); 15 | 16 | constructor(){ 17 | this.conn = new ConnectionService(); 18 | } 19 | 20 | async findUser(email: String): Promise { 21 | try{ 22 | let repo = await this.conn.getRepo(UserEntity); 23 | let result = await repo.find({ where: { email:email}}) 24 | if (result.length > 0 ) return result; 25 | return null; 26 | }catch(Exception){ 27 | this.logger.error(Exception.message); 28 | } 29 | } 30 | 31 | async findAll(): Promise { 32 | try{ 33 | let repo =await this.conn.getRepo(UserEntity); 34 | return repo.find(); 35 | }catch(error){ 36 | this.logger.error(error.message); 37 | } 38 | } 39 | 40 | 41 | async create(user: UserDto): Promise { 42 | try{ 43 | let repo = await this.conn.getRepo(UserEntity) 44 | return repo.save(user); 45 | } 46 | catch(error){ 47 | console.log("Error "+ error.message); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/Application/infrastructure/server/container-io.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'inversify'; 2 | import { IRegisterService } from '../../application/auth/register/register.interface'; 3 | import { RegisterService } from '../../application/auth/register/register.service'; 4 | import { ILoginService } from '../../application/auth/login/login.interface'; 5 | import { LoginService } from '../../application/auth/login/login.service'; 6 | import { IUserRepository } from '../../domain/repositories'; 7 | import { IJwtAuthentication} from '../authentication/jwt/jwt.interface'; 8 | 9 | 10 | import '../../presentation/rest'; 11 | import { UserRepository } from '../persistence/typeorm/repositories'; 12 | import Types from './types'; 13 | import { JwtService } from '../authentication/jwt/jwt.service'; 14 | 15 | let container = new Container(); 16 | 17 | //Repositories 18 | container.bind(Types.IUserRepository).to(UserRepository); 19 | 20 | //Services 21 | container.bind(Types.IRegisterService).to(RegisterService); 22 | container.bind(Types.ILoginService).to(LoginService); 23 | container.bind(Types.IJwtService).to(JwtService); 24 | 25 | export default container; 26 | -------------------------------------------------------------------------------- /app/src/Application/infrastructure/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './server'; -------------------------------------------------------------------------------- /app/src/Application/infrastructure/server/server.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import * as Express from 'express'; 3 | import * as session from 'express-session'; 4 | import { InversifyExpressServer } from 'inversify-express-utils'; 5 | import {createLogger} from '../logger'; 6 | import * as cors from 'cors'; 7 | 8 | 9 | const logger = createLogger('server'); 10 | 11 | 12 | // IoC 13 | import container from './container-io'; 14 | 15 | 16 | 17 | // start the server 18 | const server = new InversifyExpressServer(container); 19 | 20 | const port: number = parseInt(process.env.SERVER_PORT ?? '3000', 10); 21 | 22 | server.setConfig((App: any) => { 23 | const cookieParser = require('cookie-parser'); 24 | const bodyParser = require('body-parser'); 25 | const compress = require('compression'); 26 | const methodOverride = require('method-override'); 27 | App 28 | .use(cors({origin:true})) 29 | .options('*',cors()) 30 | .use(cookieParser()) 31 | .use(compress({})) 32 | .use(methodOverride()) 33 | .use(bodyParser.json()) 34 | .use(bodyParser.urlencoded({ 35 | extended: true 36 | })) 37 | .use(session({ 38 | secret: 'AUTH_SECRET', 39 | name: 'pp-cookie', 40 | resave: true, 41 | saveUninitialized: false 42 | })) 43 | .use('/apidoc', Express.static('apidoc')); 44 | }); 45 | 46 | 47 | const app = server.build(); 48 | 49 | const initApp= () =>{ 50 | app.listen(3000); 51 | logger.info(`Server runngin in http://localhost:${port}`); 52 | } 53 | 54 | export { app, initApp}; 55 | -------------------------------------------------------------------------------- /app/src/Application/infrastructure/server/types.ts: -------------------------------------------------------------------------------- 1 | let Types = { 2 | IUserRepository: Symbol("IUserRepository"), 3 | IRegisterService: Symbol("IRegisterService"), 4 | ILoginService: Symbol("ILoginService"), 5 | IJwtService: Symbol("IJwtService") 6 | } 7 | 8 | export default Types; -------------------------------------------------------------------------------- /app/src/Application/presentation/rest/__tests__/app.controller.ts: -------------------------------------------------------------------------------- 1 | import "jest"; 2 | import { HelloController } from '../app.controller'; 3 | 4 | describe("GET /hello", () => { 5 | 6 | let helloController : HelloController; 7 | 8 | beforeAll(async()=>{ 9 | helloController = new HelloController(); 10 | }); 11 | 12 | 13 | it("Hello API Request", async () => { 14 | 15 | 16 | const response = await helloController.helloWorld(); 17 | expect(response.statusCode).toEqual(200); 18 | 19 | 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /app/src/Application/presentation/rest/app.controller.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { BaseHttpController, controller, httpGet } from 'inversify-express-utils'; 3 | 4 | 5 | @controller('/hello') 6 | export class HelloController extends BaseHttpController { 7 | constructor(){ 8 | super(); 9 | } 10 | 11 | @httpGet('/') 12 | public helloWorld(){ 13 | const statusCode= 200; 14 | const result = {"message":"helloworld"}; 15 | return this.json(result,statusCode); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/Application/presentation/rest/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { BaseHttpController, controller, httpGet, httpPost, request, response } from 'inversify-express-utils'; 3 | import { Request, Response } from "express"; 4 | import { User } from "../../domain/models/user.model"; 5 | import { inject } from "inversify"; 6 | import Types from "../../infrastructure/server/types"; 7 | import { IRegisterService } from "../../application/auth/register/register.interface"; 8 | import { ILoginService } from "../../application/auth/login/login.interface"; 9 | 10 | 11 | @controller('/api') 12 | export class AuthController extends BaseHttpController { 13 | constructor( 14 | @inject(Types.IRegisterService) private registerService: IRegisterService, 15 | @inject(Types.ILoginService) private loginService: ILoginService 16 | 17 | ){ 18 | super(); 19 | } 20 | 21 | @httpPost('/auth') 22 | public async createUser(@request() req: Request, @response() res:Response){ 23 | 24 | try{ 25 | let user= await this.registerService.registerUser( 26 | new User( 27 | req.body.name, 28 | req.body.email, 29 | req.body.password, 30 | new Date(), 31 | new Date() 32 | ) 33 | ); 34 | const statusCode= 200; 35 | const result = {"message":"User Created ->"+user.getId}; 36 | return this.json(result,statusCode); 37 | }catch(error){ 38 | const statusCode=400; 39 | const result={"error": error.message} 40 | return this.json(result, statusCode); 41 | } 42 | } 43 | 44 | @httpGet('/auth') 45 | public async getLogin(@request() req: Request, @response() res:Response){ 46 | try{ 47 | let login = await this.loginService.loginUser( 48 | new User( 49 | null, 50 | req.body.email, 51 | req.body.password, 52 | null, 53 | null 54 | )) 55 | const statusCode= 200; 56 | const result = {"message":login}; 57 | return this.json(result,statusCode); 58 | }catch(error){ 59 | const statusCode=400; 60 | const result={"error":error.message}; 61 | return this.json(result, statusCode); 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /app/src/Application/presentation/rest/index.ts: -------------------------------------------------------------------------------- 1 | export { HelloController} from './app.controller'; 2 | export { AuthController } from './auth.controller'; 3 | -------------------------------------------------------------------------------- /app/src/Application/presentation/www/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeeksHubs/typescript-di-typeorm-ddd-skeletor/a8b07283a19ef7e6f6589c9c65e52a82cec6f8bf/app/src/Application/presentation/www/.gitkeep -------------------------------------------------------------------------------- /app/src/app.ts: -------------------------------------------------------------------------------- 1 | import { initOrm } from './Application/infrastructure/persistence/typeorm'; 2 | import { initApp } from './Application/infrastructure/server'; 3 | 4 | 5 | const startApp = async ()=>{ 6 | 7 | initOrm(); 8 | initApp(); 9 | }; 10 | 11 | startApp(); -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "es5", 5 | "sourceMap": true, 6 | "lib": [ 7 | "es5", 8 | "es6", 9 | "dom" 10 | ], 11 | "types": [ 12 | "reflect-metadata", 13 | "jest", 14 | "node" 15 | ], 16 | "module": "commonjs", 17 | "moduleResolution": "node", 18 | "experimentalDecorators": true, 19 | "emitDecoratorMetadata": true, 20 | "noUnusedLocals": true, 21 | "noImplicitAny": false 22 | }, 23 | "include": [ 24 | "src/**/*.ts" 25 | ], 26 | "exclude": [ 27 | "dist", 28 | "src/**/*.spec.ts", 29 | "node_modules" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /app/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": [ 4 | false 5 | ], 6 | "ban": false, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space", 11 | "check-lowercase" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "indent": [ 17 | true, 18 | "spaces" 19 | ], 20 | "interface-name": true, 21 | "jsdoc-format": true, 22 | "label-position": true, 23 | "label-undefined": true, 24 | "max-line-length": [ 25 | true, 26 | 140 27 | ], 28 | "member-access": true, 29 | "member-ordering": [ 30 | true, 31 | "public-before-private", 32 | "static-before-instance", 33 | "variables-before-functions" 34 | ], 35 | "no-arg": true, 36 | "no-bitwise": true, 37 | "no-conditional-assignment": true, 38 | "no-consecutive-blank-lines": false, 39 | "no-console": [ 40 | true, 41 | "debug", 42 | "info", 43 | "time", 44 | "timeEnd", 45 | "trace" 46 | ], 47 | "no-construct": true, 48 | "no-debugger": true, 49 | "no-duplicate-key": true, 50 | "no-duplicate-variable": true, 51 | "no-empty": true, 52 | "no-eval": true, 53 | "no-inferrable-types": false, 54 | "no-internal-module": true, 55 | "no-shadowed-variable": true, 56 | "no-string-literal": true, 57 | "no-switch-case-fall-through": true, 58 | "no-trailing-whitespace": true, 59 | "no-unreachable": true, 60 | "no-unused-expression": true, 61 | "no-unused-variable": true, 62 | "no-use-before-declare": true, 63 | "no-var-keyword": true, 64 | "no-var-requires": true, 65 | "object-literal-sort-keys": true, 66 | "one-line": [ 67 | true, 68 | "check-open-brace", 69 | "check-catch", 70 | "check-else", 71 | "check-whitespace" 72 | ], 73 | "quotemark": [ 74 | true, 75 | "single", 76 | "avoid-escape" 77 | ], 78 | "radix": true, 79 | "semicolon": true, 80 | "trailing-comma": [ 81 | false 82 | ], 83 | "triple-equals": [ 84 | true, 85 | "allow-null-check" 86 | ], 87 | "typedef-whitespace": [ 88 | true, 89 | { 90 | "call-signature": "nospace", 91 | "index-signature": "nospace", 92 | "parameter": "nospace", 93 | "property-declaration": "nospace", 94 | "variable-declaration": "nospace" 95 | } 96 | ], 97 | "use-strict": [ 98 | true, 99 | "check-module", 100 | "check-function" 101 | ], 102 | "variable-name": [ 103 | true, 104 | "check-format", 105 | "allow-leading-underscore", 106 | "ban-keywords" 107 | ], 108 | "whitespace": [ 109 | true, 110 | "check-branch", 111 | "check-decl", 112 | "check-operator", 113 | "check-separator", 114 | "check-type" 115 | ] 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /infrastructure/Makefile: -------------------------------------------------------------------------------- 1 | docker-up: 2 | @echo "Up All Services" 3 | docker-compose up 4 | 5 | docker-bootstrap: 6 | 7 | @echo "Create Database if not exists" 8 | docker exec -ti infrastructure_mariadb_1 sh -c "cd /opt/bd && mysql -uroot -ppassword < init.sql" 9 | 10 | docker-access-bd: 11 | 12 | @echo "Access to cotainer BD" 13 | docker exec -ti infrastructure_mariadb_1 bash 14 | 15 | docker-access-api: 16 | 17 | @echo "Access to container API" 18 | docker exec -ti infrastructure_api-rest_1 bash 19 | 20 | docker-down: 21 | 22 | @echo "Down docker-compose" 23 | docker-compose down 24 | 25 | docker-watch: 26 | 27 | @echo "Watch in js" 28 | docker exec -ti infrastructure_api-rest_1 sh -c "npm run build:watch" 29 | 30 | docker-test: 31 | 32 | @echo "Run tests" 33 | docker exec -ti infrastructure_api-rest_1 sh -c "npm run test" 34 | 35 | docker-clear: 36 | 37 | @echo "Clear Dist folder" 38 | docker exec -ti infrastructure_api-rest_1 sh -c "npm run clean" 39 | 40 | docker-generate-migration: 41 | 42 | @echo "Generate Migrations" 43 | ifdef migration 44 | docker exec -ti infrastructure_api-rest_1 sh -c "./node_modules/.bin/ts-node ./node_modules/.bin/typeorm migration:generate -n $(migration)" 45 | @echo 'Migration created' $(migration) 46 | else 47 | @echo 'Migration name is not defined' 48 | endif 49 | 50 | docker-create-migration: 51 | 52 | @echo "Create Migrations" 53 | ifdef migration 54 | docker exec -ti infrastructure_api-rest_1 sh -c "./node_modules/.bin/ts-node ./node_modules/.bin/typeorm migration:create -n $(migration)" 55 | @echo 'Migration created' $(migration) 56 | else 57 | @echo 'Migration name is not defined' 58 | endif 59 | 60 | docker-run-migration: 61 | 62 | @echo "Execute Migrations" 63 | docker exec -ti infrastructure_api-rest_1 sh -c "./node_modules/.bin/ts-node ./node_modules/.bin/typeorm migration:run" 64 | 65 | docker-revert-migration: 66 | 67 | @echo "rollback last migration" 68 | docker exec -ti infrastructure_api-rest_1 sh -c "./node_modules/.bin/ts-node ./node_modules/.bin/typeorm migration:revert" 69 | 70 | docker-clear-all: 71 | 72 | @echo "Warning !!!! Delete ALL volumes, containers and images" 73 | docker volume prune 74 | docker system prune -a -------------------------------------------------------------------------------- /infrastructure/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | api-rest: 4 | build: 5 | context: ./nodeMachine 6 | command: sh -c "npm i && npm run build && npm run start" 7 | volumes: 8 | - ../app:/app 9 | ports: 10 | - 80:3000 11 | mariadb: 12 | image: 'mariadb' 13 | command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci 14 | environment: 15 | MYSQL_ROOT_PASSWORD: "password" 16 | MYSQL_USER: "user" 17 | MYSQL_PASSWORD: "password" 18 | ports: 19 | - 3306:3306 20 | volumes: 21 | - ../db:/var/lib/mysql 22 | - ./init.sql:/opt/bd/init.sql 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /infrastructure/init.sql: -------------------------------------------------------------------------------- 1 | #create database. 2 | CREATE DATABASE IF NOT EXISTS `agenda`; -------------------------------------------------------------------------------- /infrastructure/nodeMachine/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | RUN apt-get update && apt-get install -y software-properties-common 4 | 5 | RUN apt-get -y upgrade 6 | 7 | RUN yarn global add typescript nodemon 8 | 9 | WORKDIR /app 10 | 11 | EXPOSE 3000 --------------------------------------------------------------------------------