diff --git a/README.md b/README.md index e69de29..4abca9b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,519 @@ +# 🌊 Código Abisal - API REST + +
+ +![Ocean Depths](https://img.shields.io/badge/Ocean-Depths-0077be?style=for-the-badge&logo=oceanprotocol&logoColor=white) +![Node.js](https://img.shields.io/badge/Node.js-20+-339933?style=for-the-badge&logo=node.js&logoColor=white) +![TypeScript](https://img.shields.io/badge/TypeScript-5.9-3178C6?style=for-the-badge&logo=typescript&logoColor=white) +![Express](https://img.shields.io/badge/Express-5.1-000000?style=for-the-badge&logo=express&logoColor=white) +![MySQL](https://img.shields.io/badge/MySQL-8.0-4479A1?style=for-the-badge&logo=mysql&logoColor=white) + +**API REST profesional para la gestión de artículos sobre las profundidades oceánicas** + +[Características](#-características) • +[Instalación](#-instalación) • +[Configuración](#️-configuración) • +[API](#-endpoints-principales) • +[Despliegue](#-despliegue) + +
+ +--- + +## 📋 Descripción + +**Código Abisal** es una API REST robusta y escalable diseñada para gestionar artículos científicos y divulgativos sobre las profundidades del océano. El proyecto permite crear, editar, eliminar y consultar contenido sobre fauna abisal, ecosistemas marinos, exploraciones oceánicas y conservación. + +![Diagrama de la Base de Datos](./docs/image/drawSQL-image-export-2025-09-24_2.png) + +### 🎯 Objetivo del Proyecto + +Proporcionar una plataforma backend completa para compartir conocimiento sobre el mundo abisal, con un sistema de autenticación seguro, gestión de usuarios y un sistema de "likes" para artículos populares. + +--- + +## ✨ Características + +### 🔐 Autenticación y Autorización +- ✅ Registro de usuarios con hash de contraseñas (bcrypt) +- ✅ Login con JWT (JSON Web Tokens) +- ✅ Middleware de autenticación y roles (user/admin) +- ✅ Sistema de recuperación de contraseña con tokens temporales + +### 📰 Gestión de Artículos +- ✅ CRUD completo de artículos (Create, Read, Update, Delete) +- ✅ Categorías especializadas: Fauna Abisal, Ecosistemas, Exploración, Conservación +- ✅ Sistema de likes por usuario +- ✅ Asignación automática de creador (creator_id) +- ✅ Validaciones robustas con express-validator + +### 👥 Gestión de Usuarios +- ✅ Panel de administración de usuarios (solo admins) +- ✅ Actualización de perfiles +- ✅ Protección contra auto-eliminación +- ✅ Consulta pública de perfiles de usuario + +### 🛡️ Seguridad +- ✅ Conexión TLS/SSL a base de datos (compatible con TiDB Cloud) +- ✅ Variables de entorno separadas para desarrollo/producción/test +- ✅ Validación de entrada exhaustiva +- ✅ Protección contra ataques comunes (SQL injection, XSS) + +### 🧪 Testing +- ✅ Suite de tests con Jest y Supertest +- ✅ Tests unitarios e integración +- ✅ Base de datos de prueba aislada +- ✅ Cobertura completa de endpoints críticos + +--- + +## 🚀 Tecnologías + +| Categoría | Tecnologías | +|-----------|------------| +| **Runtime** | Node.js 20+ | +| **Lenguaje** | TypeScript 5.9 | +| **Framework** | Express 5.1 | +| **Base de Datos** | MySQL 8.0 / TiDB Cloud | +| **ORM** | Sequelize 6.37 | +| **Autenticación** | JWT, bcryptjs | +| **Validación** | express-validator | +| **Testing** | Jest, Supertest, ts-jest | +| **DevOps** | Docker, GitHub Actions | + +--- + +## 📦 Instalación + +### Prerrequisitos + +```bash +Node.js >= 20.0.0 +npm >= 10.0.0 +MySQL >= 8.0 o TiDB Cloud +``` + +### Pasos de Instalación + +1️⃣ **Clonar el repositorio** + +```bash +git clone https://github.com/Codigo-Inmersion/codigo-abisal-server.git +cd codigo-abisal-server +``` + +2️⃣ **Instalar dependencias** + +```bash +npm install +``` + +3️⃣ **Configurar variables de entorno** + +```bash +cp .env.example .env +``` + +Edita el archivo `.env` con tus credenciales (ver sección [Configuración](#️-configuración)) + +4️⃣ **Ejecutar migraciones (automático al iniciar)** + +```bash +npm run dev +``` + +5️⃣ **Ejecutar tests (opcional)** + +```bash +npm test +``` + +--- + +## ⚙️ Configuración + +### Variables de Entorno + +Crea un archivo `.env` en la raíz del proyecto con las siguientes variables: + +```env +# Base de Datos +DB_NAME=tu_base_de_datos +DB_USER=tu_usuario +DB_PASS=tu_contraseña +DB_HOST=tu_host +DB_PORT=3306 +DB_DIALECT=mysql + +# TLS/SSL (opcional, para TiDB Cloud) +DB_SSL=true +DB_SSL_CA_PATH=./certs/tidb-ca.pem + +# Servidor +PORT=8000 +CORS_ORIGIN=http://localhost:5173 + +# JWT +JWT_SECRET=tu_clave_secreta_muy_segura +JWT_EXPIRES=7d + +# Frontend (para recuperación de contraseña) +FRONTEND_URL=http://localhost:5173 +``` + +### Base de Datos de Pruebas + +Crea un archivo `.env.test` para tests: + +```env +NODE_ENV=test +DB_NAME=abisal_app_test +DB_USER=tu_usuario +DB_PASS=tu_contraseña +DB_HOST=localhost +DB_PORT=3306 +DB_DIALECT=mysql +DB_SSL=false + +PORT=8001 +CORS_ORIGIN=* +JWT_SECRET=test_secret_key +JWT_EXPIRES=1h +``` + +--- + +## 🔌 Endpoints Principales + +### Base URL +``` +http://localhost:8000 +``` + +### 🔐 Autenticación + +| Método | Endpoint | Descripción | Auth | +|--------|----------|-------------|------| +| `POST` | `/auth/register` | Registrar nuevo usuario | ❌ | +| `POST` | `/auth/login` | Iniciar sesión | ❌ | +| `POST` | `/auth/forgot-password` | Solicitar recuperación | ❌ | +| `POST` | `/auth/reset-password` | Restablecer contraseña | ❌ | + +#### Ejemplo: Registro + +```bash +POST /auth/register +Content-Type: application/json + +{ + "username": "oceanexplorer", + "name": "María", + "last_name": "González", + "email": "maria@example.com", + "password": "SecurePass123!" +} +``` + +**Respuesta:** +```json +{ + "message": "Usuario registrado exitosamente", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +--- + +### 📰 Artículos + +| Método | Endpoint | Descripción | Auth | +|--------|----------|-------------|------| +| `GET` | `/article` | Listar todos los artículos | ❌ | +| `GET` | `/article/:id` | Obtener artículo específico | ❌ | +| `POST` | `/article` | Crear artículo | ✅ User/Admin | +| `PUT` | `/article/:id` | Actualizar artículo | ✅User /Admin | +| `DELETE` | `/article/:id` | Eliminar artículo | ✅ Admin | +| `POST` | `/article/:id/like` | Dar like | ✅ User/Admin | +| `DELETE` | `/article/:id/like` | Quitar like | ✅ User/Admin | + +#### Ejemplo: Crear Artículo + +```bash +POST /article +Authorization: Bearer {token} +Content-Type: application/json + +{ + "title": "El Pez Abisal Más Profundo del Mundo", + "description": "Descubrimiento de una nueva especie a 8,178 metros", + "content": "En las profundidades de la Fosa de las Marianas...", + "category": "Fauna Abisal", + "species": "Pseudoliparis swirei", + "image": "https://example.com/pez-abisal.jpg", + "references": "Nature Magazine 2023" +} +``` + +**Respuesta:** +```json +{ + "id": 42, + "title": "El Pez Abisal Más Profundo del Mundo", + "description": "Descubrimiento de una nueva especie a 8,178 metros", + "content": "En las profundidades de la Fosa de las Marianas...", + "category": "Fauna Abisal", + "species": "Pseudoliparis swirei", + "image": "https://example.com/pez-abisal.jpg", + "references": "Nature Magazine 2023", + "creator_id": 1, + "likes": 0, + "created_at": "2025-01-15T10:30:00.000Z", + "updated_at": "2025-01-15T10:30:00.000Z" +} +``` + +--- + +### 👥 Usuarios + +| Método | Endpoint | Descripción | Auth | +|--------|----------|-------------|------| +| `GET` | `/users` | Listar todos los usuarios | ✅ Admin | +| `GET` | `/users/user/:id` | Perfil público de usuario | ❌ | +| `PUT` | `/users/:id` | Actualizar usuario | ✅ Admin | +| `DELETE` | `/users/:id` | Eliminar usuario | ✅ Admin | + +--- + +## 🧪 Testing +![Ejecución de Tests](./docs/image/Captura%20de%20pantalla%202025-10-16%20113134.png) + +### Ejecutar Tests + +```bash +# Todos los tests +npm test + +# Tests en modo watch +npm test -- --watch + +# Tests con cobertura +npm test -- --coverage +``` + +### Estructura de Tests + +``` +test/ +├── auth.test.ts # Tests de autenticación +├── article.test.ts # Tests de artículos +└── jest.setup.ts # Configuración de Jest +``` + +### Cobertura Actual + +- ✅ Registro y login de usuarios +- ✅ CRUD completo de artículos +- ✅ Validaciones de entrada +- ✅ Autorización y roles + +--- + +## 🐳 Docker + +### Construcción de Imagen + +```bash +docker build -t codigo-abisal-api . +``` + +### Ejecución con Docker Compose + +```bash +docker-compose up -d +``` + +El archivo `docker-compose.yml` ya está configurado para usar la imagen de Docker Hub: + +```yaml +services: + api: + image: gema284/codigo-abisal-server-api:dev + container_name: abisal-api + platform: linux/amd64 + env_file: .env.docker + ports: + - "8000:8000" +``` + +--- + +## 🚀 Despliegue + +### Render (Recomendado) + +1️⃣ **Conecta tu repositorio de GitHub** + +2️⃣ **Configura las variables de entorno** en el dashboard de Render + +3️⃣ **Build Command:** +```bash +npm install && npm run build +``` + +4️⃣ **Start Command:** +```bash +npm start +``` + +### Railway / Heroku + +Similar a Render, asegúrate de: +- Configurar todas las variables de entorno +- Establecer `NODE_ENV=production` +- Configurar `DB_SSL=true` si usas TiDB Cloud + +--- + +## 📁 Estructura del Proyecto + +``` +codigo-abisal-server/ +├── src/ +│ ├── controllers/ # Lógica de negocio +│ │ ├── ArticleController.ts +│ │ ├── AuthController.ts +│ │ ├── UserController.ts +│ │ └── PasswordResetController.ts +│ ├── middlewares/ # Middlewares personalizados +│ │ ├── authMiddlewares.ts +│ │ └── handleValidation.ts +│ ├── models/ # Modelos de Sequelize +│ │ ├── ArticleModel.ts +│ │ ├── UserModel.ts +│ │ └── PasswordResetToken.ts +│ ├── routes/ # Definición de rutas +│ │ ├── articleRoutes.ts +│ │ ├── authRoutes.ts +│ │ ├── userRoutes.ts +│ │ └── passwordReset.routes.ts +│ ├── validators/ # Validaciones con express-validator +│ │ ├── articleValidators.ts +│ │ ├── userValidators.ts +│ │ └── passwordResetValidators.ts +│ ├── utils/ # Utilidades +│ │ ├── jwt.ts +│ │ └── resetToken.ts +│ ├── database/ # Configuración DB +│ │ └── db_connection.ts +│ ├── interface/ # Interfaces TypeScript +│ │ ├── articleInterface.ts +│ │ └── userInterface.ts +│ └── app.ts # Punto de entrada +├── test/ # Tests +│ ├── auth.test.ts +│ ├── article.test.ts +│ └── jest.setup.ts +├── certs/ # Certificados TLS +│ └── tidb-ca.pem +├── .github/workflows/ # CI/CD +│ └── docker-publish.yml +├── Dockerfile +├── docker-compose.yml +├── tsconfig.json +├── jest.config.mjs +├── package.json +└── README.md +``` + +--- + +## 🔒 Seguridad + +### Buenas Prácticas Implementadas + +✅ **Hashing de contraseñas** con bcrypt (10 rounds) +✅ **Tokens JWT** con expiración configurable +✅ **Validación exhaustiva** de entrada con express-validator +✅ **Conexión TLS/SSL** a base de datos en producción +✅ **CORS configurado** para dominios permitidos +✅ **Variables de entorno** nunca comiteadas +✅ **Middleware de autorización** por roles +✅ **Normalización de emails** (lowercase, trim) +✅ **Protección contra auto-eliminación** de usuarios admin + +### Recomendaciones Adicionales + +- 🔄 Rotar `JWT_SECRET` periódicamente +- 🚫 No exponer `PORT` públicamente sin proxy reverso +- 📝 Implementar rate limiting (ej: express-rate-limit) +- 📊 Monitorear logs en producción +- 🛡️ Usar Helmet.js para headers de seguridad + +--- + +## 📝 Scripts Disponibles + +```bash +# Desarrollo con recarga automática +npm run dev + +# Compilar TypeScript a JavaScript +npm run build + +# Ejecutar en producción +npm start + +# Ejecutar tests +npm test +``` + +--- + +## 🤝 Contribuciones + +Las contribuciones son bienvenidas. Por favor: + +1. Fork el proyecto +2. Crea una rama para tu feature (`git checkout -b feature/AmazingFeature`) +3. Commit tus cambios (`git commit -m 'Add: AmazingFeature'`) +4. Push a la rama (`git push origin feature/AmazingFeature`) +5. Abre un Pull Request + +--- + +## 👩🏽‍🏫👩🏻‍🏫👩🏼‍🏫👩🏾‍🏫👩🏽‍🏫 Equipo de Desarrollo + +| Desarrolladora | GitHub | LinkedIn | +|----------------|--------|----------| +| **Camila Arenas** | [GitHub](https://github.com/mcarenashd) | [LinkedIn](https://www.linkedin.com/in/mcarenash) | +| **Gema Yébenes** | [GitHub](https://github.com/gemayc) | [LinkedIn](https://www.linkedin.com/in/gema-yebenes-83b6a6100/) | +| **Mariana Moreno** | [GitHub](https://github.com/MarianaMH1195) | [LinkedIn](https://www.linkedin.com/in/mariana-moreno-henao-70305a16b/) | +| **Olga Ramírez** | [GitHub](https://github.com/olgararo) | [LinkedIn](https://www.linkedin.com/in/olga-ramirez-rodriguez/) | +| **Rocio Coronel** | [GitHub](https://github.com/Rocio-Coronel) | [LinkedIn](https://www.linkedi) | + + +## 📄 Licencia + +Este proyecto está bajo la Licencia ISC. Ver el archivo `LICENSE` para más detalles. + +--- + +## 🙏 Agradecimientos + +- Comunidad de TypeScript y Node.js +- Documentación de Sequelize +- Express.js Team +- Render y TiDB Cloud por sus servicios + +--- + +
+ +**[⬆ Volver arriba](#-código-abisal---api-rest)** + +Hecho con 💙 para explorar las profundidades del océano + +[![GitHub](https://img.shields.io/badge/GitHub-Codigo--Inmersion-181717?style=flat-square&logo=github)](https://github.com/Codigo-Inmersion) + +
\ No newline at end of file diff --git a/certs/tidb-ca.pem b/certs/tidb-ca.pem new file mode 100644 index 0000000..b85c803 --- /dev/null +++ b/certs/tidb-ca.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/docs/image/Captura de pantalla 2025-10-16 113134.png b/docs/image/Captura de pantalla 2025-10-16 113134.png new file mode 100644 index 0000000..884e88f Binary files /dev/null and b/docs/image/Captura de pantalla 2025-10-16 113134.png differ diff --git a/docs/image/drawSQL-image-export-2025-09-24_2.png b/docs/image/drawSQL-image-export-2025-09-24_2.png new file mode 100644 index 0000000..2f6d504 Binary files /dev/null and b/docs/image/drawSQL-image-export-2025-09-24_2.png differ diff --git a/src/app.ts b/src/app.ts index dab12bb..bdac2bb 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,52 +1,148 @@ +// import express from "express"; +// import db_connection from "./database/db_connection.js"; +// import "dotenv/config"; +// import "./models/UserModel.js"; +// import "./models/ArticleModel.js"; +// import authRouter from "./routes/authRoutes.js"; +// import articleRouter from "./routes/articleRoutes.js"; +// import { User } from "./models/UserModel.js"; +// import { Article } from "./models/ArticleModel.js"; +// import passwordResetRouter from "./routes/passwordReset.routes.js"; +// import "./models/PasswordResetToken.js"; +// import cors from "cors"; +// import userRouter from "./routes/userRoutes.js"; // 👈 el nuevo archivo + + +// User.hasMany(Article, { foreignKey: 'creator_id' }); +// Article.belongsTo(User, { foreignKey: 'creator_id' }); + +// export const app = express(); +// const PORT = process.env.PORT ? Number(process.env.PORT) : 8000; +// app.use(cors({ origin: process.env.CORS_ORIGIN || "*" })); // puerto de Vite +// app.use(express.json()); +// app.get("/", (_req, res) => { +// res.send("Hola API"); +// }); +// app.get("/healthz", (_req, res) => { +// res.status(200).send("ok"); +// }); +// app.use("/auth", authRouter ) +// app.use("/article", articleRouter) +// app.use("/users", userRouter); +// app.use("/auth", passwordResetRouter); + +// // await db_connection.sync({ alter: true }); // o { force: true } si quieres regenerar + +// async function startServer() { +// try { +// // Sincroniza los modelos con la base de datos +// await db_connection.sync(); +// console.log("✅ Database synchronized successfully."); + +// app.listen(PORT, () => { +// console.log(`🚀 Server is running on port ${PORT}`); +// }); +// } catch (error) { +// console.error("❌ Unable to sync database:", error); +// } +// } + +// if (process.env.NODE_ENV !== 'test') { +// startServer(); +// } + import express from "express"; +import cors from "cors"; +import dotenv from "dotenv"; + +// ✅ 1) Cargar .env SOLO en desarrollo (en Render ya usas el panel de variables) +if (process.env.NODE_ENV !== "production") { + dotenv.config(); +} + import db_connection from "./database/db_connection.js"; -import "dotenv/config"; -import "./models/UserModel.js"; +import "./models/UserModel.js"; import "./models/ArticleModel.js"; -import authRouter from "./routes/authRoutes.js"; -import articleRouter from "./routes/articleRoutes.js"; import { User } from "./models/UserModel.js"; import { Article } from "./models/ArticleModel.js"; -import passwordResetRouter from "./routes/passwordReset.routes.js"; import "./models/PasswordResetToken.js"; -import cors from "cors"; -import userRouter from "./routes/userRoutes.js"; // 👈 el nuevo archivo +import authRouter from "./routes/authRoutes.js"; +import articleRouter from "./routes/articleRoutes.js"; +import userRouter from "./routes/userRoutes.js"; +import passwordResetRouter from "./routes/passwordReset.routes.js"; +// Relaciones +User.hasMany(Article, { foreignKey: "creator_id" }); +Article.belongsTo(User, { foreignKey: "creator_id" }); -User.hasMany(Article, { foreignKey: 'creator_id' }); -Article.belongsTo(User, { foreignKey: 'creator_id' }); +export const app = express(); - export const app = express(); +// ✅ 2) PORT: usa el dinámico de Render si existe; 8000 en local const PORT = process.env.PORT ? Number(process.env.PORT) : 8000; - app.use(cors({ origin: process.env.CORS_ORIGIN || "*" })); // puerto de Vite - app.use(express.json()); - app.get("/", (_req, res) => { + +// ✅ 3) CORS con **lista** de orígenes (soporta uno o varios desde la env) +// Ejemplos de CORS_ORIGIN: +// - Solo prod: "https://codigo-abisal-client.vercel.app" +// - Prod + local: "https://codigo-abisal-client.vercel.app,http://localhost:5174" +const rawOrigins = process.env.CORS_ORIGIN || ""; +const whitelist = rawOrigins + .split(",") + .map(s => s.trim()) + .filter(Boolean); + +// ⚠️ IMPORTANTE: deja SOLO este middleware de CORS (no dupliques otro en otro archivo) +app.use( + cors({ + origin: (origin, callback) => { + // Peticiones sin "Origin" (curl/healthz) → permite + if (!origin) return callback(null, true); + + // Si no configuraste nada, permite todo (cámbialo a false si quieres bloquear por defecto) + if (whitelist.length === 0) return callback(null, true); + + // Permite si el origin está en la lista + if (whitelist.includes(origin)) return callback(null, true); + + // Origen no permitido + return callback(new Error("Not allowed by CORS")); + }, + credentials: true, // déjalo true solo si usas cookies/autenticación con credenciales + }) +); + +// Body parser +app.use(express.json()); + +// Rutas +app.get("/", (_req, res) => { res.send("Hola API"); }); -app.get("/healthz", (_req, res) => { - res.status(200).send("ok"); -}); -app.use("/auth", authRouter ) -app.use("/article", articleRouter) + +// Healthcheck para Render +app.get("/healthz", (_req, res) => res.status(200).send("ok")); + +app.use("/auth", authRouter); +app.use("/article", articleRouter); app.use("/users", userRouter); app.use("/auth", passwordResetRouter); -// await db_connection.sync({ alter: true }); // o { force: true } si quieres regenerar - +// Arranque async function startServer() { try { - // Sincroniza los modelos con la base de datos - await db_connection.sync(); - console.log("✅ Database synchronized successfully."); + await db_connection.sync(); + console.log("✅ Database synchronized successfully."); - app.listen(PORT, () => { + // ✅ 4) Escucha en 0.0.0.0 (necesario en Render) y en el PORT correcto + app.listen(PORT, "0.0.0.0", () => { console.log(`🚀 Server is running on port ${PORT}`); + console.log("CORS_ORIGIN =", process.env.CORS_ORIGIN || "(no configurado)"); }); } catch (error) { console.error("❌ Unable to sync database:", error); + process.exit(1); } } -if (process.env.NODE_ENV !== 'test') { +if (process.env.NODE_ENV !== "test") { startServer(); } diff --git a/test/.gitkeep b/test/.gitkeep deleted file mode 100644 index e69de29..0000000