Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/assets/flag-brasil.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/flag-eua.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#custom
*.pem
*.key*

# Logs
logs
Expand Down
156 changes: 156 additions & 0 deletions README-pt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<p align="center"> <img src="./.github/assets/logo.svg" width="200" /> </p>
<h1 align="center"> Feedback.io </h1>

#### Muda de Língua
<p>
<a href="https://github.com/gabriellopes00/feedbackio-api/blob/main/README.md" alt="turn lenguage">
<img src="./.github/assets/flag-eua.jpg" alt="flag-eua" min-width="50px" max-width="50px" width="50px" alt="turn lenguage portugues">
</a>
</p>

###### Uma API construída com

<p>
<img src="https://cdn.svgporn.com/logos/typescript-icon.svg" alt="typescript" width="30" height="30"/>
<img src="https://cdn.svgporn.com/logos/nodejs-icon.svg" alt="nodejs" width="30" height="30"/>
<img src="https://cdn.svgporn.com/logos/graphql.svg" alt="graphql" width="30" height="30"/>
<img src="https://cdn.svgporn.com/logos/socket.io.svg" alt="socket.io" width="30" height="30"/>
<img src="https://cdn.svgporn.com/logos/docker-icon.svg" alt="docker" width="30" height="30"/>
<img src="https://cdn.svgporn.com/logos/postgresql.svg" alt="postgresql" width="30" height="30"/>
<img src="https://cdn.svgporn.com/logos/eslint.svg" alt="eslint" width="30" height="30"/>
<img src="https://cdn.svgporn.com/logos/jest.svg" alt="jest" height="30">
<img src="https://cdn.svgporn.com/logos/heroku-icon.svg" alt="heroku" height="30">
<img src="https://github.com/typeorm/typeorm/raw/master/resources/logo_big.png" alt="typeorm" height="30">
</p>

##### Aplicativo hospedado em: _[heroku](https://www.heroku.com/)_.

##### Url da API: _https://api-feedbackio.herokuapp.com_.

<h2> Sobre Feedback.io 📚 </h2>

<p>
Feedback.io está sendo construído para oferecer uma plataforma completa para que todos tenham um melhor controle sobre seus serviços e a opinião de seus clientes sobre eles. Aqui você pode se cadastrar na plataforma e criar serviços, que poderão receber feedbacks e taxas de seus clientes. Isso permitirá que você entre em contato com os feedbacks dos clientes, que podem ser PROBLEMAS, IDÉIAS ou OUTROS, e calcule o NPS de seus serviços, sabendo como seus clientes estão avaliando seus serviços. Este é um aplicativo de código aberto, que é construído usando Nodejs, incluindo muitos outros conceitos e novas tecnologias, como Typescript, princípios SOLID, Clean Architecture, DDD, TDD, Docker, PostgreSQL ...
</p>

## Estrutura da API

![Clean Architecture Schema](.github/assets/clean-architecture.jpg)

Esta estrutura de projeto é inspirada na estrutura de código de arquitetura limpa, [by Uncle Bob](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html). O principal objetivo da arquitetura limpa é criar uma estrutura de projeto forte, dividindo todo o aplicativo em camadas. As melhores vantagens de utilizá-lo, é criar uma aplicação independente de qualquer framework, ferramenta, banco de dados ou tecnologia, sendo fácil manipular o código, criar novas funcionalidades, corrigir qualquer problema, ou alterar algum framework, graças aos adaptadores e portas.

#### Pastas 📚

```
root
├── .github - Github setup files
│ └── assets
│ └── workflow
├── assets - Project assets (images, logos...)
├── src - Application main code
│ │── app - Composition layer, entry point code
│ │ └── routes - routes files
│ │ └── setup - server config (middlewares, cors, body-parser)
│ │ └── builds - classes instances
│ │ └── adapters - components adapters
│ ├── config - Some global config files (.env, path alias, logger)
│ ├── domain - Business rules definition
│ │ └── entities
│ │ └── usecases
│ ├── infra - External frameworks and libs implementation
│ ├── presentation - External api communication layer (controllers, presenters, validators)
│ └── useCases - Usecases implementation
└─── tests - Components tests
└── e2e
└── unit
```

## Construindo e contribuindo 🛠

Para executar este projeto localmente, você terá que instalar o Nodejs e o PostgreSQL na máquina ou executar tudo com o Docker. Depois de executar o projeto, vá para o arquivo **_.Env.example_**, na raiz do projeto, e renomeie-o para **_.Env_**, preenchendo todas as propriedades do arquivo com suas informações.



###### Clona Repositório

```git
git clone https://github.com/gabriellopes00/feedbackio-api.git
or
git clone git@github.com:gabriellopes00/feedbackio-api.git
```

###### Rodar no Docker

```docker
docker-compose up
```

###### Executando localmente

```bash
npm install
npm run build
npm run start
```

###### Testes (jest) 🧪

- _**All**_ ❯ `yarn test`
- _**Coverage**_ ❯ `yarn test:ci`
- _**Unit**(.spec)_ ❯ `yarn test:unit`
- _**e2e**(.test)_ ❯ `yarn test:e2e`

###### Linting (eslint and prettier) 🎭

- _**Lint**(eslint)_ ❯ `yarn lint`
- _**Style**(prettier)_ ❯ `yarn style`

###### Estatísticas dos tipos de commits 📊📈

Seguindo o padrão do [Conventional Commits](https://www.conventionalcommits.org/).

- _**feature** commits(amount)_ ❯ `git shortlog -s --grep feat`
- _**test** commits(amount)_ ❯ `git shortlog -s --grep test`
- _**refactor** commits(amount)_ ❯ `git shortlog -s --grep refactor`
- _**chore** commits(amount)_ ❯ `git shortlog -s --grep chore`
- _**docs** commits(amount)_ ❯ `git shortlog -s --grep docs`
- _**build** commits(amount)_ ❯ `git shortlog -s --grep build`

## 🤝 Colaboradores

Agradecemos às seguintes pessoas que contribuíram para este projeto:

<table>
<tr>
<td align="center">
<a href="#">
<img src="https://avatars.githubusercontent.com/u/69465943?v=4" width="100px;" alt="Foto do Gabriel Lopes no GitHub"/><br>
<sub>
<b>Gabriel Lopes</b>
</sub>
</a>
</td>
<td align="center">
<a href="#">
<img src="https://avatars.githubusercontent.com/u/69259671?v=4" width="100px;" alt="Foto do Matheus Campos"/><br>
<sub>
<b>Matheus Campos</b>
</sub>
</a>
</td>
</tr>
</table>

## Licença ✒

Este projeto usa a licença [Apache 2](https://github.com/gabriellopes00/feedbackio-api/blob/main/LICENSE.md), leia para saber mais sobre permissões e direitos autorais.

## Contato 📱

[![Github Badge](https://img.shields.io/badge/-Github-000?style=flat-square&logo=Github&logoColor=white&link=https://github.com/gabriellopes00)](https://github.com/gabriellopes00)
[![Linkedin Badge](https://img.shields.io/badge/-LinkedIn-blue?style=flat-square&logo=Linkedin&logoColor=white&link=https://www.linkedin.com/in/gabriel-lopes-6625631b0/)](https://www.linkedin.com/in/gabriel-lopes-6625631b0/)
[![Twitter Badge](https://img.shields.io/badge/-Twitter-1ca0f1?style=flat-square&labelColor=1ca0f1&logo=twitter&logoColor=white&link=https://twitter.com/_gabrielllopes_)](https://twitter.com/_gabrielllopes_)
[![Gmail Badge](https://img.shields.io/badge/-Gmail-D14836?&style=flat-square&logo=Gmail&logoColor=white&link=mailto:gabrielluislopes00@gmail.com)](mailto:gabrielluislopes00@gmail.com)
[![Facebook Badge](https://img.shields.io/badge/facebook-%231877F2.svg?&style=flat-square&logo=facebook&logoColor=white)](https://www.facebook.com/profile.php?id=100034920821684)
[![Instagram Badge](https://img.shields.io/badge/instagram-%23E4405F.svg?&style=flat-square&logo=instagram&logoColor=white)](https://www.instagram.com/_.gabriellopes/?hl=pt-br)
[![StackOverflow Badge](https://img.shields.io/badge/stack%20overflow-FE7A16?logo=stack-overflow&logoColor=white&style=flat-square)](https://stackoverflow.com/users/14099025/gabriel-lopes?tab=profile)
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<p align="center"> <img src="./.github/assets/logo.svg" width="200" /> </p>
<h1 align="center"> Feedback.io </h1>

#### Turn Language
<p>
<a href="https://github.com/gabriellopes00/feedbackio-api/blob/main/README.md" alt="turn lenguage">
<img src="./.github/assets/flag-brasil.png" alt="flag-brasil" min-width="50px" max-width="50px" width="50px" alt="turn lenguage portugues">
</a>
</p>

###### An API built with

<p>
Expand Down Expand Up @@ -107,7 +114,32 @@ Following the standard of the [Conventional Commits](https://www.conventionalcom
- _**docs** commits(amount)_ ❯ `git shortlog -s --grep docs`
- _**build** commits(amount)_ ❯ `git shortlog -s --grep build`

## License ✒
## 🤝 Contributors

We thank the following people who contributed to this project:

<table>
<tr>
<td align="center">
<a href="https://github.com/gabriellopes00">
<img src="https://avatars.githubusercontent.com/u/69465943?v=4" width="100px;" alt="Photo of Gabriel Lopes no GitHub"/><br>
<sub>
<b>Gabriel Lopes</b>
</sub>
</a>
</td>
<td align="center">
<a href="https://github.com/MatheusFC2">
<img src="https://avatars.githubusercontent.com/u/69259671?v=4" width="100px;" alt="Photo of Matheus Campos"/><br>
<sub>
<b>Matheus Campos</b>
</sub>
</a>
</td>
</tr>
</table>

## License ⚖

This project uses [Apache 2](https://github.com/gabriellopes00/feedbackio-api/blob/main/LICENSE.md) license, read to know more about permissions and copyright.

Expand Down
12 changes: 11 additions & 1 deletion src/app/builds/controllers/add-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ import { DbAddUser } from '@/usecases/implementation/add-user'
import { hashGenerator, idGenerator, userRepository } from '../infra'

import { AddUserController } from '@/presentation/controllers/add-user'
import { UserAuthenticator } from '@/usecases/implementation/auth-user'
import { BcryptHasher } from '@/infra/utils/bcrypt-hasher'
import { TokenRepository } from '@/infra/database/repositories/access-token'
import { JWTEncrypter } from '@/infra/utils/jwt-encrypter'
import { TOKEN_DURATION, TOKEN_PRIVATE_KEY } from '@/config/env'

const requiredFieldsValidation = new RequiredFieldValidation(['name', 'email', 'password'])
const userValidator = new UserValidator()
const validator = new ValidatorCompositor([requiredFieldsValidation, userValidator])

const dbAddUser = new DbAddUser(userRepository, idGenerator, hashGenerator)

export const addUserController = new AddUserController(validator, dbAddUser)
const hasher = new BcryptHasher()
const jwtEncrypter = new JWTEncrypter(TOKEN_PRIVATE_KEY, TOKEN_DURATION)
const repo = new TokenRepository()
export const authenticator = new UserAuthenticator(userRepository, hasher, jwtEncrypter, repo)

export const addUserController = new AddUserController(validator, dbAddUser, authenticator)
8 changes: 8 additions & 0 deletions src/infra/database/repositories/access-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AccessTokenRepository } from '@/usecases/ports/access-token-repository'
import fs from 'fs'

export class TokenRepository implements AccessTokenRepository {
async add(token: string, userEmail: string): Promise<void> {
fs.appendFileSync('./db.txt', `${token}-${userEmail},`)
}
}
5 changes: 5 additions & 0 deletions src/infra/database/repositories/user-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ export class PsqlUserRepository extends Repository<UserModel> implements UserRep
const user = await this.findOne({ email })
return !!user
}

public async findByEmail(email: string): Promise<User> {
const user = await this.findOne({ email })
return user
}
}
7 changes: 2 additions & 5 deletions src/infra/utils/jwt-encrypter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import { Encrypter } from '@/usecases/ports/encrypter'
import { sign } from 'jsonwebtoken'

export class JWTEncrypter implements Encrypter {
constructor(private readonly privateKey: string, private readonly tokenExpiration: string) {}
constructor(private readonly secretKey: string, private readonly tokenExpiration: string) {}

async encrypt(payload: Object): Promise<string> {
const token = sign(payload, this.privateKey, {
algorithm: 'RS256',
expiresIn: this.tokenExpiration
})
const token = sign({ ...payload }, this.secretKey, { expiresIn: 60 * 15 })
return token
}
}
9 changes: 5 additions & 4 deletions src/presentation/controllers/add-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface AddUserResponse {
name: string
email: string
}
token: string
accessToken: string
}

export class AddUserController implements Controller {
Expand All @@ -29,12 +29,13 @@ export class AddUserController implements Controller {
const result = await this.addUser.add(params)
if (result instanceof Error) return conflict(result)

const { id, name, email, password } = result
const { id, name, email } = result

const token = (await this.authenticator.auth({ email, password })) as string
const token = (await this.authenticator.auth({ email, password: params.password })) as string

return ok<AddUserResponse>({ user: { id, name, email }, token })
return ok<AddUserResponse>({ user: { id, name, email }, accessToken: token })
} catch (error) {
console.log(error)
return serverError(error)
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/usecases/implementation/auth-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class UserAuthenticator implements AuthUser {

async auth(data: AuthParams): Promise<string | UnregisteredEmailError | UnmatchedPasswordError> {
const { email, password } = data
const existingUser = await this.userRepository.findOne(email)
const existingUser = await this.userRepository.findByEmail(email)
if (!existingUser) return new UnregisteredEmailError(email)

const matchedPassword = await this.hashComparer.compare(password, existingUser.password)
Expand Down
2 changes: 1 addition & 1 deletion src/usecases/ports/access-token-repository.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export interface AccessTokenRepository {
add(token: string, userEmail: string): Promise<string>
add(token: string, userEmail: string): Promise<void>
}
2 changes: 1 addition & 1 deletion src/usecases/ports/user-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { User } from '@/domain/entities/user'
export interface UserRepository {
add(data: User): Promise<User>
exists(email: string): Promise<boolean>
findOne(email: string): Promise<User>
findByEmail(email: string): Promise<User>
}