feat: agregar profile y aplanar submódulo

This commit is contained in:
Sofía Maturana 2026-04-25 10:25:02 -04:00
parent 8906d7a970
commit d6c48759db
22 changed files with 1664 additions and 5 deletions

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "backend"]
path = backend
url = ssh://git@git.silvertke.net:2222/SilverTke/backend-pizza-dela.git

@ -1 +0,0 @@
Subproject commit d86ee9ce553a0421211a48bacce8a2ea49b69c5d

1
backend/.env Normal file
View file

@ -0,0 +1 @@
JWT_SECRET=increiblementeSecreto

1
backend/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

94
backend/README.md Normal file
View file

@ -0,0 +1,94 @@
# Simple API JWT
API para consumir un servicio de Auth con JWT.
## Instalación
```sh
npm install
```
## Uso
```sh
npm run dev
```
## Endpoints
### Pizzas
```sh
GET /api/pizzas
```
### Pizza (única)
```sh
GET /api/pizzas/:id
```
### Auth
```sh
POST /api/auth/login
POST /api/auth/register
```
body:
```json
{
"email": "test@example.com",
"password": "123123"
}
```
### Checkout & Profile
Esta ruta requiere un token JWT en el header, el token se obtiene en el endpoint de Auth explicado en el item siguiente (JWT).
Además puedes enviar un carrito con los productos a comprar, esto es solo una simulación, no se guarda en la base de datos.
```sh
POST /api/checkouts
```
body:
```json
{
"cart": [...]
}
```
Endpoint para obtener el perfil del usuario autenticado. Necesitas enviar el token JWT en el header.
```sh
GET /api/auth/me
```
## JWT
Para obtener el token JWT, se debe hacer una petición a `/api/auth/login` o a `/api/auth/register` con el body correspondiente.
El token JWT se debe enviar en el header `Authorization` de la siguiente manera:
```sh
Authorization Bearer token_jwt
```
Ejemplo con fetch:
```js
await fetch("http://localhost:5000/api/checkout", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer token_jwt`,
},
body: JSON.stringify({
cart: carrito,
}),
});
```

View file

@ -0,0 +1,95 @@
import "dotenv/config";
import jwt from "jsonwebtoken";
import { nanoid } from "nanoid";
import { authModel } from "../models/auth.model.js";
import { isValidEmail } from "../utils/validators/email.validate.js";
const login = async (req, res) => {
try {
const { email = "", password = "" } = req.body;
if (!email.trim() || !password.trim()) {
return res.status(400).json({ error: "Email and password are required" });
}
if (!isValidEmail(email)) {
return res.status(400).json({ error: "Invalid email" });
}
if (password.length < 6) {
return res
.status(400)
.json({ error: "Password must be at least 6 characters" });
}
const user = await authModel.getUserByEmail(email);
if (!user) {
return res.status(400).json({ error: "User not found" });
}
if (user.password !== password) {
return res.status(400).json({ error: "Invalid password" });
}
const payload = { email, id: user.id };
const token = jwt.sign(payload, process.env.JWT_SECRET);
return res.json({ email, token });
} catch (error) {
// console.log(error);
return res.status(500).json({ error: "Server error" });
}
};
const register = async (req, res) => {
try {
const { email = "", password = "" } = req.body;
if (!email.trim() || !password.trim()) {
return res.status(400).json({ error: "Email and password are required" });
}
if (!isValidEmail(email)) {
return res.status(400).json({ error: "Invalid email" });
}
if (password.length < 6) {
return res
.status(400)
.json({ error: "Password must be at least 6 characters" });
}
const user = await authModel.getUserByEmail(email);
if (user) {
return res.status(400).json({ error: "User already exists" });
}
const newUser = { email, password, id: nanoid() };
await authModel.addUser(newUser);
const payload = { email, id: newUser.id };
const token = jwt.sign(payload, process.env.JWT_SECRET);
return res.json({ email, token });
} catch (error) {
// console.log(error);
return res.status(500).json({ error: "Server error" });
}
};
const me = async (req, res) => {
try {
const { email } = req.user;
const user = await authModel.getUserByEmail(email);
return res.json({ email, id: user.id });
} catch (error) {
// console.log(error);
return res.status(500).json({ error: "Server error" });
}
};
export const authController = {
login,
register,
me,
};

View file

@ -0,0 +1,16 @@
const create = async (req, res) => {
try {
return res.json({
message: "Checkout successful",
cart: req.body,
user: req.user,
});
} catch (error) {
// console.log(error);
return res.status(500).json({ error: "Server error" });
}
};
export const checkoutController = {
create,
};

View file

@ -0,0 +1,20 @@
import { pizzaModel } from "../models/pizza.model.js";
const readPizzas = async (req, res) => {
const pizzas = await pizzaModel.getPizzas();
res.json(pizzas);
};
const readPizza = async (req, res) => {
const { id } = req.params;
const pizza = await pizzaModel.getPizza(id.toLowerCase());
if (!pizza) {
return res.status(404).json({ message: "Pizza not found" });
}
res.json(pizza);
};
export const pizzaController = {
readPizzas,
readPizza,
};

50
backend/db/pizzas.json Normal file
View file

@ -0,0 +1,50 @@
[
{
"desc": "La pizza napolitana, de masa tierna y delgada pero bordes altos, es la versión propia de la cocina napolitana de la pizza redonda. El término pizza napoletana, por su importancia histórica o regional, se emplea en algunas zonas como sinónimo de pizza tonda.",
"id": "p001",
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-1239077_640_cl.jpg?alt=media&token=6a9a33da-5c00-49d4-9080-784dcc87ec2c",
"ingredients": ["mozzarella", "tomates", "jamón", "orégano"],
"name": "napolitana",
"price": 5950
},
{
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
"id": "p002",
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fcheese-164872_640_com.jpg?alt=media&token=18b2b821-4d0d-43f2-a1c6-8c57bc388fab",
"ingredients": ["mozzarella", "tomates", "jamón", "choricillo"],
"name": "española",
"price": 7250
},
{
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
"id": "p003",
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-1239077_640_com.jpg?alt=media&token=e7cde87a-08d5-4040-ac54-90f6c31eb3e3",
"ingredients": ["mozzarella", "tomates", "salame", "orégano"],
"name": "salame",
"price": 5990
},
{
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
"id": "p004",
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-2000595_640_c.jpg?alt=media&token=61325b6e-a1e0-441e-b3b5-7335ba13e8be",
"ingredients": ["mozzarella", "salame", "aceitunas", "champiñones"],
"name": "cuatro estaciones",
"price": 9590
},
{
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
"id": "p005",
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-salame.jpg?alt=media&token=ab3d4bf8-01f2-4810-982b-bd7fb6b517b2",
"ingredients": ["mozzarella", "tomates cherry", "bacon", "orégano"],
"name": "bacon",
"price": 6450
},
{
"desc": "La pizza es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que comúnmente se cubre con salsa de tomate, queso y otros muchos ingredientes, y que se hornea a alta temperatura, tradicionalmente en un horno de leña.",
"id": "p006",
"img": "https://firebasestorage.googleapis.com/v0/b/apis-varias-mias.appspot.com/o/pizzeria%2Fpizza-2000595_640_c.jpg?alt=media&token=61325b6e-a1e0-441e-b3b5-7335ba13e8be",
"ingredients": ["mozzarella", "pimientos", "pollo grillé", "orégano"],
"name": "pollo picante",
"price": 8500
}
]

7
backend/db/users.json Normal file
View file

@ -0,0 +1,7 @@
[
{
"email": "test@test.com",
"password": "123123",
"id": "UYz_2Vy9rNw7uELQ7AZ8D"
}
]

24
backend/index.js Normal file
View file

@ -0,0 +1,24 @@
import cors from "cors";
import "dotenv/config";
import express from "express";
import authRoute from "./routes/auth.route.js";
import checkoutRoute from "./routes/checkout.route.js";
import pizzaRoute from "./routes/pizza.route.js";
const app = express();
app.use(express.json());
app.use(cors());
app.use("/api/auth", authRoute);
app.use("/api/pizzas", pizzaRoute);
app.use("/api/checkouts", checkoutRoute);
app.use((_, res) => {
res.status(404).json({ error: "Not Found" });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server is running on port http://localhost:${PORT}`);
});

View file

@ -0,0 +1,18 @@
import "dotenv/config";
import jwt from "jsonwebtoken";
export const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
next();
} catch (error) {
// console.log(error);
return res.status(401).send({ error: "Invalid token" });
}
};

View file

@ -0,0 +1,19 @@
import { readFile, writeFile } from "node:fs/promises";
const getUserByEmail = async (email) => {
const data = await readFile("db/users.json", "utf-8");
const users = JSON.parse(data);
return users.find((user) => user.email === email);
};
const addUser = async (newUser) => {
const data = await readFile("db/users.json", "utf-8");
const users = JSON.parse(data);
users.push(newUser);
await writeFile("db/users.json", JSON.stringify(users, null, 2));
};
export const authModel = {
getUserByEmail,
addUser,
};

View file

@ -0,0 +1,16 @@
import { readFile } from "node:fs/promises";
const getPizzas = async () => {
const data = await readFile("db/pizzas.json", "utf-8");
return JSON.parse(data);
};
const getPizza = async (id) => {
const pizzas = await getPizzas();
return pizzas.find((pizza) => pizza.id === id);
};
export const pizzaModel = {
getPizzas,
getPizza,
};

1227
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
backend/package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "simple-api-jwt",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [],
"author": "bluuweb",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"nanoid": "^5.0.6"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
}

View file

@ -0,0 +1,11 @@
import { Router } from "express";
import { authController } from "../controllers/auth.controller.js";
import { authMiddleware } from "../middlewares/auth.middleware.js";
const router = Router();
router.post("/login", authController.login);
router.post("/register", authController.register);
router.get("/me", authMiddleware, authController.me);
export default router;

View file

@ -0,0 +1,10 @@
import { Router } from "express";
import { checkoutController } from "../controllers/checkout.controller.js";
import { authMiddleware } from "../middlewares/auth.middleware.js";
const router = Router();
router.use(authMiddleware);
router.post("/", checkoutController.create);
export default router;

View file

@ -0,0 +1,9 @@
import { Router } from "express";
import { pizzaController } from "../controllers/pizza.controller.js";
const router = Router();
router.get("/", pizzaController.readPizzas);
router.get("/:id", pizzaController.readPizza);
export default router;

View file

@ -0,0 +1,5 @@
export const isValidEmail = (email) => {
const regexEmail =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regexEmail.test(email);
};

View file

@ -13,7 +13,7 @@ const Navbar = () => {
</Link>
{token ? (
<>
<button>Profile</button>
<Link to="/profile">Profile</Link>
<button>Logout</button>
</>
) : (

16
src/pages/Profile.jsx Normal file
View file

@ -0,0 +1,16 @@
const Profile = () => {
const email = "test@example.com";
return (
<div>
<p>
<strong>Mail: </strong>
{email}
</p>
<button className="bg-green-700 hover:bg-green-300 text-white hover:text-black">
Cerrar sesión
</button>
</div>
);
};
export default Profile;