├── .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 | 
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
--------------------------------------------------------------------------------