├── .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 |
3 |
4 |
5 | # Nest.js Clean Architecture
6 |
7 | [](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 |
--------------------------------------------------------------------------------