diff --git a/.env b/.env index cedca2d0..5daf1e51 100644 --- a/.env +++ b/.env @@ -1,9 +1,8 @@ PORT=3003 -DB_FILE_PATH=./src/database/Labook.db +DB_FILE_PATH=./src/database/nome-do-arquivo.db -JWT_KEY=minha-senha-segura-bananinha +JWT_KEY=senha-de-exemplo-jwt-key JWT_EXPIRES_IN=7d - BCRYPT_COST=12 \ No newline at end of file diff --git a/.env.example b/.env.example deleted file mode 100644 index 5daf1e51..00000000 --- a/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -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 6793094c..22f8e2c1 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,10 @@ O Labook é uma rede social com o objetivo de promover a conexão e interação ## Documentação da API -Link Demonstração: +[Link Demonstração](https://documenter.getpostman.com/view/25825355/2s93ebUBDa) ## Estruturação do banco de dados -img aqui ![projeto-labook (2)](https://user-images.githubusercontent.com/29845719/216036534-2b3dfb48-7782-411a-bffd-36245b78594e.png) ## Requisitos: @@ -36,7 +35,7 @@ img aqui - [x] create post - [x] edit post - [x] delete post - - [x] like / dislike post + - [] like / dislike post - Autenticação e autorização @@ -83,7 +82,6 @@ $ npm run dev 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) - (https://pt.wikipedia.org/wiki/Autentica%C3%A7%C3%A3o) 11. [Roteamento](https://acervolima.com/roteamento-em-node-js/) 12. [Postman](https://www.postman.com/) diff --git a/src/business/PostBusiness.ts b/src/business/PostBusiness.ts index 9cf352ea..565242bd 100644 --- a/src/business/PostBusiness.ts +++ b/src/business/PostBusiness.ts @@ -1,80 +1,169 @@ import { PostDatabase } from "../database/PostDatabase"; -import { BadRequestError } from "../errors/BadRequestError"; -import { NotFoundError } from "../errors/NotFoundError"; -import { PostDB } from "../types"; -import { Post } from "../models/Post"; -import { IdGenerator } from "../sevices/IdGenerator"; -import { TokenManager } from "../sevices/TokenManager"; -import { GetPostInputDTO, GetPostOutputDTO } from "../dtos/Post/getPosts.dto"; 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 tokenManage: TokenManager + private tokenManeger: TokenManager ) {} - public getPosts = async ( - input: GetPostInputDTO - ): Promise => { - const { q, token } = input; + public getPost = async ( + input: GetPostsInputDTO + ): Promise => { + const { token } = input; - const payload = this.tokenManage.getPayload(token); + const payload = this.tokenManeger.getPayload(token); - if (payload === null) { - throw new Error("Você não esta logado"); + if (!payload) { + throw new BadRequestError("Token inválido."); } - const postsDB = await this.postDatabase.findePosts(q); - - const post = postsDB.map((postDB) => { - const product = new Post( - postDB.id, - postDB.content, - postDB.likes, - postDB.deslikes, - postDB.created_at, - postDB.updated_at, - postDB.creator_id - ); + const postsWithCreatorName = + await this.postDatabase.findPostsWithCreatorName(); - return product.toBusinessModel(); + 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: GetPostOutputDTO = post; + 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; }; - // public createProduct = async ( - // input: CreatePostInputDTO - // ): Promise => { - // const { name, content, like, deslike } = input; - - // const id = this.idGenerator.generate(); - - // const newProduct = new Post( - // id, - // name, - // content, - // like, - // deslike, - // new Date().toISOString(), - // new Date().toISOString(), - // creatorId - // ); - - // const newProductDB = newProduct.toDBModel(); - // await this.PostDatabase.insertProduct(newProductDB); - - // const output: CreateProductOutputDTO = { - // message: "Producto cadastrado com sucesso", - // product: newProduct.toBusinessModel(), - // }; - - // return output; - // }; } diff --git a/src/business/UserBusiness.ts b/src/business/UserBusiness.ts index 2e3477c0..b925904b 100644 --- a/src/business/UserBusiness.ts +++ b/src/business/UserBusiness.ts @@ -1,121 +1,101 @@ import { UserDatabase } from "../database/UserDatabase"; -import { GetUsersInputDTO, GetUsersOutputDTO } from "../dtos/User/getUser.dto"; 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 { User } from "../models/User"; -import { HashManager } from "../sevices/HashManager"; -import { IdGenerator } from "../sevices/IdGenerator"; -import { TokenManager } from "../sevices/TokenManager"; -import { TokenPayload, UserDB } from "../types"; +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 tokenManager: TokenManager, - private hashManager: HashManager + private tokenManeger: TokenManager, + private hashManeger: HashManager ) {} - public getUsers = async ( - input: GetUsersInputDTO - ): Promise => { - const { q, token } = input; - - const payload = this.tokenManager.getPayload(token); - - if (payload === null) { - throw new BadRequestError("token invalido"); - } - - const usersDB = await this.userDatabase.findUsers(q); - const users = usersDB.map((userDB) => { - const user = new User( - userDB.id, - userDB.name, - userDB.email, - userDB.password, - userDB.created_at - ); - - return user.toBusinessModel(); - }); - - const output: GetUsersOutputDTO = users; + public signup = async (input: SignupInputDTO) => { + const { name, email, password } = input; - return output; - }; + const userBDExists = await this.userDatabase.findUserByEmail(email); - public signup = async (input: SignupInputDTO): Promise => { - const { name, email, password } = input; + if (userBDExists) { + throw new BadRequestError("Esse e-mail já foi cadastrado"); + } const id = this.idGenerator.generate(); - - const hashPassword = await this.hashManager.hash(password); + const hashedPassword = await this.hashManeger.hash(password); const newUser = new User( id, name, email, - hashPassword, + hashedPassword, + USER_ROLES.NORMAL, new Date().toISOString() ); const newUserDB = newUser.toDBModel(); - await this.userDatabase.insertUser(newUserDB); + await this.userDatabase.postUser(newUserDB); - const tokenPayload: TokenPayload = { + const payload: TokenPayload = { id: newUser.getId(), name: newUser.getName(), + role: newUser.getRole(), }; - const token = this.tokenManager.createToken(tokenPayload); + const token = this.tokenManeger.createToken(payload); const output: SignupOutputDTO = { - message: "Cadastro realizado com sucesso", - token: token, + token, }; return output; }; - public login = async (input: LoginInputDTO): Promise => { + public userLogin = async (input: LoginInputDTO) => { const { email, password } = input; - const userDB = await this.userDatabase.findUserByEmail(email); + const userBDExists = await this.userDatabase.findUserByEmail(email); - if (!userDB) { - throw new NotFoundError("'email' não encontrado"); + if (!userBDExists) { + throw new NotFoundError("Email não encontrado"); } - const isPasswordCorrect = await this.hashManager.compare( - password, - userDB.password + const user = new User( + userBDExists.id, + userBDExists.name, + userBDExists.email, + userBDExists.password, + userBDExists.role, + userBDExists.created_at ); - if (!isPasswordCorrect) { - throw new BadRequestError("senha invalida"); - } + const hashedPassword = userBDExists.password; - const user = new User( - userDB.id, - userDB.name, - userDB.email, - userDB.password, - userDB.created_at + const isCorrectPassword = await this.hashManeger.compare( + password, + hashedPassword ); - const tokenPayload: TokenPayload = { + if (!isCorrectPassword) { + throw new BadRequestError("Email ou senha incorretos"); + } + + const payload: TokenPayload = { id: user.getId(), name: user.getName(), + role: user.getRole(), }; - const token = this.tokenManager.createToken(tokenPayload); + const token = this.tokenManeger.createToken(payload); const output: LoginOutputDTO = { - message: "Login realizado com sucesso", - token: token, + token, }; return output; diff --git a/src/controller/PostController.ts b/src/controller/PostController.ts index 63a6c9d9..b68a6faa 100644 --- a/src/controller/PostController.ts +++ b/src/controller/PostController.ts @@ -1,68 +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"; -import { GetProductsSchema } from "../dtos/Post/getPosts.dto"; -export class PostController { +export class PostControlers { constructor(private postBusiness: PostBusiness) {} + public getPosts = async (req: Request, res: Response) => { try { - const input = GetProductsSchema.parse({ - q: req.query.q, + const input = GetPostsSchema.parse({ token: req.headers.authorization, }); - - const output = await this.postBusiness.getPosts(input); + const output = await this.postBusiness.getPost(input); res.status(200).send(output); } catch (error) { console.log(error); - if (req.statusCode === 200) { - res.status(500); - } - - if (error instanceof Error) { - res.send(error.message); + if (error instanceof ZodError) { + res.status(400).send(error.issues); + } else if (error instanceof BaseError) { + res.status(error.statusCode).send(error.message); } else { - res.send("Erro inesperado"); + res.status(500).send("Erro inesperado"); } } }; - public createPost = async ( - input: CreateProductInputDTO - ): Promise => { - // const { id, name, price } = input - const { name, price, token } = input; - const payload = this.tokenManage.getPayload(token); + public postPost = async (req: Request, res: Response) => { + try { + const input = CreatePostSchema.parse({ + token: req.headers.authorization, + content: req.body.content, + }); - if (payload === null) { - throw new Error("Você não esta logado"); - } + const output = await this.postBusiness.postPost(input); - if (payload.role !== USER_ROLES.ADMIN) { - throw new BadRequestError("você não é admin"); + 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"); + } } + }; - // const productDBExists = await this.productDatabase.findProductById(id) + public putPost = async (req: Request, res: Response) => { + try { + const input = EditPostSchema.parse({ + token: req.headers.authorization, + idToEdit: req.params.id, + content: req.body.content, + }); - // if (productDBExists) { - // throw new BadRequestError("'id' já existe") - // } + const output = await this.postBusiness.putPost(input); - const id = this.idGenerator.generate(); + res.status(200).send(output); + } catch (error) { + console.log(error); - const newProduct = new Product(id, name, price, new Date().toISOString()); + 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"); + } + } + }; - const newProductDB = newProduct.toDBModel(); - await this.productDatabase.insertProduct(newProductDB); + 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); - const output: CreateProductOutputDTO = { - message: "Producto cadastrado com sucesso", - product: newProduct.toBusinessModel(), - }; + res.status(200).send(output); + } catch (error) { + console.log(error); - return output; + 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 index 75d7205a..5ea87088 100644 --- a/src/controller/UserController.ts +++ b/src/controller/UserController.ts @@ -1,37 +1,13 @@ import { Request, Response } from "express"; -import { UserBusiness } from "../business/UserBusiness"; -import { BaseError } from "../errors/BaseError"; -import { GetUsersSchema } from "../dtos/User/getUser.dto"; import { ZodError } from "zod"; -import { SignupSchema } from "../dtos/User/signup.dto"; +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 getUsers = async (req: Request, res: Response) => { - try { - const input = GetUsersSchema.parse({ - q: req.query.q, - token: req.headers.authorization, - }); - - const output = await this.userBusiness.getUsers(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 signup = async (req: Request, res: Response) => { try { const input = SignupSchema.parse({ @@ -55,6 +31,7 @@ export class UserController { } } }; + public login = async (req: Request, res: Response) => { try { const input = LoginSchema.parse({ @@ -62,7 +39,7 @@ export class UserController { password: req.body.password, }); - const output = await this.userBusiness.login(input); + const output = await this.userBusiness.userLogin(input); res.status(200).send(output); } catch (error) { diff --git a/src/database/BaseDatabase.ts b/src/database/BaseDatabase.ts index 8302a931..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/Labook.db", + filename: process.env.DB_FILE_PATH as string, }, useNullAsDefault: true, pool: { diff --git a/src/database/PostDatabase.ts b/src/database/PostDatabase.ts index 868256d2..a83f467d 100644 --- a/src/database/PostDatabase.ts +++ b/src/database/PostDatabase.ts @@ -1,40 +1,86 @@ -import { PostDB } from "../types"; +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_POST = "posts"; - - public async findePosts(q: string | undefined) { - let postDB; - if (q) { - const result: PostDB[] = await BaseDatabase.connection( - PostDatabase.TABLE_POST - ).where("name", "LIKE", `%${q}%`); - - postDB = result; - } else { - const result: PostDB[] = await BaseDatabase.connection( - PostDatabase.TABLE_POST + 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[]; + }; - postDB = result; - } + public createPost = async (newPost: PostDB): Promise => { + await BaseDatabase.connection(PostDatabase.TABLE_POSTS).insert(newPost); + }; - return postDB; - } - public async findPostById(id: string) { - const [postsDB]: PostDB[] | undefined[] = await BaseDatabase.connection( - PostDatabase.TABLE_POST + public findPostById = async (id: string): Promise => { + const [postDB]: PostDB[] | undefined[] = await BaseDatabase.connection( + PostDatabase.TABLE_POSTS ).where({ id }); - return postsDB; - } - public async insertPost(newPostDB: PostDB) { - await BaseDatabase.connection(PostDatabase.TABLE_POST).insert(newPostDB); - } - public async updatePostById(id: string, newPost: number) { - await BaseDatabase.connection(PostDatabase.TABLE_POST) - .update({ content: newPost }) - .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 index 33203eb7..13791fd7 100644 --- a/src/database/UserDatabase.ts +++ b/src/database/UserDatabase.ts @@ -1,44 +1,17 @@ -import { UserDB } from "../types"; +import { UserDB } from "../models/User"; import { BaseDatabase } from "./BaseDatabase"; export class UserDatabase extends BaseDatabase { public static TABLE_USERS = "users"; - public async findUsers(q: string | undefined) { - let usersDB; - - if (q) { - const result: UserDB[] = await BaseDatabase.connection( - UserDatabase.TABLE_USERS - ).where("name", "LIKE", `%${q}%`); - - usersDB = result; - } else { - const result: UserDB[] = await BaseDatabase.connection( - UserDatabase.TABLE_USERS - ); - - usersDB = result; - } - - return usersDB; + public async postUser(newUser: UserDB): Promise { + await BaseDatabase.connection(UserDatabase.TABLE_USERS).insert(newUser); } - public async findUserById(id: string) { - const [userDB]: UserDB[] | undefined[] = await BaseDatabase.connection( - UserDatabase.TABLE_USERS - ).where({ id }); - - return userDB; - } public async findUserByEmail(email: string): Promise { const [userDB]: UserDB[] | undefined[] = await BaseDatabase.connection( UserDatabase.TABLE_USERS ).where({ email }); - return userDB; } - public async insertUser(newUserDB: UserDB) { - await BaseDatabase.connection(UserDatabase.TABLE_USERS).insert(newUserDB); - } } diff --git a/src/database/projetoBack_End.sql b/src/database/projetoBack_End.sql index 3c594644..9a095d49 100644 --- a/src/database/projetoBack_End.sql +++ b/src/database/projetoBack_End.sql @@ -1,41 +1,59 @@ --- Active: 1683035450919@@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) + FOREIGN KEY (creator_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE ); - 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) - ); - -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á' + ); + -DROP TABLE likes_dislikes; +INSERT INTO + likes_dislikes (user_id, post_id, like) +VALUES ('u003', 'p001', 1), ('u003', 'p002', 0); -INSERT INTO users (id, name, email, password) -VALUES ("u001", "Michelle Antunes", "michelle@email.com", "paswword123"); -INSERT INTO posts (id, content, likes, dislikes,creator_id) -VALUES ("p001", "Hoje vamos estudar muito!", 1, 0, "u001"); \ No newline at end of file diff --git a/src/dtos/Post/createPost.dto.ts b/src/dtos/Post/createPost.dto.ts index f3e647f3..31d73ed0 100644 --- a/src/dtos/Post/createPost.dto.ts +++ b/src/dtos/Post/createPost.dto.ts @@ -1,24 +1,15 @@ import z from "zod"; -import { PostModel } from "../../types"; - export interface CreatePostInputDTO { - name: string; + token: string; content: string; - like: number; - deslike: number; } -export interface CreatePostOutputDTO { - message: string; - content: PostModel; -} +export type CreatePostOutputDTO = undefined; -export const CreateProductSchema = z +export const CreatePostSchema = z .object({ - // id: z.string().min(1), - name: z.string().min(2), - price: z.number().gt(0), 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 index a0234473..51b68ea0 100644 --- a/src/dtos/Post/getPosts.dto.ts +++ b/src/dtos/Post/getPosts.dto.ts @@ -1,17 +1,14 @@ import z from "zod"; +import { PostModel } from "../../models/Posts"; -import { PostModel } from "../../types"; - -export interface GetPostInputDTO { - q: string; +export interface GetPostsInputDTO { token: string; } -export type GetPostOutputDTO = PostModel[]; +export type GetPostsOutputDTO = PostModel[]; -export const GetProductsSchema = z +export const GetPostsSchema = z .object({ - q: z.string().min(1).optional(), token: z.string().min(1), }) - .transform((data) => data as GetPostInputDTO); + .transform((data) => data as GetPostsInputDTO); diff --git a/src/dtos/User/getUser.dto.ts b/src/dtos/User/getUser.dto.ts deleted file mode 100644 index 679290fe..00000000 --- a/src/dtos/User/getUser.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import z from "zod"; -import { UserModel } from "../../types"; - -export interface GetUsersInputDTO { - q: string; - token: string; -} - -// UserModel é a estrutura de User que será devolvida para o Front (sem password) -export type GetUsersOutputDTO = UserModel[]; - -export const GetUsersSchema = z - .object({ - q: z.string().min(1).optional(), - token: z.string().min(1), - }) - .transform((data) => data as GetUsersInputDTO); diff --git a/src/dtos/User/login.dto.ts b/src/dtos/User/login.dto.ts index b092420d..53c671e7 100644 --- a/src/dtos/User/login.dto.ts +++ b/src/dtos/User/login.dto.ts @@ -6,13 +6,12 @@ export interface LoginInputDTO { } export interface LoginOutputDTO { - message: string; token: string; } export const LoginSchema = z .object({ email: z.string().email(), - password: z.string().min(4), + 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 index 77bbf7e3..fa35c3aa 100644 --- a/src/dtos/User/signup.dto.ts +++ b/src/dtos/User/signup.dto.ts @@ -1,22 +1,19 @@ import z from "zod"; export interface SignupInputDTO { - // id: string, name: string; email: string; password: string; } export interface SignupOutputDTO { - message: string; token: string; } export const SignupSchema = z .object({ - // id: z.string().min(1), name: z.string().min(2), email: z.string().email(), - password: z.string().min(4), + password: z.string().min(6), }) .transform((data) => data as SignupInputDTO); diff --git a/src/index.ts b/src/index.ts index c5c95124..572d0735 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,18 @@ import express from "express"; import cors from "cors"; -import { postRouter } from "./router/PostRouter"; -import { userRouter } from "./router/UserRouter"; +import dotenv from "dotenv"; +import { postRouter } from "./router/postRouter"; +import { userRouter } from "./router/userRouter"; + +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); diff --git a/src/models/LikeDeslike.ts b/src/models/LikeDeslike.ts deleted file mode 100644 index 47ae39e7..00000000 --- a/src/models/LikeDeslike.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { LikeDeslikeDB, LikeDeslikeModel } from "../types"; - -export class LikeDeslike { - constructor( - private userId: string, - private postId: string, - private like: number - ) {} - - public getUserId(): string { - return this.userId; - } - public setUserId(value: string): void { - this.userId = value; - } - public getPostId(): string { - return this.postId; - } - public setPostId(value: string): void { - this.postId = value; - } - public getLike(): number { - return this.like; - } - public setLike(value: number): void { - this.like = value; - } - public toBusinessModel(): LikeDeslikeModel { - return { - userId: this.userId, - postId: this.postId, - like: this.like, - }; - } - public toDBModel(): LikeDeslikeDB { - return { - user_id: this.userId, - post_id: this.postId, - like: this.like, - }; - } -} diff --git a/src/models/Post.ts b/src/models/Post.ts deleted file mode 100644 index 94584979..00000000 --- a/src/models/Post.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { PostDB, PostModel } from "../types"; - -export class Post { - constructor( - private id: string, - private content: string, - private likes: number, - private deslikes: number, - private createdAt: string, - private updatedAt: string, - private creatorId: string - ) {} - - public getId(): string { - return this.id; - } - public setId(value: string): void { - this.id = value; - } - public getContent(): string { - return this.content; - } - public setContent(value: string): void { - this.content = value; - } - public getLike(): number { - return this.likes; - } - public setLike(value: number): void { - this.likes = value; - } - public getDeslike(): number { - return this.deslikes; - } - public setDeslike(value: number): void { - this.deslikes = value; - } - public getCreatedAt(): string { - return this.createdAt; - } - public setCreatedAt(value: string): void { - this.createdAt = value; - } - public getUpdateAt(): string { - return this.updatedAt; - } - public setUpdateAt(value: string): void { - this.updatedAt = value; - } - public getCreatorId(): string { - return this.creatorId; - } - public setCreatorId(value: string): void { - this.creatorId = value; - } - public toBusinessModel(): PostModel { - return { - id: this.id, - content: this.content, - likes: this.likes, - deslikes: this.deslikes, - createdAt: this.createdAt, - updatedAt: this.updatedAt, - creatorId: this.creatorId, - }; - } - public toDBModel(): PostDB { - return { - id: this.id, - content: this.content, - likes: this.likes, - deslikes: this.deslikes, - created_at: this.createdAt, - updated_at: this.updatedAt, - creator_id: this.creatorId, - }; - } -} 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 index 5fe1844e..168c684b 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -1,4 +1,30 @@ -import { UserDB, UserModel } from "../types"; +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( @@ -6,53 +32,63 @@ export class User { private name: string, private email: string, private password: string, + private role: USER_ROLES, private createdAt: string ) {} public getId(): string { return this.id; } - public setId(value: string): void { - this.id = value; - } + public getName(): string { return this.name; } - public setName(value: string): void { + public setName(value: string) { this.name = value; } + public getEmail(): string { return this.email; } - public setEmail(value: string): void { + public setEmail(value: string) { this.email = value; } - public getPassord(): string { + + public getPassaword(): string { return this.password; } - public setPassword(value: string): void { + 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 setCreatedAt(value: string): void { - this.createdAt = value; - } + 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 e32a8459..f3dbd089 100644 --- a/src/router/PostRouter.ts +++ b/src/router/PostRouter.ts @@ -1,15 +1,17 @@ import express from "express"; -import { PostController } from "../controller/PostController"; import { PostBusiness } from "../business/PostBusiness"; +import { PostControlers } from "../controller/PostController"; import { PostDatabase } from "../database/PostDatabase"; -import { IdGenerator } from "../sevices/IdGenerator"; -import { TokenManager } from "../sevices/TokenManager"; -import { HashManager } from "../sevices/HashManager"; +import { IdGenerator } from "../services/IdGenerator"; +import { TokenManager } from "../services/TokenManager"; export const postRouter = express.Router(); -const postController = new PostController( + +const postController = new PostControlers( new PostBusiness(new PostDatabase(), new IdGenerator(), new TokenManager()) ); postRouter.get("/", postController.getPosts); -// postRouter.post("/", postController.) +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 4e5236cf..68b49017 100644 --- a/src/router/UserRouter.ts +++ b/src/router/UserRouter.ts @@ -1,10 +1,10 @@ import express from "express"; -import { UserController } from "../controller/UserController"; import { UserBusiness } from "../business/UserBusiness"; +import { UserController } from "../controller/UserController"; import { UserDatabase } from "../database/UserDatabase"; -import { IdGenerator } from "../sevices/IdGenerator"; -import { TokenManager } from "../sevices/TokenManager"; -import { HashManager } from "../sevices/HashManager"; +import { HashManager } from "../services/HashManeger"; +import { IdGenerator } from "../services/IdGenerator"; +import { TokenManager } from "../services/TokenManager"; export const userRouter = express.Router(); @@ -17,5 +17,5 @@ const userController = new UserController( ) ); -userRouter.get("/", userController.getUsers); -userRouter.post("/", userController.signup); +userRouter.post("/signup", userController.signup); +userRouter.post("/login", userController.login); diff --git a/src/sevices/HashManager.ts b/src/services/HashManeger.ts similarity index 86% rename from src/sevices/HashManager.ts rename to src/services/HashManeger.ts index 46658ae5..8e5576e2 100644 --- a/src/sevices/HashManager.ts +++ b/src/services/HashManeger.ts @@ -16,7 +16,6 @@ export class HashManager { plaintext: string, hash: string ): Promise => { - // aqui não precisa do await porque o return já se comporta como um return bcrypt.compare(plaintext, hash); }; } diff --git a/src/sevices/IdGenerator.ts b/src/services/IdGenerator.ts similarity index 100% rename from src/sevices/IdGenerator.ts rename to src/services/IdGenerator.ts diff --git a/src/sevices/TokenManager.ts b/src/services/TokenManager.ts similarity index 92% rename from src/sevices/TokenManager.ts rename to src/services/TokenManager.ts index 5aca2dee..306d2525 100644 --- a/src/sevices/TokenManager.ts +++ b/src/services/TokenManager.ts @@ -1,6 +1,6 @@ import jwt from "jsonwebtoken"; import dotenv from "dotenv"; -import { TokenPayload } from "../types"; +import { TokenPayload } from "../models/User"; dotenv.config(); @@ -9,7 +9,6 @@ export class TokenManager { const token = jwt.sign(payload, process.env.JWT_KEY as string, { expiresIn: process.env.JWT_EXPIRES_IN, }); - return token; }; diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index e973d28c..00000000 --- a/src/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -export interface PostDB { - id: string; - content: string; - likes: number; - deslikes: number; - created_at: string; - updated_at: string; - creator_id: string; -} -export interface UserDB { - id: string; - name: string; - email: string; - password: string; - created_at: string; -} - -export interface PostModel { - id: string; - content: string; - likes: number; - deslikes: number; - createdAt: string; - updatedAt: string; - creatorId: string; -} -export interface UserModel { - id: string; - name: string; - email: string; - createdAt: string; -} -export interface LikeDeslikeDB { - user_id: string; - post_id: string; - like: number; -} -export interface LikeDeslikeModel { - userId: string; - postId: string; - like: number; -} -export interface TokenPayload { - id: string; - name: string; -}