├── .DS_Store ├── .env.example ├── .gitignore ├── README.md ├── package.json ├── src ├── application │ ├── app.ts │ └── config │ │ └── environment.ts ├── domain │ ├── models │ │ ├── gateways │ │ │ ├── add-user-repository.ts │ │ │ ├── authentication-repository.ts │ │ │ ├── check-email-repository.ts │ │ │ ├── decryp-repositoryt.ts │ │ │ ├── encrypt-repository.ts │ │ │ ├── get-users-repository.ts │ │ │ ├── hash-compare-repository.ts │ │ │ ├── hash-repository.ts │ │ │ ├── load-account-token-repository.ts │ │ │ └── update-access-token-repository.ts │ │ └── user.ts │ └── use-cases │ │ ├── add-user-service.ts │ │ ├── authentication-service.ts │ │ ├── get-users-service.ts │ │ ├── impl │ │ ├── add-user-service-impl.ts │ │ ├── authentication-service-impl.ts │ │ ├── get-users-service-impl.ts │ │ └── load-account-token-service-impl.ts │ │ └── load-account-token-service.ts ├── index.ts └── infrastructure │ ├── driven-adapters │ ├── adapters │ │ ├── bcrypt-adapter.ts │ │ ├── jwt-adapter.ts │ │ └── orm │ │ │ └── mongoose │ │ │ ├── models │ │ │ └── user.ts │ │ │ └── user-mongoose-repository-adapter.ts │ └── providers │ │ └── index.ts │ ├── entry-points │ └── api │ │ ├── add-user-controller.ts │ │ ├── authentication-controller.ts │ │ ├── get-users-controller.ts │ │ └── index.ts │ └── helpers │ ├── auth.ts │ └── validate-fields.ts ├── tsconfig-build.json └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsclean/api-example/fca155dfc9638cc66288578d18ea00e35cfa97b2/.DS_Store -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Mongo configuration 2 | MONGO_DEVELOPMENT= 3 | MONGO_PRODUCTION= 4 | 5 | # Mysql configuration 6 | DB_USER= 7 | DB_PASSWORD= 8 | DATABASE= 9 | 10 | # Postgres configuration 11 | DB_USER_POSTGRES= 12 | DATABASE_POSTGRES= 13 | DB_PASSWORD_POSTGRES= 14 | DB_PORT_POSTGRES= 15 | 16 | JWT_SECRET= 17 | NODE_ENV=development 18 | HOST=127.0.0.1 19 | PORT=9000 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | build/ 5 | .env 6 | package-lock.json 7 | dist 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Awesome Project Build with Clean Architecture 2 | 3 | Steps to run this project: 4 | 5 | 1. Run `npm watch` command 6 | 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-example", 3 | "version": "1.0.0", 4 | "description": "Awesome project developed with Clean Architecture", 5 | "scripts": { 6 | "start": "node ./dist/index.js", 7 | "build": "rimraf dist && tsc -p tsconfig-build.json", 8 | "watch": "nodemon --exec \"npm run build && npm run start\" --watch src --ext ts" 9 | }, 10 | "dependencies": { 11 | "@tsclean/core": "^1.6.8", 12 | "bcrypt": "^5.0.1", 13 | "dotenv": "^10.0.0", 14 | "helmet": "^4.6.0", 15 | "jsonwebtoken": "^8.5.1", 16 | "module-alias": "^2.2.2", 17 | "mongoose": "^6.0.10" 18 | }, 19 | "devDependencies": { 20 | "@types/bcrypt": "^5.0.0", 21 | "@types/jest": "^27.0.1", 22 | "@types/jsonwebtoken": "^8.5.5", 23 | "@types/mongoose": "^5.11.97", 24 | "@types/node": "^16.9.1", 25 | "nodemon": "^2.0.9", 26 | "rimraf": "^3.0.2", 27 | "ts-jest": "^27.0.5", 28 | "ts-node": "^10.2.1", 29 | "typescript": "^4.4.3" 30 | }, 31 | "_moduleAliases": { 32 | "@": "dist" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/application/app.ts: -------------------------------------------------------------------------------- 1 | import {Container} from "@tsclean/core"; 2 | import {controllers} from "@/infrastructure/entry-points/api"; 3 | import {adapters, services} from "@/infrastructure/driven-adapters/providers"; 4 | 5 | @Container({ 6 | imports: [], 7 | providers: [...services, ...adapters], 8 | controllers: [...controllers], 9 | }) 10 | 11 | export class AppContainer { 12 | } 13 | -------------------------------------------------------------------------------- /src/application/config/environment.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | dotenv.config({ path: ".env" }) 4 | 5 | 6 | /** 7 | |----------------------------------------------------------------------------------------| 8 | App Configuration 9 | |----------------------------------------------------------------------------------------| 10 | */ 11 | export const ENVIRONMENT = process.env.NODE_ENV; 12 | const PROD = ENVIRONMENT === "production" 13 | export const PORT = process.env.PORT 14 | 15 | 16 | /** 17 | |----------------------------------------------------------------------------------------| 18 | Authentication Configuration 19 | |----------------------------------------------------------------------------------------| 20 | */ 21 | 22 | export const SESSION_SECRET = process.env.JWT_SECRET || "" 23 | 24 | /** 25 | * Use only if you include jwt 26 | */ 27 | // if (!SESSION_SECRET) process.exit(1) 28 | 29 | 30 | /** 31 | |----------------------------------------------------------------------------------------| 32 | Databases Configuration 33 | |----------------------------------------------------------------------------------------| 34 | */ 35 | 36 | /** 37 | * MySQL 38 | */ 39 | export const CONFIG_MYSQL = { 40 | host : process.env.HOST, 41 | user : process.env.DB_USER, 42 | password : process.env.DB_PASSWORD, 43 | database : process.env.DATABASE 44 | } 45 | 46 | /** 47 | * Mongo DB 48 | */ 49 | export const MONGODB_URI = PROD 50 | ? process.env.MONGO_PRODUCTION 51 | : process.env.MONGO_DEVELOPMENT 52 | 53 | /** 54 | * Postgres 55 | */ 56 | export const CONFIG_POSTGRES = { 57 | host : process.env.HOST, 58 | user : process.env.DB_USER_POSTGRES, 59 | database: process.env.DATABASE_POSTGRES, 60 | password: process.env.DB_PASSWORD_POSTGRES, 61 | port: 5432, 62 | } 63 | -------------------------------------------------------------------------------- /src/domain/models/gateways/add-user-repository.ts: -------------------------------------------------------------------------------- 1 | import {AddUserParams, UserModel, UserRoleModel} from "@/domain/models/user"; 2 | 3 | export const ADD_USER_REPOSITORY = "ADD_USER_REPOSITORY"; 4 | 5 | export interface IAddUserRepository { 6 | addUserRepository: (data: AddUserParams) => Promise 7 | } 8 | 9 | export namespace IAddUserRepository { 10 | export type Result = { 11 | id?: string | number; 12 | firstName: string; 13 | lastName: string; 14 | email: string; 15 | roles: UserRoleModel[] 16 | } 17 | } -------------------------------------------------------------------------------- /src/domain/models/gateways/authentication-repository.ts: -------------------------------------------------------------------------------- 1 | export const AUTHENTICATION_REPOSITORY = "AUTHENTICATION_REPOSITORY"; 2 | 3 | export interface IAuthenticationRepository { 4 | auth: (data: IAuthenticationRepository.Params) => Promise; 5 | } 6 | 7 | export namespace IAuthenticationRepository { 8 | export type Params = { 9 | email: string; 10 | password: string 11 | } 12 | 13 | export type Result = { 14 | accessToken: string; 15 | name: string 16 | } 17 | } -------------------------------------------------------------------------------- /src/domain/models/gateways/check-email-repository.ts: -------------------------------------------------------------------------------- 1 | export const CHECK_EMAIL_REPOSITORY = "CHECK_EMAIL_REPOSITORY"; 2 | 3 | export interface ICheckEmailRepository { 4 | checkEmail: (email: string) => Promise 5 | } 6 | 7 | export namespace ICheckEmailRepository { 8 | export type Result = { 9 | id: string | number; 10 | firstName: string; 11 | password: string; 12 | roles: []; 13 | } 14 | // export type Exist = boolean; 15 | } -------------------------------------------------------------------------------- /src/domain/models/gateways/decryp-repositoryt.ts: -------------------------------------------------------------------------------- 1 | export const DECRYPT_REPOSITORY = "DECRYPT_REPOSITORY"; 2 | 3 | export interface IDecrypt { 4 | decrypt: (cipher: string) => Promise 5 | } 6 | 7 | export namespace IDecrypt { 8 | export type Result = string; 9 | } -------------------------------------------------------------------------------- /src/domain/models/gateways/encrypt-repository.ts: -------------------------------------------------------------------------------- 1 | export const ENCRYPT_REPOSITORY = "ENCRYPT_REPOSITORY"; 2 | 3 | export interface IEncrypt { 4 | encrypt: (text: string | number | Buffer, roles: []) => Promise 5 | } 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/domain/models/gateways/get-users-repository.ts: -------------------------------------------------------------------------------- 1 | import {UserModel} from "@/domain/models/user"; 2 | 3 | export const GET_USERS_REPOSITORY = "GET_USERS_REPOSITORY"; 4 | 5 | export interface IGetUsersRepository { 6 | getUsersRepository: () => Promise 7 | } -------------------------------------------------------------------------------- /src/domain/models/gateways/hash-compare-repository.ts: -------------------------------------------------------------------------------- 1 | export const HASH_COMPARE_REPOSITORY = "HASH_COMPARE_REPOSITORY"; 2 | 3 | export interface IHashCompare { 4 | compare: (text: string, verify: string) => Promise 5 | } -------------------------------------------------------------------------------- /src/domain/models/gateways/hash-repository.ts: -------------------------------------------------------------------------------- 1 | export const HASH_REPOSITORY = "HASH_REPOSITORY"; 2 | 3 | export interface IHashRepository { 4 | hash: (text: string) => Promise 5 | } -------------------------------------------------------------------------------- /src/domain/models/gateways/load-account-token-repository.ts: -------------------------------------------------------------------------------- 1 | export const LOAD_ACCOUNT_TOKEN_REPOSITORY = "LOAD_ACCOUNT_TOKEN_REPOSITORY"; 2 | 3 | export interface ILoadAccountTokenRepository { 4 | loadToken: (token: string) => Promise 5 | } 6 | 7 | export namespace ILoadAccountTokenRepository { 8 | export type Result = { 9 | id: string, 10 | roles: [] 11 | } 12 | } -------------------------------------------------------------------------------- /src/domain/models/gateways/update-access-token-repository.ts: -------------------------------------------------------------------------------- 1 | export const UPDATE_ACCESS_TOKEN_REPOSITORY = "UPDATE_ACCESS_TOKEN_REPOSITORY"; 2 | 3 | export interface IUpdateAccessTokenRepository { 4 | updateToken: (id: string | number, token: string) => Promise; 5 | } -------------------------------------------------------------------------------- /src/domain/models/user.ts: -------------------------------------------------------------------------------- 1 | export type UserModel = { 2 | id?: string | number; 3 | firstName: string; 4 | lastName: string; 5 | email: string; 6 | password: string; 7 | accessToken?: string; 8 | roles: UserRoleModel[] 9 | } 10 | 11 | export type UserRoleModel = [ 12 | { 13 | role: string 14 | } 15 | ] 16 | 17 | export type AddUserParams = Omit 18 | -------------------------------------------------------------------------------- /src/domain/use-cases/add-user-service.ts: -------------------------------------------------------------------------------- 1 | import {AddUserParams} from "@/domain/models/user"; 2 | 3 | export const ADD_USER_SERVICE = "ADD_USER_SERVICE"; 4 | 5 | export interface IAddUserService { 6 | addUserService: (data: AddUserParams) => Promise 7 | } 8 | 9 | export namespace IAddUserService { 10 | export type Exist = boolean; 11 | export type Result = { 12 | id?: string | number 13 | } 14 | } -------------------------------------------------------------------------------- /src/domain/use-cases/authentication-service.ts: -------------------------------------------------------------------------------- 1 | export const AUTHENTICATION_SERVICE = "AUTHENTICATION_SERVICE"; 2 | 3 | export interface IAuthenticationService { 4 | auth: (data: IAuthenticationService.Params) => Promise; 5 | } 6 | 7 | export namespace IAuthenticationService { 8 | export type Params = { 9 | email: string; 10 | password: string 11 | } 12 | 13 | export type Result = { 14 | accessToken: string; 15 | name: string 16 | } 17 | } -------------------------------------------------------------------------------- /src/domain/use-cases/get-users-service.ts: -------------------------------------------------------------------------------- 1 | import {UserModel} from "@/domain/models/user"; 2 | 3 | export const GET_USERS_SERVICE = "GET_USERS_SERVICE"; 4 | 5 | export interface IGetUsersService { 6 | getUsersService: () => Promise 7 | } -------------------------------------------------------------------------------- /src/domain/use-cases/impl/add-user-service-impl.ts: -------------------------------------------------------------------------------- 1 | import {Adapter, Service} from "@tsclean/core"; 2 | import {IAddUserService} from "@/domain/use-cases/add-user-service"; 3 | import {AddUserParams} from "@/domain/models/user"; 4 | import {ADD_USER_REPOSITORY, IAddUserRepository} from "@/domain/models/gateways/add-user-repository"; 5 | import {CHECK_EMAIL_REPOSITORY, ICheckEmailRepository} from "@/domain/models/gateways/check-email-repository"; 6 | import {HASH_REPOSITORY, IHashRepository} from "@/domain/models/gateways/hash-repository"; 7 | 8 | @Service() 9 | export class AddUserServiceImpl implements IAddUserService { 10 | constructor( 11 | @Adapter(HASH_REPOSITORY) private readonly hash: IHashRepository, 12 | @Adapter(CHECK_EMAIL_REPOSITORY) private readonly checkEmailRepository: ICheckEmailRepository, 13 | @Adapter(ADD_USER_REPOSITORY) private readonly addUserRepository: IAddUserRepository 14 | ) { 15 | } 16 | 17 | async addUserService(data: AddUserParams): Promise { 18 | const userExist = await this.checkEmailRepository.checkEmail(data.email); 19 | if (userExist) return true; 20 | 21 | const hashPassword = await this.hash.hash(data.password); 22 | const user = await this.addUserRepository.addUserRepository({...data, password: hashPassword}); 23 | if (user) return user; 24 | } 25 | } -------------------------------------------------------------------------------- /src/domain/use-cases/impl/authentication-service-impl.ts: -------------------------------------------------------------------------------- 1 | import {IAuthenticationService} from "@/domain/use-cases/authentication-service"; 2 | import {CHECK_EMAIL_REPOSITORY, ICheckEmailRepository} from "@/domain/models/gateways/check-email-repository"; 3 | import {HASH_COMPARE_REPOSITORY, IHashCompare} from "@/domain/models/gateways/hash-compare-repository"; 4 | import { 5 | IUpdateAccessTokenRepository, 6 | UPDATE_ACCESS_TOKEN_REPOSITORY 7 | } from "@/domain/models/gateways/update-access-token-repository"; 8 | import {ENCRYPT_REPOSITORY, IEncrypt} from "@/domain/models/gateways/encrypt-repository"; 9 | import {Adapter, Service} from "@tsclean/core"; 10 | 11 | @Service() 12 | export class AuthenticationServiceImpl implements IAuthenticationService { 13 | constructor( 14 | @Adapter(ENCRYPT_REPOSITORY) private readonly encrypt: IEncrypt, 15 | @Adapter(HASH_COMPARE_REPOSITORY) private readonly hashCompare: IHashCompare, 16 | @Adapter(CHECK_EMAIL_REPOSITORY) private readonly checkEmailRepository: ICheckEmailRepository, 17 | // @Adapter(UPDATE_ACCESS_TOKEN_REPOSITORY) private readonly updateAccessTokenRepository: IUpdateAccessTokenRepository 18 | ) { 19 | } 20 | 21 | async auth(data: IAuthenticationService.Params): Promise { 22 | const account = await this.checkEmailRepository.checkEmail(data.email); 23 | const isValid = await this.hashCompare.compare(data.password, account.password); 24 | if (isValid) { 25 | const accessToken = await this.encrypt.encrypt(account.id, account.roles); 26 | // await this.updateAccessTokenRepository.updateToken(account.id, accessToken); 27 | return { 28 | accessToken, 29 | name: account.firstName 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | } -------------------------------------------------------------------------------- /src/domain/use-cases/impl/get-users-service-impl.ts: -------------------------------------------------------------------------------- 1 | import {IGetUsersService} from "@/domain/use-cases/get-users-service"; 2 | import {UserModel} from "@/domain/models/user"; 3 | import {GET_USERS_REPOSITORY, IGetUsersRepository} from "@/domain/models/gateways/get-users-repository"; 4 | import {Adapter, Service} from "@tsclean/core"; 5 | 6 | @Service() 7 | export class GetUsersServiceImpl implements IGetUsersService { 8 | constructor( 9 | @Adapter(GET_USERS_REPOSITORY) private readonly getUsersRepository: IGetUsersRepository 10 | ) { 11 | } 12 | 13 | async getUsersService(): Promise { 14 | return await this.getUsersRepository.getUsersRepository(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/domain/use-cases/impl/load-account-token-service-impl.ts: -------------------------------------------------------------------------------- 1 | import {ILoadAccountTokenService} from "@/domain/use-cases/load-account-token-service"; 2 | import { 3 | ILoadAccountTokenRepository, 4 | LOAD_ACCOUNT_TOKEN_REPOSITORY 5 | } from "@/domain/models/gateways/load-account-token-repository"; 6 | import {DECRYPT_REPOSITORY, IDecrypt} from "@/domain/models/gateways/decryp-repositoryt"; 7 | import {Service, Adapter} from "@tsclean/core"; 8 | 9 | @Service() 10 | export class LoadAccountTokenServiceImpl implements ILoadAccountTokenService { 11 | 12 | constructor( 13 | @Adapter(DECRYPT_REPOSITORY) private readonly decrypt: IDecrypt, 14 | @Adapter(LOAD_ACCOUNT_TOKEN_REPOSITORY) private readonly loadAccountTokenRepository: ILoadAccountTokenRepository 15 | ) { 16 | } 17 | 18 | async loadToken(token: string): Promise { 19 | let accessToken: string; 20 | 21 | try { 22 | accessToken = await this.decrypt.decrypt(token); 23 | } catch (e) { 24 | return null; 25 | } 26 | 27 | if (accessToken) { 28 | const account = await this.loadAccountTokenRepository.loadToken(accessToken["account"]); 29 | console.log("service", account) 30 | if (account) return account; 31 | } 32 | 33 | return null; 34 | } 35 | } -------------------------------------------------------------------------------- /src/domain/use-cases/load-account-token-service.ts: -------------------------------------------------------------------------------- 1 | export const LOAD_ACCOUNT_TOKEN_SERVICE = "LOAD_ACCOUNT_TOKEN_SERVICE"; 2 | 3 | export interface ILoadAccountTokenService { 4 | loadToken:(token: string) => Promise 5 | } 6 | 7 | export namespace ILoadAccountTokenService { 8 | export type Result = { 9 | id: string; 10 | roles: []; 11 | } 12 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import 'module-alias/register' 2 | 3 | import helmet from 'helmet'; 4 | import {connect} from 'mongoose'; 5 | 6 | import {AppContainer} from "@/application/app"; 7 | import {MONGODB_URI, PORT} from "@/application/config/environment"; 8 | import {StartProjectInit} from "@tsclean/core"; 9 | 10 | async function run(): Promise { 11 | await connect(MONGODB_URI); 12 | console.log('DB Mongo connected') 13 | const app = await StartProjectInit.create(AppContainer); 14 | app.use(helmet()); 15 | await app.listen(PORT, () => console.log('Running on port: ' + PORT)) 16 | } 17 | 18 | run(); 19 | -------------------------------------------------------------------------------- /src/infrastructure/driven-adapters/adapters/bcrypt-adapter.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcrypt"; 2 | import {IHashCompare} from "@/domain/models/gateways/hash-compare-repository"; 3 | import {IHashRepository} from "@/domain/models/gateways/hash-repository"; 4 | 5 | export class BcryptAdapter implements IHashRepository, IHashCompare { 6 | private readonly salt: number = 12; 7 | 8 | constructor() { 9 | } 10 | 11 | async compare(text: string, verify: string): Promise { 12 | return await bcrypt.compare(text, verify); 13 | } 14 | 15 | async hash(text: string): Promise { 16 | return await bcrypt.hash(text, this.salt); 17 | } 18 | } -------------------------------------------------------------------------------- /src/infrastructure/driven-adapters/adapters/jwt-adapter.ts: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import dotenv from "dotenv"; 3 | import {IEncrypt} from "@/domain/models/gateways/encrypt-repository"; 4 | import {AccessResourceInterface, ExecutionContextInterface} from "@tsclean/core"; 5 | 6 | dotenv.config({path: ".env"}) 7 | 8 | export class JwtAdapter implements IEncrypt, AccessResourceInterface { 9 | 10 | constructor( 11 | private readonly roles: string[] 12 | ) { 13 | } 14 | 15 | async encrypt(text: string | number | Buffer, roles: []): Promise { 16 | const payload = {id: text, roles: roles} 17 | return jwt.sign({account: payload}, process.env.JWT_SECRET, {expiresIn: "1d"}); 18 | } 19 | 20 | accessResource(context: ExecutionContextInterface): boolean | Promise { 21 | try { 22 | const request = context.getHttp().getRequest(); 23 | const token = request.rawHeaders[1].split(" ")[1]; 24 | 25 | if (token) { 26 | const decode = jwt.verify(token, process.env.JWT_SECRET); 27 | const roles = decode["account"].roles; 28 | 29 | let assignRole = false; 30 | 31 | for (const role of roles) { 32 | assignRole = false; 33 | for (const roleElement of this.roles) { 34 | let roleExist = role.role === roleElement; 35 | if (roleExist) assignRole = roleExist; 36 | if (assignRole) return true; 37 | } 38 | } 39 | } 40 | } catch (e) { 41 | return false; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/infrastructure/driven-adapters/adapters/orm/mongoose/models/user.ts: -------------------------------------------------------------------------------- 1 | import { UserModel } from '@/domain/models/user'; 2 | import { model, Schema } from "mongoose"; 3 | 4 | const schema = new Schema({ 5 | id: String, 6 | firstName: String, 7 | lastName: String, 8 | email: String, 9 | password: String, 10 | roles: [{ role: String }] 11 | }); 12 | 13 | export const UserModelSchema = model('users', schema); 14 | -------------------------------------------------------------------------------- /src/infrastructure/driven-adapters/adapters/orm/mongoose/user-mongoose-repository-adapter.ts: -------------------------------------------------------------------------------- 1 | import {AddUserParams, UserModel} from "@/domain/models/user"; 2 | import {UserModelSchema} from "@/infrastructure/driven-adapters/adapters/orm/mongoose/models/user"; 3 | import {IAddUserRepository} from "@/domain/models/gateways/add-user-repository"; 4 | import {IGetUsersRepository} from "@/domain/models/gateways/get-users-repository"; 5 | import {ICheckEmailRepository} from "@/domain/models/gateways/check-email-repository"; 6 | import {IUpdateAccessTokenRepository} from "@/domain/models/gateways/update-access-token-repository"; 7 | import {ILoadAccountTokenRepository} from "@/domain/models/gateways/load-account-token-repository"; 8 | import mongoose from "mongoose"; 9 | 10 | export class UserMongooseRepositoryAdapter implements IAddUserRepository, 11 | IGetUsersRepository, 12 | ICheckEmailRepository, 13 | IUpdateAccessTokenRepository, 14 | ILoadAccountTokenRepository { 15 | 16 | map(data: any): any { 17 | const {_id, firstName, lastName, email, password, roles} = data 18 | return Object.assign({}, {id: _id.toString(), firstName, lastName, email, password, roles}) 19 | } 20 | 21 | 22 | async getUsersRepository(): Promise { 23 | return UserModelSchema.find().select("-password"); 24 | } 25 | 26 | async checkEmail(email: string): Promise { 27 | const user = await UserModelSchema.findOne({email}).exec(); 28 | return user && this.map(user); 29 | } 30 | 31 | async updateToken(id: string | number, token: string): Promise { 32 | await UserModelSchema.updateOne({ 33 | _id: id 34 | }, { 35 | $set: { 36 | accessToken: token 37 | } 38 | }, { 39 | upsert: true 40 | } 41 | ); 42 | } 43 | 44 | async loadToken(token: string): Promise { 45 | console.log("adapter", token) 46 | let objectFilter: {} 47 | objectFilter["_id"] = new mongoose.mongo.ObjectId(token) 48 | console.log(objectFilter) 49 | const result = await UserModelSchema.findOne(objectFilter); 50 | return this.map(result); 51 | } 52 | 53 | async addUserRepository(data: AddUserParams): Promise { 54 | return await UserModelSchema.create(data); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/infrastructure/driven-adapters/providers/index.ts: -------------------------------------------------------------------------------- 1 | import {BcryptAdapter} from "@/infrastructure/driven-adapters/adapters/bcrypt-adapter"; 2 | import {UserMongooseRepositoryAdapter} from "@/infrastructure/driven-adapters/adapters/orm/mongoose/user-mongoose-repository-adapter"; 3 | import {ADD_USER_REPOSITORY} from "@/domain/models/gateways/add-user-repository"; 4 | import {GET_USERS_REPOSITORY} from "@/domain/models/gateways/get-users-repository"; 5 | import {CHECK_EMAIL_REPOSITORY} from "@/domain/models/gateways/check-email-repository"; 6 | import {AddUserServiceImpl} from "@/domain/use-cases/impl/add-user-service-impl"; 7 | import {ADD_USER_SERVICE} from "@/domain/use-cases/add-user-service"; 8 | import {GetUsersServiceImpl} from "@/domain/use-cases/impl/get-users-service-impl"; 9 | import {GET_USERS_SERVICE} from "@/domain/use-cases/get-users-service"; 10 | import {UPDATE_ACCESS_TOKEN_REPOSITORY} from "@/domain/models/gateways/update-access-token-repository"; 11 | import {HASH_COMPARE_REPOSITORY} from "@/domain/models/gateways/hash-compare-repository"; 12 | import {AuthenticationServiceImpl} from "@/domain/use-cases/impl/authentication-service-impl"; 13 | import {AUTHENTICATION_SERVICE} from "@/domain/use-cases/authentication-service"; 14 | import {HASH_REPOSITORY} from "@/domain/models/gateways/hash-repository"; 15 | import {JwtAdapter} from "@/infrastructure/driven-adapters/adapters/jwt-adapter"; 16 | import {ENCRYPT_REPOSITORY} from "@/domain/models/gateways/encrypt-repository"; 17 | import {DECRYPT_REPOSITORY} from "@/domain/models/gateways/decryp-repositoryt"; 18 | 19 | export const adapters = [ 20 | { 21 | provide: HASH_REPOSITORY, 22 | useClass: BcryptAdapter, 23 | 24 | }, 25 | { 26 | provide: ADD_USER_REPOSITORY, 27 | useClass: UserMongooseRepositoryAdapter, 28 | }, 29 | { 30 | provide: GET_USERS_REPOSITORY, 31 | useClass: UserMongooseRepositoryAdapter, 32 | }, 33 | { 34 | provide: CHECK_EMAIL_REPOSITORY, 35 | useClass: UserMongooseRepositoryAdapter, 36 | }, 37 | { 38 | provide: UPDATE_ACCESS_TOKEN_REPOSITORY, 39 | useClass: UserMongooseRepositoryAdapter, 40 | }, 41 | { 42 | provide: HASH_COMPARE_REPOSITORY, 43 | useClass: BcryptAdapter, 44 | }, 45 | { 46 | provide: ENCRYPT_REPOSITORY, 47 | useClass: JwtAdapter, 48 | }, 49 | { 50 | provide: DECRYPT_REPOSITORY, 51 | useClass: JwtAdapter, 52 | } 53 | ] 54 | 55 | export const services = [ 56 | { 57 | 58 | provide: ADD_USER_SERVICE, 59 | useClass: AddUserServiceImpl, 60 | }, 61 | { 62 | 63 | provide: GET_USERS_SERVICE, 64 | useClass: GetUsersServiceImpl, 65 | }, 66 | { 67 | 68 | provide: AUTHENTICATION_SERVICE, 69 | useClass: AuthenticationServiceImpl, 70 | } 71 | ] 72 | 73 | -------------------------------------------------------------------------------- /src/infrastructure/entry-points/api/add-user-controller.ts: -------------------------------------------------------------------------------- 1 | import {Adapter, Body, Mapping, Post} from "@tsclean/core"; 2 | import {AddUserParams} from "@/domain/models/user"; 3 | import {ADD_USER_SERVICE, IAddUserService} from "@/domain/use-cases/add-user-service"; 4 | import {ValidateFields} from "@/infrastructure/helpers/validate-fields"; 5 | import {Auth} from "@/infrastructure/helpers/auth"; 6 | 7 | @Mapping('api/v1/add-user') 8 | export class AddUserController { 9 | 10 | constructor( 11 | @Adapter(ADD_USER_SERVICE) private readonly addUserService: IAddUserService 12 | ) { 13 | } 14 | 15 | @Post() 16 | @Auth(["admin"]) 17 | async addUserController(@Body() data: AddUserParams): Promise { 18 | 19 | const {errors, isValid} = ValidateFields.fieldsValidation(data); 20 | 21 | if (!isValid) return {statusCode: 422, body: {"message": errors}} 22 | 23 | const account = await this.addUserService.addUserService(data); 24 | 25 | if (account === true) return {statusCode: 400, body: {"message": "Email is already in use"}} 26 | 27 | return account; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/infrastructure/entry-points/api/authentication-controller.ts: -------------------------------------------------------------------------------- 1 | import {Post, Body, Inject, Mapping} from "@tsclean/core"; 2 | import {ValidateFields} from "@/infrastructure/helpers/validate-fields"; 3 | import {AUTHENTICATION_SERVICE, IAuthenticationService} from "@/domain/use-cases/authentication-service"; 4 | 5 | @Mapping('api/v1/authentication') 6 | export class AuthenticationController { 7 | 8 | constructor( 9 | @Inject(AUTHENTICATION_SERVICE) private readonly authenticationService: IAuthenticationService 10 | ) { 11 | } 12 | 13 | @Post() 14 | async authController(@Body() data: IAuthenticationService.Params): Promise { 15 | 16 | const {errors, isValid} = ValidateFields.fieldsValidation(data); 17 | 18 | if (!isValid) return {statusCode: 422, body: {"message": errors}} 19 | 20 | const result = await this.authenticationService.auth(data); 21 | 22 | if (result === null) return {statusCode: 401, body: {"message": "Invalid credentials"}} 23 | 24 | return { 25 | accessToken: result.accessToken, 26 | name: result.name 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/infrastructure/entry-points/api/get-users-controller.ts: -------------------------------------------------------------------------------- 1 | import {Adapter, Get, Mapping} from "@tsclean/core"; 2 | import {Auth} from "@/infrastructure/helpers/auth"; 3 | import {GET_USERS_SERVICE, IGetUsersService} from "@/domain/use-cases/get-users-service"; 4 | 5 | @Mapping('api/v1/get-users') 6 | export class GetUsersController { 7 | 8 | constructor( 9 | @Adapter(GET_USERS_SERVICE) private readonly getUsersService: IGetUsersService 10 | ) { 11 | } 12 | 13 | @Get() 14 | @Auth(["admin", "guest"]) 15 | async getUsersController(): Promise { 16 | return await this.getUsersService.getUsersService(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/infrastructure/entry-points/api/index.ts: -------------------------------------------------------------------------------- 1 | import {AddUserController} from "@/infrastructure/entry-points/api/add-user-controller"; 2 | import {GetUsersController} from "@/infrastructure/entry-points/api/get-users-controller"; 3 | import {AuthenticationController} from "@/infrastructure/entry-points/api/authentication-controller"; 4 | 5 | export const controllers = [ 6 | AddUserController, 7 | GetUsersController, 8 | AuthenticationController 9 | ] -------------------------------------------------------------------------------- /src/infrastructure/helpers/auth.ts: -------------------------------------------------------------------------------- 1 | import {applyDecorators, AccessResource} from "@tsclean/core"; 2 | import {JwtAdapter} from "@/infrastructure/driven-adapters/adapters/jwt-adapter"; 3 | 4 | export function Auth(roles: string[]) { 5 | return applyDecorators(AccessResource(new JwtAdapter(roles))); 6 | } 7 | -------------------------------------------------------------------------------- /src/infrastructure/helpers/validate-fields.ts: -------------------------------------------------------------------------------- 1 | export const REGEX = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/ 2 | 3 | export class ValidateFields { 4 | 5 | static fieldsValidation(data: any) { 6 | let errors = {} 7 | for (const key in data) { 8 | if (ValidateFields.isFieldEmpty(data[key])) { 9 | errors[key] = `${key} field is required` 10 | } else if (key === "email" && !REGEX.test(data[key])) { 11 | errors[key] = `${key} is invalid` 12 | } 13 | } 14 | 15 | return { errors, isValid: ValidateFields.isFieldEmpty(errors) } 16 | } 17 | 18 | private static isFieldEmpty (value: any): boolean { 19 | if (value === undefined || value === null || 20 | typeof value === "object" && Object.keys(value).length === 0 || 21 | typeof value === "string" && value.trim().length === 0) { 22 | return true 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "coverage", 5 | "jest.config.js", 6 | "**/*.spec.ts", 7 | "**/*.test.ts", 8 | "**/tests" 9 | ] 10 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "emitDecoratorMetadata": true, 5 | "outDir": "./dist", 6 | "module": "commonjs", 7 | "target": "es2019", 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "rootDirs": [ 11 | "src", 12 | "tests" 13 | ], 14 | "baseUrl": "src", 15 | "paths": { 16 | "@/tests/*": [ 17 | "../tests/*" 18 | ], 19 | "@/*": [ 20 | "*" 21 | ] 22 | } 23 | }, 24 | "include": [ 25 | "src", 26 | "tests" 27 | ], 28 | "exclude": [] 29 | } --------------------------------------------------------------------------------