diff --git a/.env b/.env
new file mode 100644
index 00000000..5daf1e51
--- /dev/null
+++ b/.env
@@ -0,0 +1,8 @@
+PORT=3003
+
+DB_FILE_PATH=./src/database/nome-do-arquivo.db
+
+JWT_KEY=senha-de-exemplo-jwt-key
+JWT_EXPIRES_IN=7d
+
+BCRYPT_COST=12
\ No newline at end of file
diff --git a/README.md b/README.md
index 266434ba..22f8e2c1 100644
--- a/README.md
+++ b/README.md
@@ -1,207 +1,100 @@
# Projeto Labook
+
+## Índice:
+
+- Objetivo
+- Documentação da API
+- Estruturação do banco de dados
+- Requisitos:
+- Como rodar este projeto?
+- Técnologias utilizadas
+- Autoria
+- Próximos Passos
+
+## Objetivo
+
O Labook é uma rede social com o objetivo de promover a conexão e interação entre pessoas. Quem se cadastrar no aplicativo poderá criar e curtir publicações.
-Agora que temos as bases de criação de APIs e banco de dados, o próximo nível é a implementação de segurança e códigos mais escaláveis. Veremos durante o prazo de entrega desse projeto inúmeros conceitos e formas de desenvolvimento seguindo padrões de design e arquitetura, e seu desafio será unir as funcionalidades com as boas práticas de código.
-
-# Conteúdos abordados
-- NodeJS
-- Typescript
-- Express
-- SQL e SQLite
-- Knex
-- POO
-- Arquitetura em camadas
-- Geração de UUID
-- Geração de hashes
-- Autenticação e autorização
-- Roteamento
-- Postman
+## Documentação da API
+
+[Link Demonstração](https://documenter.getpostman.com/view/25825355/2s93ebUBDa)
+
+## Estruturação do banco de dados
-# Banco de dados
![projeto-labook (2)](https://user-images.githubusercontent.com/29845719/216036534-2b3dfb48-7782-411a-bffd-36245b78594e.png)
-https://dbdiagram.io/d/63d16443296d97641d7c1ae1
+## Requisitos:
-# Lista de requisitos
- Documentação Postman de todos os endpoints (obrigatória para correção)
- Endpoints
- - [ ] signup
- - [ ] login
- - [ ] get posts
- - [ ] create post
- - [ ] edit post
- - [ ] delete post
- - [ ] like / dislike post
+
+ - [x] signup
+ - [x] login
+ - [x] get posts
+ - [x] create post
+ - [x] edit post
+ - [x] delete post
+ - [] like / dislike post
- Autenticação e autorização
- - [ ] identificação UUID
- - [ ] senhas hasheadas com Bcrypt
- - [ ] tokens JWT
-
- - Código
- - [ ] POO
- - [ ] Arquitetura em camadas
- - [ ] Roteadores no Express
+
+ - [x] identificação UUID
+ - [x] senhas hasheadas com Bcrypt
+ - [x] tokens JWT
+
+- Código
+
+ - [x] POO
+ - [x] Arquitetura em camadas
+ - [x] Roteadores no Express
- README.md
-# Exemplos de requisição
-
-## Signup
-Endpoint público utilizado para cadastro. Devolve um token jwt.
-```typescript
-// request POST /users/signup
-// body JSON
-{
- "name": "Beltrana",
- "email": "beltrana@email.com",
- "password": "beltrana00"
-}
-
-// response
-// status 201 CREATED
-{
- token: "um token jwt"
-}
-```
+## Como rodar este projeto?
-## Login
-Endpoint público utilizado para login. Devolve um token jwt.
-```typescript
-// request POST /users/login
-// body JSON
-{
- "email": "beltrana@email.com",
- "password": "beltrana00"
-}
-
-// response
-// status 200 OK
-{
- token: "um token jwt"
-}
-```
+```bash
+#Clone este repositório
+$ git clone lin krepo
-## Get posts
-Endpoint protegido, requer um token jwt para acessá-lo.
-```typescript
-// request GET /posts
-// headers.authorization = "token jwt"
-
-// response
-// status 200 OK
-[
- {
- "id": "uma uuid v4",
- "content": "Hoje vou estudar POO!",
- "likes": 2,
- "dislikes" 1,
- "createdAt": "2023-01-20T12:11:47:000Z"
- "updatedAt": "2023-01-20T12:11:47:000Z"
- "creator": {
- "id": "uma uuid v4",
- "name": "Fulano"
- }
- },
- {
- "id": "uma uuid v4",
- "content": "kkkkkkkkkrying",
- "likes": 0,
- "dislikes" 0,
- "createdAt": "2023-01-20T15:41:12:000Z"
- "updatedAt": "2023-01-20T15:49:55:000Z"
- "creator": {
- "id": "uma uuid v4",
- "name": "Ciclana"
- }
- }
-]
-```
+#Acesse a pasta do projeto no seu terminal
+$ cd nomeDaPasta
-## Create post
-Endpoint protegido, requer um token jwt para acessá-lo.
-```typescript
-// request POST /posts
-// headers.authorization = "token jwt"
-// body JSON
-{
- "content": "Partiu happy hour!"
-}
-
-// response
-// status 201 CREATED
-```
+# Instale as dependencias
+$ npm install
+
+# Execute a aplicação
+$ npm run dev
+
+# A aplicação será iniciada na porta 3004, acesse pelo navegador: http://localhost:3003
-## Edit post
-Endpoint protegido, requer um token jwt para acessá-lo.
-Só quem criou o post pode editá-lo e somente o conteúdo pode ser editado.
-```typescript
-// request PUT /posts/:id
-// headers.authorization = "token jwt"
-// body JSON
-{
- "content": "Partiu happy hour lá no point de sempre!"
-}
-
-// response
-// status 200 OK
```
-## Delete post
-Endpoint protegido, requer um token jwt para acessá-lo.
-Só quem criou o post pode deletá-lo. Admins podem deletar o post de qualquer pessoa.
+## Técnologias utilizadas
-```typescript
-// request DELETE /posts/:id
-// headers.authorization = "token jwt"
+1. [Node.js](https://nodejs.org/en)
+2. [TypeScript](https://www.typescriptlang.org/)
+3. [Express](https://expressjs.com/)
+4. [SQLite3 / SQL](https://sqlite.org/index.html)
+5. [Knex](https://knexjs.org/)
+6. [POO](https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_orientada_a_objetos)
+7. [Arquiterura em camadas](https://pt.wikipedia.org/wiki/Arquitetura_multicamada)
+8. [Geração de UUID](https://pt.wikipedia.org/wiki/Identificador_%C3%BAnico_universal)
+9. [Geração de hashes](https://pt.wikipedia.org/wiki/Fun%C3%A7%C3%A3o_hash_criptogr%C3%A1fica)
+10. [Autenticação e autorização](https://pt.wikipedia.org/wiki/Autoriza%C3%A7%C3%A3o)
+11. [Roteamento](https://acervolima.com/roteamento-em-node-js/)
+12. [Postman](https://www.postman.com/)
-// response
-// status 200 OK
-```
+## Autoria
-## Like or dislike post (mesmo endpoint faz as duas coisas)
-
-Endpoint protegido, requer um token jwt para acessá-lo.
-Quem criou o post não pode dar like ou dislike no mesmo.
-Caso dê um like em um post que já tenha dado like, o like é desfeito.
-Caso dê um dislike em um post que já tenha dado dislike, o dislike é desfeito.
-Caso dê um like em um post que tenha dado dislike, o like sobrescreve o dislike.
-Caso dê um dislike em um post que tenha dado like, o dislike sobrescreve o like.
-### Like (funcionalidade 1)
-```typescript
-// request PUT /posts/:id/like
-// headers.authorization = "token jwt"
-// body JSON
-{
- "like": true
-}
-
-// response
-// status 200 OK
-```
+Michelle Antunes, abril/2023.
+
-### Dislike (funcionalidade 2)
-```typescript
-// request PUT /posts/:id/like
-// headers.authorization = "token jwt"
-// body JSON
-{
- "like": false
-}
-
-// response
-// status 200 OK
-```
+Linkedin: www.linkedin.com/in/michelle-antunes-868b24156
+
+Email: miichelleantunes@outlook.com
+
+## Próximos Passos
-### Para entender a tabela likes_dislikes
-- no SQLite, lógicas booleanas devem ser controladas via 0 e 1 (INTEGER)
-- quando like valer 1 na tabela é porque a pessoa deu like no post
- - na requisição like é true
-
-- quando like valer 0 na tabela é porque a pessoa deu dislike no post
- - na requisição like é false
-
-- caso não exista um registro na tabela de relação, é porque a pessoa não deu like nem dislike
-- caso dê like em um post que já tenha dado like, o like é removido (deleta o item da tabela)
-- caso dê dislike em um post que já tenha dado dislike, o dislike é removido (deleta o item da tabela)
+- [ ] Deploy
+- [ ] Testes unitários
diff --git a/package-lock.json b/package-lock.json
index 1c170026..e2c2798e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,16 +9,24 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "bcryptjs": "^2.4.3",
"cors": "^2.8.5",
+ "dotenv": "^16.0.3",
"express": "^4.18.2",
+ "jsonwebtoken": "^9.0.0",
"knex": "^2.4.2",
- "sqlite3": "^5.1.4"
+ "sqlite3": "^5.1.4",
+ "uuid": "^9.0.0",
+ "zod": "^3.21.4"
},
"devDependencies": {
+ "@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.15",
+ "@types/jsonwebtoken": "^9.0.2",
"@types/knex": "^0.16.1",
"@types/node": "^18.11.18",
+ "@types/uuid": "^9.0.1",
"ts-node-dev": "^2.0.0",
"typescript": "^4.9.4"
}
@@ -142,6 +150,12 @@
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
+ "node_modules/@types/bcryptjs": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz",
+ "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==",
+ "dev": true
+ },
"node_modules/@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@@ -193,6 +207,15 @@
"@types/range-parser": "*"
}
},
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/knex": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@types/knex/-/knex-0.16.1.tgz",
@@ -249,6 +272,12 @@
"integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
"dev": true
},
+ "node_modules/@types/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==",
+ "dev": true
+ },
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -432,6 +461,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "node_modules/bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -485,6 +519,11 @@
"node": ">=8"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -712,6 +751,14 @@
"node": ">=0.3.1"
}
},
+ "node_modules/dotenv": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+ "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/dynamic-dedupe": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
@@ -721,6 +768,14 @@
"xtend": "^4.0.0"
}
},
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -1264,6 +1319,45 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"optional": true
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
+ "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash": "^4.17.21",
+ "ms": "^2.1.1",
+ "semver": "^7.3.8"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/knex": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz",
@@ -2491,6 +2585,14 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -2569,6 +2671,14 @@
"engines": {
"node": ">=6"
}
+ },
+ "node_modules/zod": {
+ "version": "3.21.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
+ "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
},
"dependencies": {
@@ -2675,6 +2785,12 @@
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
+ "@types/bcryptjs": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz",
+ "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==",
+ "dev": true
+ },
"@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@@ -2726,6 +2842,15 @@
"@types/range-parser": "*"
}
},
+ "@types/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/knex": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@types/knex/-/knex-0.16.1.tgz",
@@ -2781,6 +2906,12 @@
"integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
"dev": true
},
+ "@types/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==",
+ "dev": true
+ },
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -2919,6 +3050,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
+ },
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -2962,6 +3098,11 @@
"fill-range": "^7.0.1"
}
},
+ "buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
"buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -3132,6 +3273,11 @@
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
+ "dotenv": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+ "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
+ },
"dynamic-dedupe": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
@@ -3141,6 +3287,14 @@
"xtend": "^4.0.0"
}
},
+ "ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -3569,6 +3723,43 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"optional": true
},
+ "jsonwebtoken": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
+ "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
+ "requires": {
+ "jws": "^3.2.2",
+ "lodash": "^4.17.21",
+ "ms": "^2.1.1",
+ "semver": "^7.3.8"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ }
+ }
+ },
+ "jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "requires": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "requires": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"knex": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz",
@@ -4431,6 +4622,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
},
+ "uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
+ },
"v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -4494,6 +4690,11 @@
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
+ },
+ "zod": {
+ "version": "3.21.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
+ "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw=="
}
}
}
diff --git a/package.json b/package.json
index 679b688e..7478a560 100644
--- a/package.json
+++ b/package.json
@@ -12,17 +12,25 @@
"author": "",
"license": "ISC",
"devDependencies": {
+ "@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.15",
+ "@types/jsonwebtoken": "^9.0.2",
"@types/knex": "^0.16.1",
"@types/node": "^18.11.18",
+ "@types/uuid": "^9.0.1",
"ts-node-dev": "^2.0.0",
"typescript": "^4.9.4"
},
"dependencies": {
+ "bcryptjs": "^2.4.3",
"cors": "^2.8.5",
+ "dotenv": "^16.0.3",
"express": "^4.18.2",
+ "jsonwebtoken": "^9.0.0",
"knex": "^2.4.2",
- "sqlite3": "^5.1.4"
+ "sqlite3": "^5.1.4",
+ "uuid": "^9.0.0",
+ "zod": "^3.21.4"
}
}
diff --git a/src/business/PostBusiness.ts b/src/business/PostBusiness.ts
new file mode 100644
index 00000000..565242bd
--- /dev/null
+++ b/src/business/PostBusiness.ts
@@ -0,0 +1,169 @@
+import { PostDatabase } from "../database/PostDatabase";
+import {
+ CreatePostInputDTO,
+ CreatePostOutputDTO,
+} from "../dtos/Post/createPost.dto";
+import {
+ DeletePostInputDTO,
+ DeletePostOutputDTO,
+} from "../dtos/Post/delete.dto";
+import { EditPostInputDTO, EditPostOutputDTO } from "../dtos/Post/editPost.dto";
+import { GetPostsInputDTO, GetPostsOutputDTO } from "../dtos/Post/getPosts.dto";
+
+import {
+ LikeOrDislikePostInputDTO,
+ LikeOrDislikePostOutputDTO,
+} from "../dtos/Post/likeOrdeslikePost.dto";
+import { NotFoundError } from "../errors/NotFoundError";
+
+import { LikeDislikeDB, Post, PostDB, POST_LIKE } from "../models/Posts";
+import { USER_ROLES } from "../models/User";
+import { TokenManager } from "../services/TokenManager";
+
+import { IdGenerator } from "../services/IdGenerator";
+import { BadRequestError } from "../errors/BadRequestError";
+
+export class PostBusiness {
+ constructor(
+ private postDatabase: PostDatabase,
+ private idGenerator: IdGenerator,
+ private tokenManeger: TokenManager
+ ) {}
+
+ public getPost = async (
+ input: GetPostsInputDTO
+ ): Promise => {
+ const { token } = input;
+
+ const payload = this.tokenManeger.getPayload(token);
+
+ if (!payload) {
+ throw new BadRequestError("Token inválido.");
+ }
+
+ const postsWithCreatorName =
+ await this.postDatabase.findPostsWithCreatorName();
+
+ const posts = postsWithCreatorName.map((postWithCreatorName) => {
+ const post = new Post(
+ postWithCreatorName.id,
+ postWithCreatorName.content,
+ postWithCreatorName.likes,
+ postWithCreatorName.dislikes,
+ postWithCreatorName.created_at,
+ postWithCreatorName.updated_at,
+ postWithCreatorName.creator_id,
+ postWithCreatorName.creator_name
+ );
+ return post.toBusinessModel();
+ });
+
+ const output: GetPostsOutputDTO = posts;
+
+ return output;
+ };
+
+ public postPost = async (
+ input: CreatePostInputDTO
+ ): Promise => {
+ const { token, content } = input;
+
+ const payload = this.tokenManeger.getPayload(token);
+
+ if (!payload) {
+ throw new BadRequestError("Token inválido");
+ }
+
+ const id = this.idGenerator.generate();
+
+ const newPost = new Post(
+ id,
+ content,
+ 0,
+ 0,
+ new Date().toString(),
+ new Date().toString(),
+ payload.id,
+ payload.name
+ );
+
+ const newPostDB = newPost.toDBModel();
+ await this.postDatabase.createPost(newPostDB);
+
+ const output: CreatePostOutputDTO = undefined;
+
+ return output;
+ };
+
+ public putPost = async (
+ input: EditPostInputDTO
+ ): Promise => {
+ const { token, idToEdit, content } = input;
+
+ const payload = this.tokenManeger.getPayload(token);
+
+ if (!payload) {
+ throw new BadRequestError("Token inválido");
+ }
+
+ const postDBExists = await this.postDatabase.findPostById(idToEdit);
+
+ if (!postDBExists) {
+ throw new NotFoundError("Post id not found");
+ }
+
+ if (postDBExists.creator_id !== payload.id) {
+ throw new BadRequestError("Only the creator of the post can edit it");
+ }
+
+ const post = new Post(
+ postDBExists.id,
+ postDBExists.content,
+ postDBExists.likes,
+ postDBExists.dislikes,
+ postDBExists.created_at,
+ postDBExists.updated_at,
+ postDBExists.creator_id,
+ payload.name
+ );
+
+ post.setContent(content);
+
+ const updatedPostDB = post.toDBModel();
+ await this.postDatabase.editPost(updatedPostDB);
+
+ const output: EditPostOutputDTO = undefined;
+
+ return output;
+ };
+
+ public deletePost = async (
+ input: DeletePostInputDTO
+ ): Promise => {
+ const { token, idToDelete } = input;
+
+ const payload = this.tokenManeger.getPayload(token);
+
+ if (!payload) {
+ throw new BadRequestError("Token inválido");
+ }
+
+ const postDBExists = await this.postDatabase.findPostById(idToDelete);
+
+ if (!postDBExists) {
+ throw new NotFoundError("Post-Is não existe");
+ }
+
+ if (payload.role !== USER_ROLES.ADMIN) {
+ if (payload.id !== postDBExists.creator_id) {
+ throw new BadRequestError("Somente quem criou o post pode deletá-lo");
+ }
+ }
+
+ await this.postDatabase.removePost(idToDelete);
+
+ const output: DeletePostOutputDTO = undefined;
+
+ return output;
+ };
+}
diff --git a/src/business/UserBusiness.ts b/src/business/UserBusiness.ts
new file mode 100644
index 00000000..b925904b
--- /dev/null
+++ b/src/business/UserBusiness.ts
@@ -0,0 +1,103 @@
+import { UserDatabase } from "../database/UserDatabase";
+import { LoginInputDTO, LoginOutputDTO } from "../dtos/User/login.dto";
+import { SignupInputDTO, SignupOutputDTO } from "../dtos/User/signup.dto";
+import { BadRequestError } from "../errors/BadRequestError";
+
+import { NotFoundError } from "../errors/NotFoundError";
+import { TokenPayload, User, USER_ROLES } from "../models/User";
+
+import { HashManager } from "../services/HashManeger";
+import { IdGenerator } from "../services/IdGenerator";
+import { TokenManager } from "../services/TokenManager";
+
+export class UserBusiness {
+ constructor(
+ private userDatabase: UserDatabase,
+ private idGenerator: IdGenerator,
+ private tokenManeger: TokenManager,
+ private hashManeger: HashManager
+ ) {}
+
+ public signup = async (input: SignupInputDTO) => {
+ const { name, email, password } = input;
+
+ const userBDExists = await this.userDatabase.findUserByEmail(email);
+
+ if (userBDExists) {
+ throw new BadRequestError("Esse e-mail já foi cadastrado");
+ }
+
+ const id = this.idGenerator.generate();
+ const hashedPassword = await this.hashManeger.hash(password);
+
+ const newUser = new User(
+ id,
+ name,
+ email,
+ hashedPassword,
+ USER_ROLES.NORMAL,
+ new Date().toISOString()
+ );
+
+ const newUserDB = newUser.toDBModel();
+ await this.userDatabase.postUser(newUserDB);
+
+ const payload: TokenPayload = {
+ id: newUser.getId(),
+ name: newUser.getName(),
+ role: newUser.getRole(),
+ };
+
+ const token = this.tokenManeger.createToken(payload);
+
+ const output: SignupOutputDTO = {
+ token,
+ };
+
+ return output;
+ };
+
+ public userLogin = async (input: LoginInputDTO) => {
+ const { email, password } = input;
+
+ const userBDExists = await this.userDatabase.findUserByEmail(email);
+
+ if (!userBDExists) {
+ throw new NotFoundError("Email não encontrado");
+ }
+
+ const user = new User(
+ userBDExists.id,
+ userBDExists.name,
+ userBDExists.email,
+ userBDExists.password,
+ userBDExists.role,
+ userBDExists.created_at
+ );
+
+ const hashedPassword = userBDExists.password;
+
+ const isCorrectPassword = await this.hashManeger.compare(
+ password,
+ hashedPassword
+ );
+
+ if (!isCorrectPassword) {
+ throw new BadRequestError("Email ou senha incorretos");
+ }
+
+ const payload: TokenPayload = {
+ id: user.getId(),
+ name: user.getName(),
+ role: user.getRole(),
+ };
+
+ const token = this.tokenManeger.createToken(payload);
+
+ const output: LoginOutputDTO = {
+ token,
+ };
+
+ return output;
+ };
+}
diff --git a/src/controller/PostController.ts b/src/controller/PostController.ts
new file mode 100644
index 00000000..b68a6faa
--- /dev/null
+++ b/src/controller/PostController.ts
@@ -0,0 +1,104 @@
+import { Request, Response } from "express";
+import { ZodError } from "zod";
+import { PostBusiness } from "../business/PostBusiness";
+import { CreatePostSchema } from "../dtos/Post/createPost.dto";
+import { DeletePostSchema } from "../dtos/Post/delete.dto";
+import { EditPostSchema } from "../dtos/Post/editPost.dto";
+import { GetPostsSchema } from "../dtos/Post/getPosts.dto";
+
+import { BaseError } from "../errors/BaseError";
+
+export class PostControlers {
+ constructor(private postBusiness: PostBusiness) {}
+
+ public getPosts = async (req: Request, res: Response) => {
+ try {
+ const input = GetPostsSchema.parse({
+ token: req.headers.authorization,
+ });
+ const output = await this.postBusiness.getPost(input);
+
+ res.status(200).send(output);
+ } catch (error) {
+ console.log(error);
+
+ if (error instanceof ZodError) {
+ res.status(400).send(error.issues);
+ } else if (error instanceof BaseError) {
+ res.status(error.statusCode).send(error.message);
+ } else {
+ res.status(500).send("Erro inesperado");
+ }
+ }
+ };
+
+ public postPost = async (req: Request, res: Response) => {
+ try {
+ const input = CreatePostSchema.parse({
+ token: req.headers.authorization,
+ content: req.body.content,
+ });
+
+ const output = await this.postBusiness.postPost(input);
+
+ res.status(201).send(output);
+ } catch (error) {
+ console.log(error);
+
+ if (error instanceof ZodError) {
+ res.status(400).send(error.issues);
+ } else if (error instanceof BaseError) {
+ res.status(error.statusCode).send(error.message);
+ } else {
+ res.status(500).send("Erro inesperado");
+ }
+ }
+ };
+
+ public putPost = async (req: Request, res: Response) => {
+ try {
+ const input = EditPostSchema.parse({
+ token: req.headers.authorization,
+ idToEdit: req.params.id,
+ content: req.body.content,
+ });
+
+ const output = await this.postBusiness.putPost(input);
+
+ res.status(200).send(output);
+ } catch (error) {
+ console.log(error);
+
+ if (error instanceof ZodError) {
+ res.status(400).send(error.issues);
+ } else if (error instanceof BaseError) {
+ res.status(error.statusCode).send(error.message);
+ } else {
+ res.status(500).send("Erro inesperado");
+ }
+ }
+ };
+
+ public deletePosts = async (req: Request, res: Response) => {
+ try {
+ const input = DeletePostSchema.parse({
+ token: req.headers.authorization,
+ idToDelete: req.params.id,
+ });
+
+ const output = await this.postBusiness.deletePost(input);
+
+ res.status(200).send(output);
+ } catch (error) {
+ console.log(error);
+
+ if (error instanceof ZodError) {
+ res.status(400).send(error.issues);
+ } else if (error instanceof BaseError) {
+ res.status(error.statusCode).send(error.message);
+ } else {
+ res.status(500).send("Erro inesperado");
+ }
+ }
+ };
+}
diff --git a/src/controller/UserController.ts b/src/controller/UserController.ts
new file mode 100644
index 00000000..5ea87088
--- /dev/null
+++ b/src/controller/UserController.ts
@@ -0,0 +1,57 @@
+import { Request, Response } from "express";
+import { ZodError } from "zod";
+import { UserBusiness } from "../business/UserBusiness";
+import { LoginSchema } from "../dtos/User/login.dto";
+import { SignupSchema } from "../dtos/User/signup.dto";
+import { BaseError } from "../errors/BaseError";
+
+export class UserController {
+ constructor(private userBusiness: UserBusiness) {}
+
+ public signup = async (req: Request, res: Response) => {
+ try {
+ const input = SignupSchema.parse({
+ name: req.body.name,
+ email: req.body.email,
+ password: req.body.password,
+ });
+
+ const output = await this.userBusiness.signup(input);
+
+ res.status(201).send(output);
+ } catch (error) {
+ console.log(error);
+
+ if (error instanceof ZodError) {
+ res.status(400).send(error.issues);
+ } else if (error instanceof BaseError) {
+ res.status(error.statusCode).send(error.message);
+ } else {
+ res.status(500).send("Erro inesperado");
+ }
+ }
+ };
+
+ public login = async (req: Request, res: Response) => {
+ try {
+ const input = LoginSchema.parse({
+ email: req.body.email,
+ password: req.body.password,
+ });
+
+ const output = await this.userBusiness.userLogin(input);
+
+ res.status(200).send(output);
+ } catch (error) {
+ console.log(error);
+
+ if (error instanceof ZodError) {
+ res.status(400).send(error.issues);
+ } else if (error instanceof BaseError) {
+ res.status(error.statusCode).send(error.message);
+ } else {
+ res.status(500).send("Erro inesperado");
+ }
+ }
+ };
+}
diff --git a/src/database/BaseDatabase.ts b/src/database/BaseDatabase.ts
index f8cc53d5..9b443942 100644
--- a/src/database/BaseDatabase.ts
+++ b/src/database/BaseDatabase.ts
@@ -1,10 +1,13 @@
import { knex } from "knex";
+import dotenv from "dotenv";
+
+dotenv.config();
export abstract class BaseDatabase {
protected static connection = knex({
client: "sqlite3",
connection: {
- filename: "./src/database/projetoBack_End.db",
+ filename: process.env.DB_FILE_PATH as string,
},
useNullAsDefault: true,
pool: {
diff --git a/src/database/PostDatabase.ts b/src/database/PostDatabase.ts
new file mode 100644
index 00000000..a83f467d
--- /dev/null
+++ b/src/database/PostDatabase.ts
@@ -0,0 +1,86 @@
+import {
+ LikeDislikeDB,
+ PostDB,
+ PostDBWithCreatorName,
+ POST_LIKE,
+} from "../models/Posts";
+import { BaseDatabase } from "./BaseDatabase";
+import { UserDatabase } from "./UserDatabase";
+
+export class PostDatabase extends BaseDatabase {
+ public static TABLE_POSTS = "posts";
+ public static TABLE_LIKES_DISLIKES = "likes_dislikes";
+
+ public findPostsWithCreatorName = async (): Promise<
+ PostDBWithCreatorName[]
+ > => {
+ const result: PostDB[] = await BaseDatabase.connection(
+ PostDatabase.TABLE_POSTS
+ )
+ .select(
+ `${PostDatabase.TABLE_POSTS}.id`,
+ `${PostDatabase.TABLE_POSTS}.creator_id`,
+ `${PostDatabase.TABLE_POSTS}.content`,
+ `${PostDatabase.TABLE_POSTS}.likes`,
+ `${PostDatabase.TABLE_POSTS}.dislikes`,
+ `${PostDatabase.TABLE_POSTS}.created_at`,
+ `${PostDatabase.TABLE_POSTS}.updated_at`,
+ `${UserDatabase.TABLE_USERS}.name as creator_name`
+ )
+ .join(
+ `${UserDatabase.TABLE_USERS}`,
+ `${PostDatabase.TABLE_POSTS}.creator_id`,
+ "=",
+ `${UserDatabase.TABLE_USERS}.id`
+ );
+ return result as PostDBWithCreatorName[];
+ };
+
+ public createPost = async (newPost: PostDB): Promise => {
+ await BaseDatabase.connection(PostDatabase.TABLE_POSTS).insert(newPost);
+ };
+
+ public findPostById = async (id: string): Promise => {
+ const [postDB]: PostDB[] | undefined[] = await BaseDatabase.connection(
+ PostDatabase.TABLE_POSTS
+ ).where({ id });
+
+ return postDB;
+ };
+
+ public editPost = async (newPost: PostDB): Promise => {
+ await BaseDatabase.connection(PostDatabase.TABLE_POSTS)
+ .update(newPost)
+ .where({ id: newPost.id });
+ };
+
+ public removePost = async (id: string): Promise => {
+ await BaseDatabase.connection(PostDatabase.TABLE_POSTS).del().where({ id });
+ };
+
+ public findPostsWithCreatorNameById = async (
+ id: string
+ ): Promise => {
+ const [result] = await BaseDatabase.connection(PostDatabase.TABLE_POSTS)
+ .select(
+ `${PostDatabase.TABLE_POSTS}.id`,
+ `${PostDatabase.TABLE_POSTS}.creator_id`,
+ `${PostDatabase.TABLE_POSTS}.content`,
+ `${PostDatabase.TABLE_POSTS}.likes`,
+ `${PostDatabase.TABLE_POSTS}.dislikes`,
+ `${PostDatabase.TABLE_POSTS}.created_at`,
+ `${PostDatabase.TABLE_POSTS}.updated_at`,
+ `${UserDatabase.TABLE_USERS}.name as creator_name`
+ )
+ .join(
+ `${UserDatabase.TABLE_USERS}`,
+ `${PostDatabase.TABLE_POSTS}.creator_id`,
+ "=",
+ `${UserDatabase.TABLE_USERS}.id`
+ )
+ .where({
+ [`${PostDatabase.TABLE_POSTS}.id`]: id,
+ });
+ return result as PostDBWithCreatorName | undefined;
+ };
+}
diff --git a/src/database/UserDatabase.ts b/src/database/UserDatabase.ts
new file mode 100644
index 00000000..13791fd7
--- /dev/null
+++ b/src/database/UserDatabase.ts
@@ -0,0 +1,17 @@
+import { UserDB } from "../models/User";
+import { BaseDatabase } from "./BaseDatabase";
+
+export class UserDatabase extends BaseDatabase {
+ public static TABLE_USERS = "users";
+
+ public async postUser(newUser: UserDB): Promise {
+ await BaseDatabase.connection(UserDatabase.TABLE_USERS).insert(newUser);
+ }
+
+ public async findUserByEmail(email: string): Promise {
+ const [userDB]: UserDB[] | undefined[] = await BaseDatabase.connection(
+ UserDatabase.TABLE_USERS
+ ).where({ email });
+ return userDB;
+ }
+}
diff --git a/src/database/projetoBack_End.sql b/src/database/projetoBack_End.sql
index b3df6a74..9a095d49 100644
--- a/src/database/projetoBack_End.sql
+++ b/src/database/projetoBack_End.sql
@@ -1,34 +1,59 @@
--- Active: 1682949688943@@127.0.0.1@3306
-CREATE TABLE users (
- id TEXT PRIMARY KEY UNIQUE NOT NULL,
- name TEXT NOT NULL,
- email TEXT NOT NULL,
- password TEXT NOT NULL,
- created_at TEXT DEFAULT (DATETIME()) NOT NULL
-);
+-- Active: 1683738400790@@127.0.0.1@3306
+
+CREATE TABLE
+ users (
+ id TEXT PRIMARY KEY UNIQUE NOT NULL,
+ name TEXT NOT NULL,
+ email TEXT UNIQUE NOT NULL,
+ password TEXT NOT NULL,
+ role TEXT NOT NULL,
+ created_at TEXT DEFAULT (DATETIME()) NOT NULL
+ );
CREATE TABLE
posts (
id TEXT PRIMARY KEY UNIQUE NOT NULL,
+ creator_id TEXT NOT NULL,
content TEXT NOT NULL,
likes INTEGER DEFAULT(0) NOT NULL,
dislikes INTEGER DEFAULT(0) NOT NULL,
created_at TEXT DEFAULT (DATETIME()) NOT NULL,
updated_at TEXT DEFAULT (DATETIME()) NOT NULL,
- creator_id TEXT NOT NULL,
- FOREIGN KEY (creator_id) REFERENCES users(id)
- );
- CREATE TABLE
- likes_dislikes (
- user_id TEXT NOT NULL,
- post_id TEXT NOT NULL,
- like INTEGER DEFAULT (0) NOT NULL,
- FOREIGN KEY (user_id) REFERENCES users (id),
- FOREIGN KEY (post_id) REFERENCES posts (id)
+ FOREIGN KEY (creator_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-
-SELECT * FROM users;
+
+
+SELECT * FROM users;
SELECT * FROM posts;
-SELECT * FROM likes_dislikes;
+DROP TABLE users;
+DROP TABLE posts;
+
+
+INSERT INTO
+ users (id, name, email, password, role)
+VALUES
+
+ -- tipo ADMIN e senha = astrodev99
+ (
+ 'u003',
+ 'Astrodev',
+ 'astrodev@email.com',
+ '$2a$12$lHyD.hKs3JDGu2nIbBrxYujrnfIX5RW5oq/B41HCKf7TSaq9RgqJ.',
+ 'ADMIN'
+ );
+
+INSERT INTO
+ posts (id, creator_id, content)
+VALUES (
+ 'p001',
+ 'u003',
+ 'Olá'
+ );
+
+
+INSERT INTO
+ likes_dislikes (user_id, post_id, like)
+VALUES ('u003', 'p001', 1), ('u003', 'p002', 0);
+
diff --git a/src/dtos/Post/createPost.dto.ts b/src/dtos/Post/createPost.dto.ts
new file mode 100644
index 00000000..31d73ed0
--- /dev/null
+++ b/src/dtos/Post/createPost.dto.ts
@@ -0,0 +1,15 @@
+import z from "zod";
+
+export interface CreatePostInputDTO {
+ token: string;
+ content: string;
+}
+
+export type CreatePostOutputDTO = undefined;
+
+export const CreatePostSchema = z
+ .object({
+ token: z.string().min(1),
+ content: z.string().min(1),
+ })
+ .transform((data) => data as CreatePostInputDTO);
diff --git a/src/dtos/Post/delete.dto.ts b/src/dtos/Post/delete.dto.ts
new file mode 100644
index 00000000..df37d234
--- /dev/null
+++ b/src/dtos/Post/delete.dto.ts
@@ -0,0 +1,15 @@
+import z from "zod";
+
+export interface DeletePostInputDTO {
+ idToDelete: string;
+ token: string;
+}
+
+export type DeletePostOutputDTO = undefined;
+
+export const DeletePostSchema = z
+ .object({
+ idToDelete: z.string().min(1),
+ token: z.string().min(1),
+ })
+ .transform((data) => data as DeletePostInputDTO);
diff --git a/src/dtos/Post/editPost.dto.ts b/src/dtos/Post/editPost.dto.ts
new file mode 100644
index 00000000..4e5627f8
--- /dev/null
+++ b/src/dtos/Post/editPost.dto.ts
@@ -0,0 +1,17 @@
+import z from "zod";
+
+export interface EditPostInputDTO {
+ idToEdit: string;
+ token: string;
+ content: string;
+}
+
+export type EditPostOutputDTO = undefined;
+
+export const EditPostSchema = z
+ .object({
+ idToEdit: z.string().min(1),
+ token: z.string().min(1),
+ content: z.string().min(1),
+ })
+ .transform((data) => data as EditPostInputDTO);
diff --git a/src/dtos/Post/getPosts.dto.ts b/src/dtos/Post/getPosts.dto.ts
new file mode 100644
index 00000000..51b68ea0
--- /dev/null
+++ b/src/dtos/Post/getPosts.dto.ts
@@ -0,0 +1,14 @@
+import z from "zod";
+import { PostModel } from "../../models/Posts";
+
+export interface GetPostsInputDTO {
+ token: string;
+}
+
+export type GetPostsOutputDTO = PostModel[];
+
+export const GetPostsSchema = z
+ .object({
+ token: z.string().min(1),
+ })
+ .transform((data) => data as GetPostsInputDTO);
diff --git a/src/dtos/User/login.dto.ts b/src/dtos/User/login.dto.ts
new file mode 100644
index 00000000..53c671e7
--- /dev/null
+++ b/src/dtos/User/login.dto.ts
@@ -0,0 +1,17 @@
+import z from "zod";
+
+export interface LoginInputDTO {
+ email: string;
+ password: string;
+}
+
+export interface LoginOutputDTO {
+ token: string;
+}
+
+export const LoginSchema = z
+ .object({
+ email: z.string().email(),
+ password: z.string().min(6),
+ })
+ .transform((data) => data as LoginInputDTO);
diff --git a/src/dtos/User/signup.dto.ts b/src/dtos/User/signup.dto.ts
new file mode 100644
index 00000000..fa35c3aa
--- /dev/null
+++ b/src/dtos/User/signup.dto.ts
@@ -0,0 +1,19 @@
+import z from "zod";
+
+export interface SignupInputDTO {
+ name: string;
+ email: string;
+ password: string;
+}
+
+export interface SignupOutputDTO {
+ token: string;
+}
+
+export const SignupSchema = z
+ .object({
+ name: z.string().min(2),
+ email: z.string().email(),
+ password: z.string().min(6),
+ })
+ .transform((data) => data as SignupInputDTO);
diff --git a/src/index.ts b/src/index.ts
index b543ee71..572d0735 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,14 +1,19 @@
import express from "express";
import cors from "cors";
+import dotenv from "dotenv";
+import { postRouter } from "./router/postRouter";
+import { userRouter } from "./router/userRouter";
-// import { userRouter } from "./router/userRouter";
-// import { accountRouter } from "./router/accountRouter";
+dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
-app.listen(3003, () => {
- console.log(`Servidor rodando na porta ${3003}`);
+app.listen(Number(process.env.PORT) || 3003, () => {
+ console.log(`server on port ${Number(process.env.PORT) || 3003}`);
});
+
+app.use("/posts", postRouter);
+app.use("/users", userRouter);
diff --git a/src/models/Posts.ts b/src/models/Posts.ts
new file mode 100644
index 00000000..2b9122b6
--- /dev/null
+++ b/src/models/Posts.ts
@@ -0,0 +1,146 @@
+export interface PostDB {
+ id: string;
+ creator_id: string;
+ content: string;
+ likes: number;
+ dislikes: number;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface PostDBWithCreatorName {
+ id: string;
+ creator_id: string;
+ content: string;
+ likes: number;
+ dislikes: number;
+ created_at: string;
+ updated_at: string;
+ creator_name: string;
+}
+
+export interface PostModel {
+ id: string;
+ content: string;
+ likes: number;
+ dislikes: number;
+ created_at: string;
+ updated_at: string;
+ creator: {
+ id: string;
+ name: string;
+ };
+}
+
+export interface LikeDislikeDB {
+ user_id: string;
+ post_id: string;
+ like: number;
+}
+
+export enum POST_LIKE {
+ ALREADY_LIKED = "ALREADY_LIKED",
+ ALREADY_DISLIKED = "ALREADY_DISLIKED",
+}
+
+export class Post {
+ constructor(
+ private id: string,
+ private content: string,
+ private likes: number,
+ private dislikes: number,
+ private createdAt: string,
+ private updatedAt: string,
+ private creatorId: string,
+ private creatorName: string
+ ) {}
+
+ public getId(): string {
+ return this.id;
+ }
+
+ public getContent(): string {
+ return this.content;
+ }
+ public setContent(value: string) {
+ this.content = value;
+ }
+
+ public getLikes(): number {
+ return this.likes;
+ }
+ public setLikes(value: number) {
+ this.likes = value;
+ }
+ public addLike = (): void => {
+ this.likes++;
+ };
+ public removeLike = (): void => {
+ this.likes--;
+ };
+
+ public getDislikes(): number {
+ return this.dislikes;
+ }
+ public setDislikes(value: number) {
+ this.dislikes = value;
+ }
+ public addDislike = (): void => {
+ this.dislikes++;
+ };
+ public removeDislike = (): void => {
+ this.dislikes--;
+ };
+
+ public getCreatedAt(): string {
+ return this.createdAt;
+ }
+
+ public getUdatedAt(): string {
+ return this.updatedAt;
+ }
+ public setUpdatedAt(value: string) {
+ this.updatedAt = value;
+ }
+
+ public getCreatorId(): string {
+ return this.creatorId;
+ }
+ public setCreatorId(value: string) {
+ this.creatorId = value;
+ }
+
+ public getCreatorName(): string {
+ return this.creatorName;
+ }
+ public setCreatorName(value: string) {
+ this.creatorName = value;
+ }
+
+ public toDBModel(): PostDB {
+ return {
+ id: this.id,
+ creator_id: this.creatorId,
+ content: this.content,
+ likes: this.likes,
+ dislikes: this.dislikes,
+ created_at: this.createdAt,
+ updated_at: this.updatedAt,
+ };
+ }
+
+ public toBusinessModel(): PostModel {
+ return {
+ id: this.id,
+ content: this.content,
+ likes: this.likes,
+ dislikes: this.dislikes,
+ created_at: this.createdAt,
+ updated_at: this.updatedAt,
+ creator: {
+ id: this.creatorId,
+ name: this.creatorName,
+ },
+ };
+ }
+}
diff --git a/src/models/User.ts b/src/models/User.ts
new file mode 100644
index 00000000..168c684b
--- /dev/null
+++ b/src/models/User.ts
@@ -0,0 +1,95 @@
+export enum USER_ROLES {
+ NORMAL = "NORMAL",
+ ADMIN = "ADMIN",
+}
+
+export interface TokenPayload {
+ id: string;
+ name: string;
+ role: USER_ROLES;
+}
+
+export interface UserDB {
+ id: string;
+ name: string;
+ email: string;
+ password: string;
+ role: USER_ROLES;
+ created_at: string;
+}
+
+export interface UserModel {
+ id: string;
+ name: string;
+ email: string;
+ role: USER_ROLES;
+ createdAt: string;
+}
+
+export class User {
+ constructor(
+ private id: string,
+ private name: string,
+ private email: string,
+ private password: string,
+ private role: USER_ROLES,
+ private createdAt: string
+ ) {}
+
+ public getId(): string {
+ return this.id;
+ }
+
+ public getName(): string {
+ return this.name;
+ }
+ public setName(value: string) {
+ this.name = value;
+ }
+
+ public getEmail(): string {
+ return this.email;
+ }
+ public setEmail(value: string) {
+ this.email = value;
+ }
+
+ public getPassaword(): string {
+ return this.password;
+ }
+ public setPassword(value: string) {
+ this.password = value;
+ }
+
+ public getRole(): USER_ROLES {
+ return this.role;
+ }
+ public setRole(value: USER_ROLES) {
+ this.role = value;
+ }
+
+ public getCreatedAt(): string {
+ return this.createdAt;
+ }
+
+ public toDBModel(): UserDB {
+ return {
+ id: this.id,
+ name: this.name,
+ email: this.email,
+ password: this.password,
+ role: this.role,
+ created_at: this.createdAt,
+ };
+ }
+
+ public toBusinessModel(): UserModel {
+ return {
+ id: this.id,
+ name: this.name,
+ email: this.email,
+ role: this.role,
+ createdAt: this.createdAt,
+ };
+ }
+}
diff --git a/src/router/PostRouter.ts b/src/router/PostRouter.ts
index e69de29b..f3dbd089 100644
--- a/src/router/PostRouter.ts
+++ b/src/router/PostRouter.ts
@@ -0,0 +1,17 @@
+import express from "express";
+import { PostBusiness } from "../business/PostBusiness";
+import { PostControlers } from "../controller/PostController";
+import { PostDatabase } from "../database/PostDatabase";
+import { IdGenerator } from "../services/IdGenerator";
+import { TokenManager } from "../services/TokenManager";
+
+export const postRouter = express.Router();
+
+const postController = new PostControlers(
+ new PostBusiness(new PostDatabase(), new IdGenerator(), new TokenManager())
+);
+
+postRouter.get("/", postController.getPosts);
+postRouter.post("/", postController.postPost);
+postRouter.put("/:id", postController.putPost);
+postRouter.delete("/:id", postController.deletePosts);
diff --git a/src/router/UserRouter.ts b/src/router/UserRouter.ts
index e69de29b..68b49017 100644
--- a/src/router/UserRouter.ts
+++ b/src/router/UserRouter.ts
@@ -0,0 +1,21 @@
+import express from "express";
+import { UserBusiness } from "../business/UserBusiness";
+import { UserController } from "../controller/UserController";
+import { UserDatabase } from "../database/UserDatabase";
+import { HashManager } from "../services/HashManeger";
+import { IdGenerator } from "../services/IdGenerator";
+import { TokenManager } from "../services/TokenManager";
+
+export const userRouter = express.Router();
+
+const userController = new UserController(
+ new UserBusiness(
+ new UserDatabase(),
+ new IdGenerator(),
+ new TokenManager(),
+ new HashManager()
+ )
+);
+
+userRouter.post("/signup", userController.signup);
+userRouter.post("/login", userController.login);
diff --git a/src/services/HashManeger.ts b/src/services/HashManeger.ts
new file mode 100644
index 00000000..8e5576e2
--- /dev/null
+++ b/src/services/HashManeger.ts
@@ -0,0 +1,21 @@
+import bcrypt from "bcryptjs";
+import dotenv from "dotenv";
+
+dotenv.config();
+
+export class HashManager {
+ public hash = async (plaintext: string): Promise => {
+ const rounds = Number(process.env.BCRYPT_COST);
+ const salt = await bcrypt.genSalt(rounds);
+ const hash = await bcrypt.hash(plaintext, salt);
+
+ return hash;
+ };
+
+ public compare = async (
+ plaintext: string,
+ hash: string
+ ): Promise => {
+ return bcrypt.compare(plaintext, hash);
+ };
+}
diff --git a/src/services/IdGenerator.ts b/src/services/IdGenerator.ts
new file mode 100644
index 00000000..5f327f66
--- /dev/null
+++ b/src/services/IdGenerator.ts
@@ -0,0 +1,7 @@
+import { v4 } from "uuid";
+
+export class IdGenerator {
+ public generate = (): string => {
+ return v4();
+ };
+}
diff --git a/src/services/TokenManager.ts b/src/services/TokenManager.ts
new file mode 100644
index 00000000..306d2525
--- /dev/null
+++ b/src/services/TokenManager.ts
@@ -0,0 +1,24 @@
+import jwt from "jsonwebtoken";
+import dotenv from "dotenv";
+import { TokenPayload } from "../models/User";
+
+dotenv.config();
+
+export class TokenManager {
+ public createToken = (payload: TokenPayload): string => {
+ const token = jwt.sign(payload, process.env.JWT_KEY as string, {
+ expiresIn: process.env.JWT_EXPIRES_IN,
+ });
+ return token;
+ };
+
+ public getPayload = (token: string): TokenPayload | null => {
+ try {
+ const payload = jwt.verify(token, process.env.JWT_KEY as string);
+
+ return payload as TokenPayload;
+ } catch (error) {
+ return null;
+ }
+ };
+}
diff --git a/src/types.ts b/src/types.ts
deleted file mode 100644
index e69de29b..00000000