Spaces:
Sleeping
Sleeping
| 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://<usuario>-<space>.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 <ACCESS_TOKEN>" \ | |
| -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/ | |
| ``` | |