--- title: Directorio Online API emoji: 📒 colorFrom: blue colorTo: indigo sdk: docker app_port: 7860 pinned: false --- # Directorio Online — Backend (FastAPI + MongoDB) API REST genérica y fácil de extender para un directorio de negocios organizado por **categorías**, con **items** (menús, servicios o productos), **reseñas** e **imágenes**. Construida con **FastAPI** y **MongoDB** (driver `pymongo` con su API asíncrona), autenticación **JWT (OAuth2)** y validación con **Pydantic v2**. ## Modelo de datos y relaciones ``` Categoria │ (puede anidarse a sí misma vía parent_id) └── Negocio (N) Negocio ├── Item (N) (tipo: menu | servicio | producto) ├── Reseña (N) └── Imagen (N) (embebida) Item └── Imagen (N) (embebida) ``` Los identificadores son el `ObjectId` de MongoDB, expuesto siempre como cadena en el campo `id` de las respuestas. ## Estructura del proyecto ``` app/ ├── core/ Configuración FastAPI: settings, seguridad (JWT), │ middlewares (CORS), lifespan y manejadores de excepciones. ├── database/ Conexión y cliente de MongoDB, índices y utilidades. ├── exceptions/ Excepciones propias de la aplicación. ├── models/ Modelos de dominio (lo que se guarda en la BD). ├── schemas/ Esquemas de entrada/salida con validaciones │ (contrato entre rutas y servicios). ├── services/ Lógica de negocio (CRUD genérico + reglas por entidad). ├── rutas/ Endpoints de comunicación con los clientes. └── main.py Punto de entrada (crea la app). ``` ### Cómo extender Añadir una nueva entidad es directo gracias al `BaseService` (`app/services/base.py`), que implementa el CRUD genérico: 1. Define el modelo en `models/` y los esquemas en `schemas/`. 2. Crea un servicio que herede de `BaseService` (fija `collection_name`). 3. Añade el router en `rutas/` e inclúyelo en `rutas/__init__.py`. 4. (Opcional) Declara índices en `database/indexes.py`. ## Requisitos - Python 3.11+ - MongoDB 4.4+ en ejecución (local o remoto) ## Instalación ```bash python -m venv .venv source .venv/bin/activate # En Windows: .venv\Scripts\activate pip install -r requirements.txt ``` ## Configuración Copia `.env.example` a `.env` y ajusta los valores: ```bash cp .env.example .env ``` Genera una clave secreta segura para `SECRET_KEY`: ```bash python -c "import secrets; print(secrets.token_urlsafe(48))" ``` | Variable | Descripción | Por defecto | |-------------------------------|-----------------------------------------------|-----------------------------| | `APP_NAME` | Nombre de la aplicación | `Directorio Online API` | | `APP_VERSION` | Versión | `1.0.0` | | `APP_DESCRIPTION` | Descripción de la API | (ver `.env.example`) | | `DEBUG` | Modo depuración | `false` | | `API_PREFIX` | Prefijo de las rutas | `/api/v1` | | `PORT` | Puerto de escucha del contenedor | `7860` | | `MONGODB_URI` | URI de conexión a MongoDB | `mongodb://localhost:27017` | | `MONGODB_DB_NAME` | Nombre de la base de datos | `directorio_online` | | `SECRET_KEY` | Clave para firmar los JWT (**obligatoria**) | — | | `ALGORITHM` | Algoritmo de firma del JWT | `HS256` | | `ACCESS_TOKEN_EXPIRE_MINUTES` | Validez del token de acceso (minutos) | `60` | | `CORS_ORIGINS` | Orígenes permitidos (lista por comas o `*`) | `*` | ## Ejecución ```bash uvicorn app.main:app --reload ``` - Documentación interactiva (Swagger): `http://localhost:8000/docs` - Documentación alternativa (ReDoc): `http://localhost:8000/redoc` - Comprobación de salud: `http://localhost:8000/health` ## Docker La aplicación se empaqueta con el `Dockerfile` incluido. El contenedor escucha en el puerto indicado por la variable `PORT` (por defecto `7860`). ```bash # Construir la imagen docker build -t directorio-online-backend . # Ejecutar (las variables se pasan con -e o con --env-file) docker run --rm -p 7860:7860 \ -e SECRET_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')" \ -e MONGODB_URI="mongodb+srv://usuario:password@cluster.mongodb.net" \ -e MONGODB_DB_NAME="directorio_online" \ directorio-online-backend ``` La API quedará disponible en `http://localhost:7860` (Swagger en `/docs`). ## Despliegue en HuggingFace Spaces (Docker) El repositorio está listo para desplegarse como un **Space de tipo Docker**: - El `README.md` incluye las **metadatos** del Space en su cabecera YAML (`sdk: docker`, `app_port: 7860`). - El `Dockerfile` ejecuta el contenedor como el usuario `UID 1000` (requisito de HuggingFace) y arranca `uvicorn` en el puerto `7860`. Pasos: 1. Crea un Space → **SDK: Docker** → *Blank*. 2. Sube este repositorio al Space (o conéctalo a GitHub). 3. En **Settings → Variables and secrets**, define las variables del archivo `.env.example`: - Como **Secret**: `SECRET_KEY` y `MONGODB_URI`. - Como **Variable**: el resto (`MONGODB_DB_NAME`, `CORS_ORIGINS`, etc.). 4. El Space construye la imagen y la API queda expuesta en la URL del Space (`https://-.hf.space`), con Swagger en `/docs`. > **MongoDB:** HuggingFace no provee base de datos, así que `MONGODB_URI` > debe apuntar a una instancia accesible desde internet (p. ej. MongoDB > Atlas). El driver ya incluye `dnspython` (extra `pymongo[srv]`) para los > URIs `mongodb+srv://`. ## Autenticación - La **lectura** (GET) es pública. - La **escritura** (POST/PUT/DELETE) requiere un token JWT. ```bash # 1) Registrar un usuario curl -X POST http://localhost:8000/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"username":"admin","email":"admin@correo.com","password":"supersecreta"}' # 2) Iniciar sesión (formulario OAuth2; "username" admite usuario o correo) curl -X POST http://localhost:8000/api/v1/auth/login \ -d "username=admin&password=supersecreta" # 3) Usar el token en las operaciones de escritura curl -X POST http://localhost:8000/api/v1/categorias \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"nombre":"Restaurantes"}' ``` ## Endpoints principales | Método | Ruta | Auth | Descripción | |--------|-------------------------------|:----:|-----------------------------------| | POST | `/api/v1/auth/register` | — | Registrar usuario | | POST | `/api/v1/auth/login` | — | Iniciar sesión (devuelve JWT) | | GET | `/api/v1/auth/me` | ✓ | Usuario autenticado | | GET | `/api/v1/categorias` | — | Listar categorías | | POST | `/api/v1/categorias` | ✓ | Crear categoría | | GET | `/api/v1/categorias/{id}` | — | Obtener categoría | | PUT | `/api/v1/categorias/{id}` | ✓ | Actualizar categoría | | DELETE | `/api/v1/categorias/{id}` | ✓ | Eliminar categoría | | GET | `/api/v1/negocios` | — | Listar negocios (filtros) | | POST | `/api/v1/negocios` | ✓ | Crear negocio | | GET | `/api/v1/negocios/{id}` | — | Obtener negocio | | PUT | `/api/v1/negocios/{id}` | ✓ | Actualizar negocio | | DELETE | `/api/v1/negocios/{id}` | ✓ | Eliminar negocio (cascada) | | GET | `/api/v1/items` | — | Listar items (filtros) | | POST | `/api/v1/items` | ✓ | Crear item | | GET | `/api/v1/items/{id}` | — | Obtener item | | PUT | `/api/v1/items/{id}` | ✓ | Actualizar item | | DELETE | `/api/v1/items/{id}` | ✓ | Eliminar item | | GET | `/api/v1/resenas` | — | Listar reseñas (filtros) | | POST | `/api/v1/resenas` | ✓ | Crear reseña | | GET | `/api/v1/resenas/{id}` | — | Obtener reseña | | PUT | `/api/v1/resenas/{id}` | ✓ | Actualizar reseña | | DELETE | `/api/v1/resenas/{id}` | ✓ | Eliminar reseña | ### Filtros y paginación Todos los listados aceptan `skip` (≥0) y `limit` (1–100) y devuelven una respuesta paginada `{ total, skip, limit, items }`. - `GET /negocios?categoria_id=...&activo=true&buscar=pepe` - `GET /items?negocio_id=...&tipo=menu&activo=true` - `GET /resenas?negocio_id=...` - `GET /categorias?parent_id=...` ## Reglas de negocio - **Integridad referencial:** al crear un negocio se valida que la categoría exista; al crear items y reseñas se valida que el negocio exista. - **Borrado en cascada:** eliminar un negocio elimina también sus items y reseñas. - **Categorías:** no se puede eliminar una categoría que tenga subcategorías o negocios asociados. - **Slug único:** el `slug` de la categoría es único; se autogenera a partir del nombre si no se proporciona. ## Calidad de código El código sigue PEP 8 (límite de línea de 99 columnas, configurado en `setup.cfg`). Para verificarlo: ```bash pip install pycodestyle pyflakes pyflakes app/ pycodestyle app/ ```