├── .commitlintrc.json ├── .editorconfig ├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.json ├── .prettierignore ├── .prettierrc ├── README.md ├── jest-e2e.json ├── nest-cli.json ├── package.json ├── src ├── core │ ├── base │ │ ├── entity.ts │ │ ├── mapper.ts │ │ ├── repository.ts │ │ └── use-case.ts │ ├── domain │ │ ├── entities │ │ │ ├── index.ts │ │ │ ├── post.entity.ts │ │ │ └── user.entity.ts │ │ └── mappers │ │ │ ├── index.ts │ │ │ ├── post │ │ │ ├── create-post.mapper.spec.ts │ │ │ ├── create-post.mapper.ts │ │ │ ├── created-post.mapper.spec.ts │ │ │ ├── created-post.mapper.ts │ │ │ └── index.ts │ │ │ └── user │ │ │ ├── create-user.mapper.spec.ts │ │ │ ├── create-user.mapper.ts │ │ │ ├── created-user.mapper.spec.ts │ │ │ ├── created-user.mapper.ts │ │ │ └── index.ts │ └── repositories │ │ ├── index.ts │ │ ├── post.repository.ts │ │ └── user.repository.ts ├── infra │ ├── data │ │ ├── cache-memory │ │ │ ├── posts-cache-memory.repository.ts │ │ │ ├── repository-cache-memory.ts │ │ │ └── users-cache-memory.repository.ts │ │ └── prisma │ │ │ ├── migrations │ │ │ ├── 20220619132857_init │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ │ ├── posts-prisma.repository.ts │ │ │ ├── prisma.service.ts │ │ │ ├── schema.prisma │ │ │ └── users-prisma.repository.ts │ └── framework │ │ ├── app.e2e-spec.ts │ │ ├── app.module.ts │ │ ├── main.ts │ │ ├── post │ │ ├── post.controller.spec.ts │ │ ├── post.controller.ts │ │ └── post.module.ts │ │ └── user │ │ ├── user.controller.spec.ts │ │ ├── user.controller.ts │ │ └── user.module.ts ├── shared │ └── dtos │ │ ├── post │ │ ├── create-post.dto.ts │ │ ├── created-post.dto.ts │ │ └── index.ts │ │ └── user │ │ ├── create-user.dto.ts │ │ ├── created-user.dto.ts │ │ └── index.ts └── use-cases │ ├── post │ ├── create-post-use-case │ │ ├── create-post-use-case.spec.ts │ │ ├── create-post-use-case.ts │ │ └── index.ts │ ├── get-all-posts-use-case │ │ ├── get-all-posts-use-case.spec.ts │ │ ├── get-all-posts-use-case.ts │ │ └── index.ts │ └── index.ts │ └── user │ ├── create-user-use-case │ ├── create-user.use-case.spec.ts │ ├── create-user.use-case.ts │ └── index.ts │ ├── get-all-users.use-case │ ├── get-all-users.use-case.spec.ts │ ├── get-all-users.use-case.ts │ └── index.ts │ └── index.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.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 = false 9 | insert_final_newline = false 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "project": "tsconfig.json", 5 | "sourceType": "module" 6 | }, 7 | "plugins": ["@typescript-eslint/eslint-plugin", "prettier", "simple-import-sort"], 8 | "extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], 9 | "root": true, 10 | "env": { 11 | "node": true, 12 | "jest": true 13 | }, 14 | "rules": { 15 | "@typescript-eslint/interface-name-prefix": "off", 16 | "@typescript-eslint/explicit-function-return-type": "off", 17 | "@typescript-eslint/explicit-module-boundary-types": "off", 18 | "@typescript-eslint/no-explicit-any": "off", 19 | "@typescript-eslint/no-unused-vars": "error", 20 | "prettier/prettier": "error", 21 | "simple-import-sort/imports": "error", 22 | "simple-import-sort/exports": "error" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | install: 13 | name: Installing Packages 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Cache node_modules 20 | uses: actions/cache@v2 21 | with: 22 | path: node_modules 23 | key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} 24 | 25 | - name: Install dependencies 26 | run: yarn install 27 | 28 | check-linters: 29 | name: Check linters 30 | runs-on: ubuntu-latest 31 | needs: install 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v3 35 | 36 | - name: Restore node_modules 37 | uses: actions/cache@v2 38 | with: 39 | path: node_modules 40 | key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} 41 | 42 | - name: Run test:lint 43 | run: yarn test:lint 44 | 45 | testing: 46 | name: Jest 47 | runs-on: ubuntu-latest 48 | needs: install 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v3 52 | 53 | - name: Restore node_modules 54 | uses: actions/cache@v2 55 | with: 56 | path: node_modules 57 | key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} 58 | 59 | - name: Run tests and collect coverage 60 | run: yarn test:cov 61 | 62 | - name: Upload coverage reports to Codecov 63 | uses: codecov/codecov-action@v3 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # environment 6 | .env 7 | 8 | # database 9 | *.db 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | pnpm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | lerna-debug.log* 19 | 20 | # OS 21 | .DS_Store 22 | 23 | # Tests 24 | /coverage 25 | /.nyc_output 26 | 27 | # IDEs and editors 28 | /.idea 29 | .project 30 | .classpath 31 | .c9/ 32 | *.launch 33 | .settings/ 34 | *.sublime-workspace 35 | 36 | # IDE - VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | yarn pretty-quick --staged 6 | # yarn test:ci 7 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.ts": "eslint --fix" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.hbs 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true, 6 | "arrowParens": "avoid", 7 | "printWidth": 100 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | # Nest.js Clean Architecture 6 | 7 | [![codecov](https://codecov.io/gh/dsoaress/nest-clean-architecture/branch/main/graph/badge.svg?token=N3L06J3GQN)](https://codecov.io/gh/dsoaress/nest-clean-architecture) 8 | 9 | ## Description 10 | 11 | A proof of concept of applying Clean Architecture in Nest.js applications. 12 | 13 | ## Installation 14 | 15 | ```bash 16 | $ npm install 17 | ``` 18 | 19 | ## Running the app 20 | 21 | ```bash 22 | # development 23 | $ npm run start 24 | 25 | # watch mode 26 | $ npm run start:dev 27 | 28 | # production mode 29 | $ npm run start:prod 30 | ``` 31 | 32 | ## API Reference 33 | 34 | #### Get all users 35 | 36 | ```http 37 | GET /users 38 | ``` 39 | 40 | #### Create a user 41 | 42 | ```http 43 | POST /users 44 | ``` 45 | 46 | ```json 47 | { 48 | "name": "Jane Doe", 49 | "email": "jane@doe.com", 50 | "password": "123456" 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | }, 9 | "moduleNameMapper": { 10 | "^@/core(.*)$": "/core$1", 11 | "^@/data(.*)$": "/data$1", 12 | "^@/infra(.*)$": "/infra$1", 13 | "^@/shared(.*)$": "/shared$1", 14 | "^@/use-cases(.*)$": "/use-cases$1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src/infra/framework", 5 | "entryFile": "infra/framework/main.js" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-clean-architecture", 3 | "version": "0.0.1", 4 | "description": "Nest.js with clean architecture", 5 | "author": "Daniel Soares ", 6 | "private": true, 7 | "license": "MIT", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "prestart:dev": "rimraf dist", 14 | "start:dev": "nest start --watch", 15 | "start:debug": "nest start --debug --watch", 16 | "start:prod": "node dist/infra/framework/main.js", 17 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 18 | "test:lint": "eslint \"{src,apps,libs,test}/**/*.ts\"", 19 | "test": "jest", 20 | "test:watch": "jest --watch", 21 | "test:cov": "jest --coverage", 22 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 23 | "test:e2e": "jest --config ./jest-e2e.json", 24 | "prisma:format": "prisma format --schema ./src/infra/data/prisma/schema.prisma", 25 | "prisma:generate": "prisma generate --schema ./src/infra/data/prisma/schema.prisma", 26 | "prisma:migrate": "prisma migrate dev --schema ./src/infra/data/prisma/schema.prisma", 27 | "prepare": "husky install" 28 | }, 29 | "dependencies": { 30 | "@nestjs/common": "8.4.5", 31 | "@nestjs/core": "8.4.5", 32 | "@nestjs/mapped-types": "*", 33 | "@nestjs/platform-express": "8.4.5", 34 | "@prisma/client": "^3.15.2", 35 | "reflect-metadata": "0.1.13", 36 | "rimraf": "3.0.2", 37 | "rxjs": "7.5.5" 38 | }, 39 | "devDependencies": { 40 | "@commitlint/cli": "17.0.1", 41 | "@commitlint/config-conventional": "17.0.0", 42 | "@nestjs/cli": "8.2.6", 43 | "@nestjs/schematics": "8.0.11", 44 | "@nestjs/testing": "8.4.5", 45 | "@types/express": "4.17.13", 46 | "@types/jest": "27.5.1", 47 | "@types/node": "17.0.35", 48 | "@types/supertest": "2.0.12", 49 | "@typescript-eslint/eslint-plugin": "5.26.0", 50 | "@typescript-eslint/parser": "5.26.0", 51 | "eslint": "8.16.0", 52 | "eslint-config-prettier": "8.5.0", 53 | "eslint-plugin-prettier": "4.0.0", 54 | "eslint-plugin-simple-import-sort": "7.0.0", 55 | "husky": "8.0.1", 56 | "jest": "28.1.0", 57 | "lint-staged": "12.4.2", 58 | "prettier": "2.6.2", 59 | "pretty-quick": "3.1.3", 60 | "prisma": "^3.15.2", 61 | "source-map-support": "0.5.21", 62 | "supertest": "6.2.3", 63 | "ts-jest": "28.0.3", 64 | "ts-loader": "9.3.0", 65 | "ts-node": "10.8.0", 66 | "tsconfig-paths": "4.0.0", 67 | "typescript": "4.7.2", 68 | "webpack": "5.72.1" 69 | }, 70 | "jest": { 71 | "moduleFileExtensions": [ 72 | "js", 73 | "json", 74 | "ts" 75 | ], 76 | "rootDir": "src", 77 | "testRegex": ".*\\.spec\\.ts$", 78 | "transform": { 79 | "^.+\\.(t|j)s$": "ts-jest" 80 | }, 81 | "collectCoverageFrom": [ 82 | "**/*.(t|j)s", 83 | "!**/*.module.(t|j)s", 84 | "!**/*.e2e-spec.(t|j)s", 85 | "!infra/data/**/*.(t|j)s", 86 | "!**/main.(t|j)s" 87 | ], 88 | "moduleNameMapper": { 89 | "^@/core(.*)$": "/core$1", 90 | "^@/infra(.*)$": "/infra$1", 91 | "^@/shared(.*)$": "/shared$1", 92 | "^@/use-cases(.*)$": "/use-cases$1" 93 | }, 94 | "coverageDirectory": "../coverage", 95 | "testEnvironment": "node" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/core/base/entity.ts: -------------------------------------------------------------------------------- 1 | export class Entity { 2 | id?: number 3 | } 4 | -------------------------------------------------------------------------------- /src/core/base/mapper.ts: -------------------------------------------------------------------------------- 1 | export abstract class Mapper { 2 | abstract mapFrom(param: I): O 3 | abstract mapTo(param: O): I 4 | } 5 | -------------------------------------------------------------------------------- /src/core/base/repository.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from './entity' 2 | 3 | export abstract class Repository { 4 | abstract create(data: TEntity): Promise 5 | abstract update(id: number, data: TEntity): Promise 6 | abstract patch(id: number, data: Partial): Promise 7 | abstract getById(id: number): Promise 8 | abstract getAll(): Promise 9 | abstract getOne(filter: Partial): Promise 10 | abstract getMany(filter: Partial): Promise 11 | abstract delete(id: number): Promise 12 | } 13 | -------------------------------------------------------------------------------- /src/core/base/use-case.ts: -------------------------------------------------------------------------------- 1 | export interface UseCase { 2 | execute(...args: any[]): Promise 3 | } 4 | -------------------------------------------------------------------------------- /src/core/domain/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './post.entity' 2 | export * from './user.entity' 3 | -------------------------------------------------------------------------------- /src/core/domain/entities/post.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from '@/core/base/entity' 2 | 3 | export class PostEntity extends Entity { 4 | title: string 5 | content: string 6 | userId: number 7 | } 8 | -------------------------------------------------------------------------------- /src/core/domain/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from '@/core/base/entity' 2 | 3 | export class UserEntity extends Entity { 4 | name: string 5 | password: string 6 | email: string 7 | } 8 | -------------------------------------------------------------------------------- /src/core/domain/mappers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './post' 2 | export * from './user' 3 | -------------------------------------------------------------------------------- /src/core/domain/mappers/post/create-post.mapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { CreatePostMapper } from '.' 2 | 3 | describe('CreatePostMapper', () => { 4 | let createPostMapper: CreatePostMapper 5 | 6 | const title = 'Post title' 7 | const content = 'Post content' 8 | const userId = 1 9 | 10 | beforeEach(() => { 11 | createPostMapper = new CreatePostMapper() 12 | }) 13 | 14 | it('should be defined', () => { 15 | expect(createPostMapper).toBeDefined() 16 | }) 17 | 18 | it('should map from', () => { 19 | const post = createPostMapper.mapFrom({ title, content, userId }) 20 | expect(post).toEqual({ title, content, userId }) 21 | }) 22 | 23 | it('should map to', () => { 24 | const post = createPostMapper.mapTo({ id: 1, title, content, userId }) 25 | expect(post).toEqual({ id: 1, title, content, userId }) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /src/core/domain/mappers/post/create-post.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Mapper } from '@/core/base/mapper' 2 | import { PostEntity } from '@/core/domain/entities' 3 | import { CreatePostDto } from '@/shared/dtos/post' 4 | 5 | export class CreatePostMapper extends Mapper { 6 | public mapFrom(data: CreatePostDto): PostEntity { 7 | const post = new PostEntity() 8 | 9 | post.title = data.title 10 | post.content = data.content 11 | post.userId = data.userId 12 | 13 | return post 14 | } 15 | 16 | public mapTo(data: PostEntity): CreatePostDto { 17 | const post = new CreatePostDto() 18 | 19 | post.id = data.id 20 | post.title = data.title 21 | post.content = data.content 22 | post.userId = data.userId 23 | 24 | return post 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/core/domain/mappers/post/created-post.mapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { CreatedPostMapper } from '.' 2 | 3 | describe('CreatedPostMapper', () => { 4 | let createdPostMapper: CreatedPostMapper 5 | 6 | const id = 1 7 | const title = 'Post title' 8 | const content = 'Post content' 9 | const userId = 1 10 | 11 | beforeEach(() => { 12 | createdPostMapper = new CreatedPostMapper() 13 | }) 14 | 15 | it('should be defined', () => { 16 | expect(createdPostMapper).toBeDefined() 17 | }) 18 | 19 | it('should map from', () => { 20 | const post = createdPostMapper.mapFrom({ id, title, content, userId }) 21 | expect(post).toEqual({ id, title, content, userId }) 22 | }) 23 | 24 | it('should map to', () => { 25 | const post = createdPostMapper.mapTo({ id, title, content, userId }) 26 | expect(post).toEqual({ id, title, content, userId }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/core/domain/mappers/post/created-post.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Mapper } from '@/core/base/mapper' 2 | import { PostEntity } from '@/core/domain/entities' 3 | import { CreatedPostDto } from '@/shared/dtos/post' 4 | 5 | export class CreatedPostMapper implements Mapper { 6 | public mapFrom(data: CreatedPostDto): PostEntity { 7 | const post = new PostEntity() 8 | 9 | post.id = data.id 10 | post.title = data.title 11 | post.content = data.content 12 | post.userId = data.userId 13 | 14 | return post 15 | } 16 | 17 | public mapTo(data: PostEntity): CreatedPostDto { 18 | const post = new CreatedPostDto() 19 | 20 | post.id = data.id 21 | post.title = data.title 22 | post.content = data.content 23 | post.userId = data.userId 24 | 25 | return post 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/core/domain/mappers/post/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-post.mapper' 2 | export * from './created-post.mapper' 3 | -------------------------------------------------------------------------------- /src/core/domain/mappers/user/create-user.mapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserMapper } from '.' 2 | 3 | describe('CreateUserMapper', () => { 4 | let createUserMapper: CreateUserMapper 5 | 6 | const name = 'John Doe' 7 | const email = 'jdoe@test.com' 8 | const password = '123456' 9 | 10 | beforeEach(() => { 11 | createUserMapper = new CreateUserMapper() 12 | }) 13 | 14 | it('should be defined', () => { 15 | expect(createUserMapper).toBeDefined() 16 | }) 17 | 18 | it('should map from', () => { 19 | const user = createUserMapper.mapFrom({ name, email, password }) 20 | expect(user).toEqual({ name, email, password }) 21 | }) 22 | 23 | it('should map to', () => { 24 | const user = createUserMapper.mapTo({ id: 1, name, email, password }) 25 | expect(user).toEqual({ id: 1, name, email, password }) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /src/core/domain/mappers/user/create-user.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Mapper } from '@/core/base/mapper' 2 | import { UserEntity } from '@/core/domain/entities' 3 | import { CreateUserDto } from '@/shared/dtos/user' 4 | 5 | export class CreateUserMapper extends Mapper { 6 | public mapFrom(data: CreateUserDto): UserEntity { 7 | const user = new UserEntity() 8 | 9 | user.name = data.name 10 | user.email = data.email 11 | user.password = data.password 12 | 13 | return user 14 | } 15 | 16 | public mapTo(data: UserEntity): CreateUserDto { 17 | const user = new CreateUserDto() 18 | 19 | user.id = data.id 20 | user.name = data.name 21 | user.email = data.email 22 | user.password = data.password 23 | 24 | return user 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/core/domain/mappers/user/created-user.mapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { CreatedUserMapper } from '.' 2 | 3 | describe('CreatedUserMapper', () => { 4 | let createdUserMapper: CreatedUserMapper 5 | 6 | const name = 'John Doe' 7 | const email = 'jdoe@test.com' 8 | const password = '123456' 9 | 10 | beforeEach(() => { 11 | createdUserMapper = new CreatedUserMapper() 12 | }) 13 | 14 | it('should be defined', () => { 15 | expect(createdUserMapper).toBeDefined() 16 | }) 17 | 18 | it('should map from', () => { 19 | const user = createdUserMapper.mapFrom({ id: 1, name, email }) 20 | expect(user).toEqual({ id: 1, name, email }) 21 | }) 22 | 23 | it('should map to', () => { 24 | const user = createdUserMapper.mapTo({ id: 1, name, email, password }) 25 | expect(user).toEqual({ id: 1, name, email }) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /src/core/domain/mappers/user/created-user.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Mapper } from '@/core/base/mapper' 2 | import { UserEntity } from '@/core/domain/entities' 3 | import { CreatedUserDto } from '@/shared/dtos/user' 4 | 5 | export class CreatedUserMapper implements Mapper { 6 | public mapFrom(data: CreatedUserDto): UserEntity { 7 | const user = new UserEntity() 8 | 9 | user.id = data.id 10 | user.name = data.name 11 | user.email = data.email 12 | 13 | return user 14 | } 15 | 16 | public mapTo(data: UserEntity): CreatedUserDto { 17 | const user = new CreatedUserDto() 18 | 19 | user.id = data.id 20 | user.name = data.name 21 | user.email = data.email 22 | 23 | return user 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/core/domain/mappers/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-user.mapper' 2 | export * from './created-user.mapper' 3 | -------------------------------------------------------------------------------- /src/core/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './post.repository' 2 | export * from './user.repository' 3 | -------------------------------------------------------------------------------- /src/core/repositories/post.repository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '@/core/base/repository' 2 | import { PostEntity } from '@/core/domain/entities' 3 | 4 | export abstract class PostRepository extends Repository {} 5 | -------------------------------------------------------------------------------- /src/core/repositories/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '@/core/base/repository' 2 | import { UserEntity } from '@/core/domain/entities' 3 | 4 | export abstract class UserRepository extends Repository {} 5 | -------------------------------------------------------------------------------- /src/infra/data/cache-memory/posts-cache-memory.repository.ts: -------------------------------------------------------------------------------- 1 | import { PostEntity } from '@/core/domain/entities' 2 | import { PostRepository } from '@/core/repositories' 3 | 4 | import { RepositoryCacheMemory } from './repository-cache-memory' 5 | 6 | export class PostsCacheMemoryRepository 7 | extends RepositoryCacheMemory 8 | implements PostRepository {} 9 | -------------------------------------------------------------------------------- /src/infra/data/cache-memory/repository-cache-memory.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from '@/core/base/entity' 2 | import { Repository } from '@/core/base/repository' 3 | 4 | export class RepositoryCacheMemory extends Repository { 5 | protected readonly items: TEntity[] 6 | 7 | constructor() { 8 | super() 9 | this.items = [] 10 | } 11 | 12 | async create(data: TEntity): Promise { 13 | data.id = this.items.length > 0 ? this.items.slice(-1)[0].id + 1 : 1 14 | 15 | const count = this.items.push(data) 16 | 17 | return this.items[count - 1] 18 | } 19 | 20 | async update(id: number, data: TEntity): Promise { 21 | const index = this.getIndexById(id) 22 | 23 | if (index === -1) { 24 | // TODO: handle the case of not finding the item to update 25 | } 26 | 27 | this.items[index] = data 28 | 29 | return this.items[index] 30 | } 31 | 32 | async patch(id: number, data: Partial): Promise { 33 | const index = this.getIndexById(id) 34 | 35 | if (index === -1) { 36 | // TODO: handle the case of not finding the item to update 37 | } 38 | 39 | for (const key in data) { 40 | this.items[index][key] = data[key] 41 | } 42 | 43 | return this.items[index] 44 | } 45 | 46 | async getById(id: number): Promise { 47 | const items = this.items.find(item => item.id === id) 48 | 49 | return items 50 | } 51 | 52 | async getAll(): Promise { 53 | return this.items 54 | } 55 | 56 | async getOne(filter: Partial): Promise { 57 | return this.getMany(filter).then(items => (items.length > 0 ? items[0] : null)) 58 | } 59 | 60 | async getMany(filter: Partial): Promise { 61 | let filtered = this.items 62 | 63 | for (const key in filter) { 64 | filtered = filtered.filter(item => item[key] === filter[key]) 65 | } 66 | 67 | return filtered 68 | } 69 | 70 | async delete(id: number): Promise { 71 | const index = this.getIndexById(id) 72 | 73 | if (index === -1) { 74 | // TODO: handle the case of not finding the item to be deleted 75 | } 76 | 77 | this.items.splice(index, 1) 78 | } 79 | 80 | private getIndexById(id: number) { 81 | return this.items.findIndex(item => item.id === id) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/infra/data/cache-memory/users-cache-memory.repository.ts: -------------------------------------------------------------------------------- 1 | import { UserEntity } from '@/core/domain/entities' 2 | import { UserRepository } from '@/core/repositories' 3 | 4 | import { RepositoryCacheMemory } from './repository-cache-memory' 5 | 6 | export class UsersCacheMemoryRepository 7 | extends RepositoryCacheMemory 8 | implements UserRepository {} 9 | -------------------------------------------------------------------------------- /src/infra/data/prisma/migrations/20220619132857_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "users" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "name" TEXT NOT NULL, 5 | "email" TEXT NOT NULL, 6 | "password" TEXT NOT NULL 7 | ); 8 | 9 | -- CreateTable 10 | CREATE TABLE "posts" ( 11 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 12 | "title" TEXT NOT NULL, 13 | "content" TEXT NOT NULL, 14 | "user_id" INTEGER NOT NULL, 15 | CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 16 | ); 17 | -------------------------------------------------------------------------------- /src/infra/data/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /src/infra/data/prisma/posts-prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '@/core/base/repository' 2 | import { PostEntity } from '@/core/domain/entities' 3 | 4 | import { PrismaService } from './prisma.service' 5 | 6 | export class PostsPrismaRepository extends Repository { 7 | constructor(private readonly prisma: PrismaService) { 8 | super() 9 | } 10 | 11 | async create(data: PostEntity): Promise { 12 | return this.prisma.post.create({ data }) 13 | } 14 | 15 | async update(id: number, data: PostEntity): Promise { 16 | return this.prisma.post.update({ 17 | where: { id }, 18 | data 19 | }) 20 | } 21 | patch(id: number, data: Partial): Promise { 22 | return this.prisma.post.update({ 23 | where: { id }, 24 | data 25 | }) 26 | } 27 | 28 | async getById(id: number): Promise { 29 | return await this.prisma.post.findUnique({ where: { id } }) 30 | } 31 | 32 | async getAll(): Promise { 33 | return await this.prisma.post.findMany() 34 | } 35 | 36 | async getOne(filter: Partial): Promise { 37 | return await this.getMany(filter).then(items => (items.length > 0 ? items[0] : null)) 38 | } 39 | 40 | async getMany(filter: Partial): Promise { 41 | return await this.prisma.post.findMany({ where: filter }) 42 | } 43 | 44 | async delete(id: number): Promise { 45 | await this.prisma.post.delete({ where: { id } }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/infra/data/prisma/prisma.service.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common' 2 | import { PrismaClient } from '@prisma/client' 3 | 4 | @Injectable() 5 | export class PrismaService extends PrismaClient implements OnModuleInit { 6 | async onModuleInit() { 7 | await this.$connect() 8 | } 9 | 10 | async enableShutdownHooks(app: INestApplication) { 11 | this.$on('beforeExit', async () => { 12 | await app.close() 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/infra/data/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "sqlite" 7 | url = "file:./dev.db" 8 | } 9 | 10 | model User { 11 | id Int @id @default(autoincrement()) 12 | name String 13 | email String 14 | password String 15 | posts Post[] 16 | 17 | @@map("users") 18 | } 19 | 20 | model Post { 21 | id Int @id @default(autoincrement()) 22 | title String 23 | content String 24 | userId Int @map("user_id") 25 | user User @relation(fields: [userId], references: [id]) 26 | 27 | @@map("posts") 28 | } 29 | -------------------------------------------------------------------------------- /src/infra/data/prisma/users-prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '@/core/base/repository' 2 | import { UserEntity } from '@/core/domain/entities' 3 | 4 | import { PrismaService } from './prisma.service' 5 | 6 | export class UsersPrismaRepository extends Repository { 7 | constructor(private readonly prisma: PrismaService) { 8 | super() 9 | } 10 | 11 | async create(data: UserEntity): Promise { 12 | return await this.prisma.user.create({ data }) 13 | } 14 | 15 | async update(id: number, data: UserEntity): Promise { 16 | return await this.prisma.user.update({ 17 | where: { id }, 18 | data 19 | }) 20 | } 21 | 22 | async patch(id: number, data: Partial): Promise { 23 | return await this.prisma.user.update({ 24 | where: { id }, 25 | data 26 | }) 27 | } 28 | 29 | async getById(id: number): Promise { 30 | return await this.prisma.user.findUnique({ where: { id } }) 31 | } 32 | 33 | async getAll(): Promise { 34 | return await this.prisma.user.findMany() 35 | } 36 | 37 | async getOne(filter: Partial): Promise { 38 | return await this.getMany(filter).then(items => (items.length > 0 ? items[0] : null)) 39 | } 40 | 41 | async getMany(filter: Partial): Promise { 42 | return await this.prisma.user.findMany({ where: filter }) 43 | } 44 | 45 | async delete(id: number): Promise { 46 | await this.prisma.user.delete({ where: { id } }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/infra/framework/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import { Test, TestingModule } from '@nestjs/testing' 3 | import * as request from 'supertest' 4 | 5 | import { AppModule } from './app.module' 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication 9 | 10 | beforeEach(async () => { 11 | const moduleFixture: TestingModule = await Test.createTestingModule({ 12 | imports: [AppModule] 13 | }).compile() 14 | 15 | app = moduleFixture.createNestApplication() 16 | await app.init() 17 | }) 18 | 19 | it('/users (GET)', () => { 20 | return request(app.getHttpServer()).get('/').expect(200).expect([]) 21 | }) 22 | 23 | it('/users (POST)', () => { 24 | return request(app.getHttpServer()).post('/').expect(201).expect({ id: 1 }) 25 | }) 26 | 27 | it('/posts (GET)', () => { 28 | return request(app.getHttpServer()).get('/').expect(200).expect([]) 29 | }) 30 | 31 | it('/posts (POST)', () => { 32 | return request(app.getHttpServer()).post('/').expect(201).expect({ id: 1 }) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /src/infra/framework/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Logger, Module } from '@nestjs/common' 2 | 3 | import { PostModule } from './post/post.module' 4 | import { UserModule } from './user/user.module' 5 | 6 | @Module({ 7 | providers: [Logger], 8 | imports: [UserModule, PostModule] 9 | }) 10 | export class AppModule {} 11 | -------------------------------------------------------------------------------- /src/infra/framework/main.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common' 2 | import { NestFactory } from '@nestjs/core' 3 | 4 | import { AppModule } from './app.module' 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule) 8 | const logger = app.get(Logger) 9 | 10 | await app.listen(3000).then(() => logger.log('Listening on port 3000', 'Bootstrap')) 11 | } 12 | 13 | bootstrap() 14 | -------------------------------------------------------------------------------- /src/infra/framework/post/post.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | 3 | import { PostRepository, UserRepository } from '@/core/repositories' 4 | import { PostsCacheMemoryRepository } from '@/infra/data/cache-memory/posts-cache-memory.repository' 5 | import { UsersCacheMemoryRepository } from '@/infra/data/cache-memory/users-cache-memory.repository' 6 | import { CreatePostUseCase, GetAllPostsUseCase } from '@/use-cases/post' 7 | import { CreateUserUseCase, GetAllUsersUseCase } from '@/use-cases/user' 8 | 9 | import { UserController } from '../user/user.controller' 10 | import { PostController } from './post.controller' 11 | 12 | describe('PostController', () => { 13 | let userController: UserController 14 | let postController: PostController 15 | 16 | const title = 'Post title' 17 | const content = 'Post content' 18 | const userId = 1 19 | 20 | const name = 'John Doe' 21 | const email = 'johndoe@example.com' 22 | const password = '123456' 23 | 24 | beforeEach(async () => { 25 | const userModule: TestingModule = await Test.createTestingModule({ 26 | controllers: [UserController], 27 | providers: [ 28 | { 29 | provide: UserRepository, 30 | useClass: UsersCacheMemoryRepository 31 | }, 32 | { 33 | provide: CreateUserUseCase, 34 | useFactory: (repository: UserRepository) => new CreateUserUseCase(repository), 35 | inject: [UserRepository] 36 | }, 37 | { 38 | provide: GetAllUsersUseCase, 39 | useFactory: (repository: UserRepository) => new GetAllUsersUseCase(repository), 40 | inject: [UserRepository] 41 | } 42 | ] 43 | }).compile() 44 | 45 | const postModule: TestingModule = await Test.createTestingModule({ 46 | controllers: [PostController], 47 | providers: [ 48 | { 49 | provide: PostRepository, 50 | useClass: PostsCacheMemoryRepository 51 | }, 52 | { 53 | provide: CreatePostUseCase, 54 | useFactory: (repository: PostRepository) => new CreatePostUseCase(repository), 55 | inject: [PostRepository] 56 | }, 57 | { 58 | provide: GetAllPostsUseCase, 59 | useFactory: (repository: PostRepository) => new GetAllPostsUseCase(repository), 60 | inject: [PostRepository] 61 | } 62 | ] 63 | }).compile() 64 | 65 | userController = userModule.get(UserController) 66 | postController = postModule.get(PostController) 67 | 68 | await userController.create({ name, email, password }) 69 | }) 70 | 71 | it('should be defined', () => { 72 | expect(postController).toBeDefined() 73 | }) 74 | 75 | it('should create a post', async () => { 76 | const post = await postController.create({ title, content, userId }) 77 | expect(post).toEqual({ id: 1, title, content, userId }) 78 | }) 79 | 80 | it('should get all posts', async () => { 81 | await postController.create({ title, content, userId }) 82 | const posts = await postController.findAll() 83 | expect(posts).toEqual([{ id: 1, title, content, userId }]) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /src/infra/framework/post/post.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post } from '@nestjs/common' 2 | 3 | import { CreatePostDto } from '@/shared/dtos/post' 4 | import { CreatePostUseCase, GetAllPostsUseCase } from '@/use-cases/post' 5 | 6 | @Controller('posts') 7 | export class PostController { 8 | constructor( 9 | private readonly createPostUseCase: CreatePostUseCase, 10 | private readonly getAllPostsUseCase: GetAllPostsUseCase 11 | ) {} 12 | 13 | @Post() 14 | create(@Body() createPostDto: CreatePostDto) { 15 | return this.createPostUseCase.execute(createPostDto) 16 | } 17 | 18 | @Get() 19 | findAll() { 20 | return this.getAllPostsUseCase.execute() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/infra/framework/post/post.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | 3 | import { PostRepository } from '@/core/repositories' 4 | import { PostsPrismaRepository } from '@/infra/data/prisma/posts-prisma.repository' 5 | import { PrismaService } from '@/infra/data/prisma/prisma.service' 6 | import { CreatePostUseCase, GetAllPostsUseCase } from '@/use-cases/post' 7 | 8 | import { PostController } from './post.controller' 9 | 10 | @Module({ 11 | controllers: [PostController], 12 | providers: [ 13 | PrismaService, 14 | { 15 | provide: PostRepository, 16 | useFactory: (prisma: PrismaService) => new PostsPrismaRepository(prisma), 17 | inject: [PrismaService] 18 | }, 19 | { 20 | provide: CreatePostUseCase, 21 | useFactory: (repository: PostRepository) => new CreatePostUseCase(repository), 22 | inject: [PostRepository] 23 | }, 24 | { 25 | provide: GetAllPostsUseCase, 26 | useFactory: (repository: PostRepository) => new GetAllPostsUseCase(repository), 27 | inject: [PostRepository] 28 | } 29 | ] 30 | }) 31 | export class PostModule {} 32 | -------------------------------------------------------------------------------- /src/infra/framework/user/user.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | 3 | import { UserRepository } from '@/core/repositories' 4 | import { UsersCacheMemoryRepository } from '@/infra/data/cache-memory/users-cache-memory.repository' 5 | import { CreateUserUseCase, GetAllUsersUseCase } from '@/use-cases/user' 6 | 7 | import { UserController } from './user.controller' 8 | 9 | describe('UserController', () => { 10 | let userController: UserController 11 | 12 | const name = 'John Doe' 13 | const email = 'johndoe@example.com' 14 | const password = '123456' 15 | 16 | beforeEach(async () => { 17 | const userModule: TestingModule = await Test.createTestingModule({ 18 | controllers: [UserController], 19 | providers: [ 20 | { 21 | provide: UserRepository, 22 | useClass: UsersCacheMemoryRepository 23 | }, 24 | { 25 | provide: CreateUserUseCase, 26 | useFactory: (repository: UserRepository) => new CreateUserUseCase(repository), 27 | inject: [UserRepository] 28 | }, 29 | { 30 | provide: GetAllUsersUseCase, 31 | useFactory: (repository: UserRepository) => new GetAllUsersUseCase(repository), 32 | inject: [UserRepository] 33 | } 34 | ] 35 | }).compile() 36 | 37 | userController = userModule.get(UserController) 38 | }) 39 | 40 | it('should be defined', () => { 41 | expect(userController).toBeDefined() 42 | }) 43 | 44 | it('should create a user', async () => { 45 | const user = await userController.create({ name, email, password }) 46 | expect(user).toEqual({ id: 1, name, email }) 47 | }) 48 | 49 | it('should get all users', async () => { 50 | await userController.create({ name, email, password }) 51 | const users = await userController.findAll() 52 | expect(users).toEqual([{ id: 1, name, email }]) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /src/infra/framework/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post } from '@nestjs/common' 2 | 3 | import { CreateUserDto } from '@/shared/dtos/user' 4 | import { CreateUserUseCase, GetAllUsersUseCase } from '@/use-cases/user' 5 | 6 | @Controller('users') 7 | export class UserController { 8 | constructor( 9 | private readonly createUserUseCase: CreateUserUseCase, 10 | private readonly getAllUsersUseCase: GetAllUsersUseCase 11 | ) {} 12 | 13 | @Post() 14 | create(@Body() createUserDto: CreateUserDto) { 15 | return this.createUserUseCase.execute(createUserDto) 16 | } 17 | 18 | @Get() 19 | findAll() { 20 | return this.getAllUsersUseCase.execute() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/infra/framework/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | 3 | import { UserRepository } from '@/core/repositories' 4 | import { PrismaService } from '@/infra/data/prisma/prisma.service' 5 | import { UsersPrismaRepository } from '@/infra/data/prisma/users-prisma.repository' 6 | import { CreateUserUseCase, GetAllUsersUseCase } from '@/use-cases/user' 7 | 8 | import { UserController } from './user.controller' 9 | 10 | @Module({ 11 | controllers: [UserController], 12 | providers: [ 13 | PrismaService, 14 | { 15 | provide: UserRepository, 16 | useFactory: (prisma: PrismaService) => new UsersPrismaRepository(prisma), 17 | inject: [PrismaService] 18 | }, 19 | { 20 | provide: CreateUserUseCase, 21 | useFactory: (repository: UserRepository) => new CreateUserUseCase(repository), 22 | inject: [UserRepository] 23 | }, 24 | { 25 | provide: GetAllUsersUseCase, 26 | useFactory: (repository: UserRepository) => new GetAllUsersUseCase(repository), 27 | inject: [UserRepository] 28 | } 29 | ] 30 | }) 31 | export class UserModule {} 32 | -------------------------------------------------------------------------------- /src/shared/dtos/post/create-post.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreatePostDto { 2 | id?: number 3 | title: string 4 | content: string 5 | userId: number 6 | } 7 | -------------------------------------------------------------------------------- /src/shared/dtos/post/created-post.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreatedPostDto { 2 | id: number 3 | title: string 4 | content: string 5 | userId: number 6 | } 7 | -------------------------------------------------------------------------------- /src/shared/dtos/post/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-post.dto' 2 | export * from './created-post.dto' 3 | -------------------------------------------------------------------------------- /src/shared/dtos/user/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateUserDto { 2 | id?: number 3 | name: string 4 | email: string 5 | password: string 6 | } 7 | -------------------------------------------------------------------------------- /src/shared/dtos/user/created-user.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreatedUserDto { 2 | id: number 3 | name: string 4 | email: string 5 | } 6 | -------------------------------------------------------------------------------- /src/shared/dtos/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-user.dto' 2 | export * from './created-user.dto' 3 | -------------------------------------------------------------------------------- /src/use-cases/post/create-post-use-case/create-post-use-case.spec.ts: -------------------------------------------------------------------------------- 1 | import { PostRepository, UserRepository } from '@/core/repositories' 2 | import { PostsCacheMemoryRepository } from '@/infra/data/cache-memory/posts-cache-memory.repository' 3 | import { UsersCacheMemoryRepository } from '@/infra/data/cache-memory/users-cache-memory.repository' 4 | import { CreateUserUseCase } from '@/use-cases/user' 5 | 6 | import { CreatePostUseCase } from './create-post-use-case' 7 | 8 | describe('CreatePostUseCase', () => { 9 | let createPostUseCase: CreatePostUseCase 10 | let postRepository: PostRepository 11 | let createUserUserCase: CreateUserUseCase 12 | let userRepository: UserRepository 13 | 14 | const title = 'Post title' 15 | const content = 'Post content' 16 | const userId = 1 17 | 18 | const name = 'John Doe' 19 | const email = 'johndoe@example.com' 20 | const password = '123456' 21 | 22 | beforeEach(async () => { 23 | postRepository = new PostsCacheMemoryRepository() 24 | userRepository = new UsersCacheMemoryRepository() 25 | createPostUseCase = new CreatePostUseCase(postRepository) 26 | createUserUserCase = new CreateUserUseCase(userRepository) 27 | 28 | await createUserUserCase.execute({ name, email, password }) 29 | }) 30 | 31 | it('should be defined', () => { 32 | expect(createPostUseCase).toBeDefined() 33 | }) 34 | 35 | it('should create a post', async () => { 36 | const post = await createPostUseCase.execute({ title, content, userId }) 37 | expect(post).toEqual({ id: 1, title, content, userId }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /src/use-cases/post/create-post-use-case/create-post-use-case.ts: -------------------------------------------------------------------------------- 1 | import { UseCase } from '@/core/base/use-case' 2 | import { CreatedPostMapper } from '@/core/domain/mappers' 3 | import { CreatePostMapper } from '@/core/domain/mappers/post/create-post.mapper' 4 | import { PostRepository } from '@/core/repositories' 5 | import { CreatedPostDto, CreatePostDto } from '@/shared/dtos/post' 6 | 7 | export class CreatePostUseCase implements UseCase { 8 | private CreatePostMapper: CreatePostMapper 9 | private CreatedPostMapper: CreatedPostMapper 10 | 11 | constructor(private readonly repository: PostRepository) { 12 | this.CreatePostMapper = new CreatePostMapper() 13 | this.CreatedPostMapper = new CreatedPostMapper() 14 | } 15 | 16 | public async execute(post: CreatePostDto): Promise { 17 | const entity = this.CreatePostMapper.mapFrom(post) 18 | const createdPost = await this.repository.create(entity) 19 | return this.CreatedPostMapper.mapTo(createdPost) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/use-cases/post/create-post-use-case/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-post-use-case' 2 | -------------------------------------------------------------------------------- /src/use-cases/post/get-all-posts-use-case/get-all-posts-use-case.spec.ts: -------------------------------------------------------------------------------- 1 | import { PostRepository, UserRepository } from '@/core/repositories' 2 | import { PostsCacheMemoryRepository } from '@/infra/data/cache-memory/posts-cache-memory.repository' 3 | import { UsersCacheMemoryRepository } from '@/infra/data/cache-memory/users-cache-memory.repository' 4 | import { CreateUserUseCase } from '@/use-cases/user' 5 | 6 | import { CreatePostUseCase } from '../create-post-use-case' 7 | import { GetAllPostsUseCase } from './get-all-posts-use-case' 8 | 9 | describe('CreatePostUseCase', () => { 10 | let getAllPostsUseCase: GetAllPostsUseCase 11 | let createPostUseCase: CreatePostUseCase 12 | let postRepository: PostRepository 13 | let createUserUserCase: CreateUserUseCase 14 | let userRepository: UserRepository 15 | 16 | const title = 'Post title' 17 | const content = 'Post content' 18 | const userId = 1 19 | 20 | const name = 'John Doe' 21 | const email = 'johndoe@example.com' 22 | const password = '123456' 23 | 24 | beforeEach(async () => { 25 | postRepository = new PostsCacheMemoryRepository() 26 | userRepository = new UsersCacheMemoryRepository() 27 | createPostUseCase = new CreatePostUseCase(postRepository) 28 | createUserUserCase = new CreateUserUseCase(userRepository) 29 | getAllPostsUseCase = new GetAllPostsUseCase(postRepository) 30 | 31 | await createUserUserCase.execute({ name, email, password }) 32 | await createPostUseCase.execute({ title, content, userId }) 33 | }) 34 | 35 | it('should be defined', () => { 36 | expect(getAllPostsUseCase).toBeDefined() 37 | }) 38 | 39 | it('should get all posts', async () => { 40 | const posts = await getAllPostsUseCase.execute() 41 | expect(posts).toEqual([{ id: 1, title, content, userId }]) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /src/use-cases/post/get-all-posts-use-case/get-all-posts-use-case.ts: -------------------------------------------------------------------------------- 1 | import { UseCase } from '@/core/base/use-case' 2 | import { CreatedPostMapper } from '@/core/domain/mappers' 3 | import { PostRepository } from '@/core/repositories' 4 | import { CreatedPostDto } from '@/shared/dtos/post' 5 | 6 | export class GetAllPostsUseCase implements UseCase { 7 | private CreatedPostMapper: CreatedPostMapper 8 | 9 | constructor(private readonly repository: PostRepository) { 10 | this.CreatedPostMapper = new CreatedPostMapper() 11 | } 12 | 13 | public async execute(): Promise { 14 | const posts = await this.repository.getAll() 15 | return posts.map(post => this.CreatedPostMapper.mapTo(post)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/use-cases/post/get-all-posts-use-case/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-all-posts-use-case' 2 | -------------------------------------------------------------------------------- /src/use-cases/post/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-post-use-case' 2 | export * from './get-all-posts-use-case' 3 | -------------------------------------------------------------------------------- /src/use-cases/user/create-user-use-case/create-user.use-case.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserRepository } from '@/core/repositories' 2 | import { UsersCacheMemoryRepository } from '@/infra/data/cache-memory/users-cache-memory.repository' 3 | 4 | import { CreateUserUseCase } from './create-user.use-case' 5 | 6 | describe('CreateUserUseCase', () => { 7 | let createUserUserCase: CreateUserUseCase 8 | let userRepository: UserRepository 9 | 10 | const name = 'John Doe' 11 | const email = 'johndoe@example.com' 12 | const password = '123456' 13 | 14 | beforeEach(() => { 15 | userRepository = new UsersCacheMemoryRepository() 16 | createUserUserCase = new CreateUserUseCase(userRepository) 17 | }) 18 | 19 | it('should be defined', () => { 20 | expect(createUserUserCase).toBeDefined() 21 | }) 22 | 23 | it('should create a user', async () => { 24 | const user = await createUserUserCase.execute({ name, email, password }) 25 | expect(user).toEqual({ id: 1, name, email }) 26 | }) 27 | 28 | // it('should throw an error if user already exists', async () => { 29 | // await createUserUserCase.execute({ name, email, password }) 30 | // await expect(createUserUserCase.execute({ name, email, password })).rejects.toThrowError( 31 | // 'User already exists' 32 | // ) 33 | // }) 34 | }) 35 | -------------------------------------------------------------------------------- /src/use-cases/user/create-user-use-case/create-user.use-case.ts: -------------------------------------------------------------------------------- 1 | import { UseCase } from '@/core/base/use-case' 2 | import { CreatedUserMapper } from '@/core/domain/mappers' 3 | import { CreateUserMapper } from '@/core/domain/mappers/user/create-user.mapper' 4 | import { UserRepository } from '@/core/repositories' 5 | import { CreatedUserDto, CreateUserDto } from '@/shared/dtos/user' 6 | 7 | export class CreateUserUseCase implements UseCase { 8 | private CreateUserMapper: CreateUserMapper 9 | private CreatedUserMapper: CreatedUserMapper 10 | 11 | constructor(private readonly repository: UserRepository) { 12 | this.CreateUserMapper = new CreateUserMapper() 13 | this.CreatedUserMapper = new CreatedUserMapper() 14 | } 15 | 16 | public async execute(user: CreateUserDto): Promise { 17 | const entity = this.CreateUserMapper.mapFrom(user) 18 | const createdUser = await this.repository.create(entity) 19 | return this.CreatedUserMapper.mapTo(createdUser) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/use-cases/user/create-user-use-case/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-user.use-case' 2 | -------------------------------------------------------------------------------- /src/use-cases/user/get-all-users.use-case/get-all-users.use-case.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserRepository } from '@/core/repositories' 2 | import { UsersCacheMemoryRepository } from '@/infra/data/cache-memory/users-cache-memory.repository' 3 | 4 | import { CreateUserUseCase } from '../create-user-use-case' 5 | import { GetAllUsersUseCase } from './get-all-users.use-case' 6 | 7 | describe('GetAllUsersUseCase', () => { 8 | let getAllUsersUseCase: GetAllUsersUseCase 9 | let createUserUserCase: CreateUserUseCase 10 | let userRepository: UserRepository 11 | 12 | const name = 'John Doe' 13 | const email = 'johndoe@example.com' 14 | const password = '123456' 15 | 16 | beforeEach(async () => { 17 | userRepository = new UsersCacheMemoryRepository() 18 | createUserUserCase = new CreateUserUseCase(userRepository) 19 | getAllUsersUseCase = new GetAllUsersUseCase(userRepository) 20 | 21 | await createUserUserCase.execute({ name, email, password }) 22 | }) 23 | 24 | it('should be defined', () => { 25 | expect(getAllUsersUseCase).toBeDefined() 26 | }) 27 | 28 | it('should get all users', async () => { 29 | const users = await getAllUsersUseCase.execute() 30 | expect(users).toEqual([{ id: 1, name, email }]) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/use-cases/user/get-all-users.use-case/get-all-users.use-case.ts: -------------------------------------------------------------------------------- 1 | import { UseCase } from '@/core/base/use-case' 2 | import { CreatedUserMapper } from '@/core/domain/mappers' 3 | import { UserRepository } from '@/core/repositories' 4 | import { CreatedUserDto } from '@/shared/dtos/user' 5 | 6 | export class GetAllUsersUseCase implements UseCase { 7 | private CreatedUserMapper: CreatedUserMapper 8 | 9 | constructor(private readonly repository: UserRepository) { 10 | this.CreatedUserMapper = new CreatedUserMapper() 11 | } 12 | 13 | public async execute(): Promise { 14 | const users = await this.repository.getAll() 15 | return users.map(user => this.CreatedUserMapper.mapTo(user)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/use-cases/user/get-all-users.use-case/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-all-users.use-case' 2 | -------------------------------------------------------------------------------- /src/use-cases/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-user-use-case' 2 | export * from './get-all-users.use-case' 3 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "paths": { 13 | "@/core/*": ["./src/core/*"], 14 | "@/infra/*": ["./src/infra/*"], 15 | "@/shared/*": ["./src/shared/*"], 16 | "@/use-cases/*": ["./src/use-cases/*"] 17 | }, 18 | "incremental": true, 19 | "skipLibCheck": true, 20 | "strictNullChecks": false, 21 | "noImplicitAny": false, 22 | "strictBindCallApply": false, 23 | "forceConsistentCasingInFileNames": false, 24 | "noFallthroughCasesInSwitch": false 25 | } 26 | } 27 | --------------------------------------------------------------------------------