Este proyecto es una API RESTful desarrollada con Flask. Utiliza autenticación JWT, autorización basada en roles (RBAC) y operaciones CRUD con eliminación lógica.
- Flask==3.0.0
- Flask-SQLAlchemy==3.1.1
- Flask-JWT-Extended==4.4.4
- Flask-CORS==4.0.0
- Flask-Migrate==4.0.5
- Marshmallow==3.21.1
- passlib[bcrypt]==1.7.4
- pymysql==1.1.0
Se implementó una arquitectura de tres capas (Vistas, Servicios y Repositorios) para garantizar la separación de responsabilidades y la modularidad del código.
Nota sobre el diseño: Elegmos utilizar Blueprints en lugar de MethodView porque, si bien MethodView es ideal para estructurar operaciones CRUD de un recurso individual de forma orientada a objetos, Blueprint ofrece una estrategia de modularización superior para todo el proyecto. Permite segmentar el código por áreas funcionales (ej. autenticación, posts, usuarios) en archivos separados, manteniendo la aplicación principal ligera y garantizando que el proyecto sea escalable y fácil de mantener.
- Python 3.8+
- uv instalado. Si no lo tienes, instálalo globalmente con pip install uv.
- MySQL instalado y corriendo (o MariaDB).
- Crear la base de datos llamada
py_efi2_db.
# 1. Clona el repositorio
git clone https://github.com/Ulicrack/EFI2_python_2025.git
cd EFI2_python_2025
# 2. Crea el entorno virtual con uv (esto también lo activa automáticamente en muchas shells)
uv venv
# 3. Instala y sincroniza las dependencias desde requirements.txt
uv sync
# Si tu shell no activó automáticamente el entorno, hazlo manualmente:
source .venv/bin/activate # En Linux/macOS
# .\venv\Scripts\activate # En Windows/PowerShell
3. Configuración de la Base de Datos
Asegúrate de que las credenciales de MySQL en app.py o config_vars.py coincidan con tu entorno.
4. Ejecución
# Ejecuta el script principal
python app.py
La API estará disponible en http://127.0.0.1:5000/.
Utiliza estas credenciales para obtener tokens en el endpoint /login.
Rol | Email | Contraseña
Admin | lautaro@admin.com | lautaropass
Moderator | maria@mod.com | mariapass
User | sofia@user.com | sofiapass
(Las contraseñas hasheadas para estos usuarios deben ser inyectadas en la base de datos con un script SQL)
Módulo | Método | Ruta | Descripción | Acceso
Auth | POST | /register | Crea un nuevo usuario. | Público
Auth | POST | /login | Genera JWT de acceso. | Público
Posts | GET | /posts/active | Lista posts activos. | Público
Posts | POST | /posts/ | Crea un nuevo post. | Autenticado
Posts | PUT | /posts/<id> | Actualiza post. | Autor / Mod / Admin
Posts | DELETE | /posts/<id> | Elimina post (Soft Delete). | Autor / Mod / Admin
Comments | POST | /posts/<id>/comments/ | Crea un comentario en un post. | Autenticado
Comments | PUT | /comments/<id> | Actualiza comentario. | Autor / Mod / Admin
Users | GET | /users/ | Lista todos los usuarios. | Admin
Users | PUT | /users/<id> | Actualiza perfil (username/email). | Propietario / Admin
Users | PUT | /users/<id>/role | Cambia el rol de un usuario. | Admin
Stats | GET | /stats/ | Obtiene estadísticas del sistema. | Admin / Mod
db_setup.sql (Migraciones y Seeding)
Dado que usas Flask-Migrate, el enfoque ideal es:
Crear Tablas: Ejecuta la migración inicial:
# Inicializar migraciones
flask db init
# Crear la primera migración
flask db migrate -m "Initial models creation"
# Aplicar la migración a la DB
flask db upgrade
Datos de Prueba (Seeding): Aquí se usa SQL directo para insertar los usuarios y roles base. Necesitarás los hashes generados por hash_generator.py.
-- db_seeding.sql
- INSERTAR CATEGORÍAS DE PRUEBA
INSERT INTO categoria (id, name) VALUES (1, 'Tecnología');
INSERT INTO categoria (id, name) VALUES (2, 'Deportes');
INSERT INTO categoria (id, name) VALUES (3, 'Noticias');
- INSERTAR USUARIOS DE PRUEBA (User)
-- NOTA: Los IDs deben coincidir con las FK en user_credentials
INSERT INTO user (id, name, email, is_active) VALUES (1, 'LautaroAdmin', 'lautaro@admin.com', 1);
INSERT INTO user (id, name, email, is_active) VALUES (2, 'MariaMod', 'maria@mod.com', 1);
INSERT INTO user (id, name, email, is_active) VALUES (3, 'SofiaUser', 'sofia@user.com', 1);
- INSERTAR CREDENCIALES Y ROLES (UserCredentials)
-- Contraseñas: lautaropass, mariapass, sofiapass, hasheadas con bcrypt
-- Reemplaza los placeholders de HASH_... por los hashes generados por hash_generator.py
INSERT INTO user_credentials (id, user_id, password_hash, role) VALUES (
1, 1, 'HASH_DE_LAUTAROPASS', 'admin'
);
INSERT INTO user_credentials (id, user_id, password_hash, role) VALUES (
2, 2, 'HASH_DE_MARIAPASS', 'moderator'
);
INSERT INTO user_credentials (id, user_id, password_hash, role) VALUES (
3, 3, 'HASH_DE_SOFIAPASS', 'user'
);
- INSERTAR POSTS DE PRUEBA
INSERT INTO post (id, title, content, date_time, user_id, is_active, categoria_id) VALUES (
1, 'Primer Post Admin', 'Contenido del primer post hecho por el administrador.', NOW(), 1, 1, 1
);
INSERT INTO post (id, title, content, date_time, user_id, is_active, categoria_id) VALUES (
2, 'Noticia Importante', 'Este post es de un usuario normal.', NOW(), 3, 1, 3
);
- INSERTAR COMENTARIOS DE PRUEBA
INSERT INTO comment (id, content, date_time, user_id, post_id, is_active) VALUES (
1, '¡Excelente artículo!', NOW(), 2, 1, 1
);
INSERT INTO comment (id, content, date_time, user_id, post_id, is_active) VALUES (
2, 'Buen resumen de la noticia.', NOW(), 1, 2, 1
);
api_requests.http
HTTP
### =================================================================
### AUTENTICACIÓN
### =================================================================
# @name loginAdmin
POST [http://127.0.0.1:5000/login](http://127.0.0.1:5000/login)
Content-Type: application/json
{
"email": "lautaro@admin.com",
"password": "lautaropass"
}
### @name loginUser
POST [http://127.0.0.1:5000/login](http://127.0.0.1:5000/login)
Content-Type: application/json
{
"email": "sofia@user.com",
"password": "sofiapass"
}
### =================================================================
### POSTS (LECTURA PÚBLICA)
### =================================================================
# GET Posts activos
GET [http://127.0.0.1:5000/posts/active](http://127.0.0.1:5000/posts/active)
### GET Post por categoría (ID 1: Tecnología)
GET [http://127.0.0.1:5000/posts/category/1](http://127.0.0.1:5000/posts/category/1)
### GET Post por ID (asumiendo ID 1)
GET [http://127.0.0.1:5000/posts/1](http://127.0.0.1:5000/posts/1)
### GET Comentarios de un Post (asumiendo ID 1)
GET [http://127.0.0.1:5000/posts/1/comments/](http://127.0.0.1:5000/posts/1/comments/)
### =================================================================
### POSTS (CREACIÓN - REQUIERE AUTH)
### =================================================================
# @name createPost
POST [http://127.0.0.1:5000/posts/](http://127.0.0.1:5000/posts/)
Content-Type: application/json
Authorization: Bearer {{loginUser.response.body.access_token}}
{
"title": "Nuevo Post del Usuario",
"content": "Este es el contenido de prueba del post.",
"categoria_id": 2
}
### =================================================================
### POSTS (UPDATE/DELETE - REQUIERE AUTOR/ADMIN)
### =================================================================
# @name updatePost
PUT [http://127.0.0.1:5000/posts/2](http://127.0.0.1:5000/posts/2)
Content-Type: application/json
Authorization: Bearer {{loginAdmin.response.body.access_token}}
{
"title": "Post 2 Actualizado por Admin",
"content": "El admin puede editar el post de un usuario."
}
### DELETE Post (Soft Delete)
# El usuario 3 (SofiaUser) elimina su propio post (asumiendo post ID 2)
DELETE [http://127.0.0.1:5000/posts/2](http://127.0.0.1:5000/posts/2)
Authorization: Bearer {{loginUser.response.body.access_token}}
### =================================================================
### USER MANAGEMENT (ADMIN ONLY)
### =================================================================
# @name listUsers
GET [http://127.0.0.1:5000/users/](http://127.0.0.1:5000/users/)
Authorization: Bearer {{loginAdmin.response.body.access_token}}
### PUT Change Role (Asumiendo user ID 3, rol 'user' a 'moderator')
PUT [http://127.0.0.1:5000/users/3/role](http://127.0.0.1:5000/users/3/role)
Content-Type: application/json
Authorization: Bearer {{loginAdmin.response.body.access_token}}
{
"role": "moderator"
}
### =================================================================
### STATS (ADMIN/MODERATOR ONLY)
### =================================================================
# GET Stats (Admin obtiene todas las métricas)
GET [http://127.0.0.1:5000/stats/](http://127.0.0.1:5000/stats/)
Authorization: Bearer {{loginAdmin.response.body.access_token}}
### GET Stats (User obtiene error 403)
GET [http://127.0.0.1:5000/stats/](http://127.0.0.1:5000/stats/)
Authorization: Bearer {{loginUser.response.body.access_token}}