From 9a70e4e9fd3f8ad59b453c790da91e7d76e782cf Mon Sep 17 00:00:00 2001
From: apsr23 <apsr23@inf.ufpr.br>
Date: Wed, 19 Mar 2025 10:56:19 -0300
Subject: [PATCH] Issue #11 ADD login user route

---
 src/handlers/user.ts            | 145 +++++++++++++++++++++-----------
 src/index.ts                    |   7 +-
 src/validators/userValidator.ts |  23 +++--
 3 files changed, 110 insertions(+), 65 deletions(-)

diff --git a/src/handlers/user.ts b/src/handlers/user.ts
index 19da118..36041a6 100644
--- a/src/handlers/user.ts
+++ b/src/handlers/user.ts
@@ -4,7 +4,7 @@ import { db } from "@/db";
 import { or, eq } from 'drizzle-orm';
 import { usersTable } from "@/db/schema";
 import bcrypt from "bcrypt"; 
-import { z } from 'zod';
+import jwt from 'jsonwebtoken';
 
 export default class User{
     static userRequestValidation (req: Request){    //valida a requisição do usuário
@@ -76,61 +76,106 @@ export default class User{
             return res.status(408).json({ error: "Error during user deletion" });
         }
     } 
-}
-
-export const createUser = async (req: Request, res: Response) => {
-    try {
-      
-      const parsedData = userSchema.safeParse(req.body);
-  
-      if (!parsedData.success) {
-        return res.status(400).json({ error: "Dados Inválidos", details: parsedData.error.errors });
+    static createUser = async (req: Request, res: Response) => {
+      try {
+        
+        // validar com o Zod
+        const parsedData = userSchema.safeParse(req.body);
+        if (!parsedData.success) {
+          return res.status(400).json({ error: "Dados Inválidos", details: parsedData.error.errors });
+        }
+        
+        // desestruturação dos dados para inserir no banco
+        const { name, email, password, birthday, cpf, money, cyberpsychosis, cyberLimit } = parsedData.data;
+    
+        const hashedPassword = await bcrypt.hash(password, 10);
+    
+        const result = await db.insert(usersTable).values({ 
+          name, 
+          email,
+          password: hashedPassword,  
+          birthday,
+          cpf,
+          money,
+          cyberpsychosis,
+          cyberLimit,
+        }).returning();
+    
+        res.status(201).json({ message: "Usuário criado com sucesso", user: result }); 
+    
+      } catch (error) {
+        console.error(error);
+        return res.status(500).json({ error: "Erro ao inserir o usuário." });
       }
+    }
   
-      const { name, email, password, birthday, cpf, money, cyberpsychosis, cyberLimit } = parsedData.data;
-  
-      const hashedPassword = await bcrypt.hash(password, 10);
-  
-      const result = await db.insert(usersTable).values({ 
-        name, 
-        email,
-        password: hashedPassword,  
-        birthday,
-        cpf,
-        money,
-        cyberpsychosis,
-        cyberLimit,
-      }).returning();
-  
-      res.status(201).json({ message: "Usuário criado com sucesso", user: result }); 
+    static readUser = async (req: Request, res: Response) => {
+      try {
+        
+        // extrair id e nome da requisição
+        const { id, name } = req.params; 
+
+        // converter id para número
+        const parsedId = parseInt(id, 10);
+        if (isNaN(parsedId)) {
+          return res.status(400).json({ error: "ID inválido" });
+        }
+        
+        // validar com o Zod
+        const parsedData = userSchema.safeParse({ id: parsedId, name });
+        if (!parsedData.success) {
+          return res.status(400).json({ error: "Dados Inválidos", details: parsedData.error.errors });
+        }
+         
+        const result = await db.select().from(usersTable)
+          .where(or(eq(usersTable.id, parsedId), eq(usersTable.name, name)))
+          .limit(1);
+        if (result.length === 0) { // array vazio
+          return res.status(404).json({ error: "Usuário não encontrado" });
+        }
+        
+        // desestruturar result e remover password
+        const { password, ...userWithoutPassword } = result[0];
+        
+        return res.status(200).json(userWithoutPassword);
   
-    } catch (error) {
-      console.error(error);
-      return res.status(500).json({ error: "Erro ao inserir o usuário. Por favor, tente novamente mais tarde." });
-    }
-  };
+      } catch (error) {
+        console.error(error);
+        return res.status(500).json({ error: "Erro ao buscar o usuário." });
+      }
+    }  
+    
+    static loginUser = async (req: Request, res: Response) => {
+      try {
 
-  export const readUser = async (req: Request, res: Response) => {
-    try {
-      
-      const { id, nome } = req.params; //extracao da requisicao
+        const { email, password } = req.body;
 
-      const parsedData = userSchema.safeParse({ id, nome });
-  
-      if (!parsedData.success) {
-        return res.status(400).json({ error: "Dados Inválidos", details: parsedData.error.errors });}
-       
-      const result = await db.select().from(usersTable).where(or(eq(usersTable.id, id), eq(usersTable.nome, nome))).limit(1);
+        // buscar usuário
+        const user = await db.select().from(usersTable).where(eq(usersTable.email, email)).limit(1);
+        if (user.length === 0) { // array vazio
+          return res.status(404).json({ message: 'Usuário não encontrado' });
+        }
 
-      if (result.length === 0) { //array vazio
-        return res.status(404).json({ error: "Usuário não encontrado" });
-      }
+        // validar senha
+        const isPasswordValid = await bcrypt.compare(password, user[0].password); 
+        if (!isPasswordValid) {
+         return res.status(401).json({ message: 'Senha incorreta' });
+        }
 
-      delete result[0].password;
+          // criar chave secreta para assinar e validar tokens, guardar no .env
+        const jwtSecret = process.env["JWT_SECRET"];
+        if (!jwtSecret) {
+          return res.status(500).json({ error: "Chave secreta do JWT não configurada" });
+        }
 
-      return res.status(200).json(result[0]);
-    } catch (error) {
-      console.error(error);
-      return res.status(500).json({ error: "Erro ao buscar o usuário. Por favor, tente novamente mais tarde." });
+        //gerar token 
+        const token = jwt.sign({ id: user[0].id, email: user[0].email }, jwtSecret, { expiresIn: '1h' });
+        
+        return res.status(200).json({ message: 'Login bem-sucedido', token });
+
+      } catch (error) {
+        console.error(error);
+        return res.status(500).json({ error: "Erro ao realizar login" });
+      }
     }
-  }    
\ No newline at end of file
+}
diff --git a/src/index.ts b/src/index.ts
index b062d03..120d5ed 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,6 @@
 import express, { urlencoded, json } from 'express'
 import dotenv from 'dotenv'
-import { createUser, readUser } from './handlers/user';
+import User from './handlers/user';
 import Implant from '@/handlers/implants'
 import { Auth } from '@/middleware/auth'
 
@@ -14,8 +14,9 @@ app.use(urlencoded({ extended: true }))
 app.use(json())
 
 // Rotas de usuário
-app.post('/addUser', createUser)
-app.get('/getUser', readUser)
+app.post('/addUser', User.createUser)
+app.get('/getUser', User.readUser)
+app.post('/loginUser', User.loginUser)
 
 // Definir a porta e iniciar o servidor
 const PORT = process.env['PORT'] || 3000
diff --git a/src/validators/userValidator.ts b/src/validators/userValidator.ts
index 79def35..52e52c9 100644
--- a/src/validators/userValidator.ts
+++ b/src/validators/userValidator.ts
@@ -1,15 +1,14 @@
 import { z } from 'zod'
 
 export const userSchema = z.object({
-        name: z.string().min(3, { message: "O nome deve ter pelo menos 3 caracteres" }),
-        password: z.string().min(8, { message: "A senha deve ter pelo menos 8 caracteres" }),
-        email: z.string().email({ message: "Formato de e-mail inválido" }),
-        birthday: z.string().refine(date => !isNaN(Date.parse(date)), 
-          { message: "Data de nascimento inválida" }),
-        cpf: z.string().min(11, { message: "O CPF deve ter pelo menos 11 números" })
-          .regex(/^\d{3}\.\d{3}\.\d{3}-\d{2}$/, "CPF inválido (use o formato 000.000.000-00)"),
-        money: z.number().nonnegative(),
-        cyberpsychosis: z.number(), //a definir restrições de validação
-        cyberLimit: z.number(),
-      });
-
+  name: z.string().min(3, { message: "O nome deve ter pelo menos 3 caracteres" }),
+  password: z.string().min(8, { message: "A senha deve ter pelo menos 8 caracteres" }),
+  email: z.string().email({ message: "Formato de e-mail inválido" }),
+  birthday: z.string().refine(date => !isNaN(Date.parse(date)), 
+    { message: "Data de nascimento inválida" }),
+  cpf: z.string().min(11, { message: "O CPF deve ter pelo menos 11 números" })
+    .regex(/^\d{3}\.\d{3}\.\d{3}-\d{2}$/, "CPF inválido (use o formato 000.000.000-00)"),
+  money: z.number().nonnegative(),
+  cyberpsychosis: z.number(), // a definir restrições de validação
+  cyberLimit: z.number(),
+});
\ No newline at end of file
-- 
GitLab