Backend¶
API REST en FastAPI sobre MongoDB 8 con el ODM Beanie (driver async Motor). Dependencias con Poetry (backend/pyproject.toml, Python >=3.12,<3.14; la imagen Docker usa Python 3.13).
Estructura¶
backend/app/
├── main.py # FastAPI, lifespan (init_beanie), middlewares, routers, Sentry
├── core/
│ ├── config.py # Settings (pydantic-settings, lee backend/.env)
│ ├── security.py # JWT + BCrypt
│ ├── rate_limit.py # limitador sliding-window en memoria compartido
│ ├── request_limits.py # middleware ASGI de tamaño máximo del cuerpo (413)
│ └── clock.py # utcnow() sin zona horaria (como lo devuelve Motor)
└── api/
├── deps.py # get_current_user_email / get_current_user / get_current_user_id
└── v1/<módulo>/ # un módulo por entidad
Patrón de un módulo¶
Cada entidad vive en su carpeta bajo backend/app/api/v1/ con las mismas capas:
flowchart LR
RT["routes.py<br/>HTTP: parseo y Depends"] --> S["services.py<br/>lógica de negocio"]
S --> I["interfaces.py<br/>contratos ABC"]
RP["repository.py<br/>Beanie / MongoDB"] -.implementa.-> I
SC["schemas.py<br/>Pydantic v2 + Document"] --- RT
SC --- RP
| Archivo | Responsabilidad |
|---|---|
interfaces.py |
Clases abstractas (ABC) con el contrato del repositorio |
repository.py |
Implementación concreta con Beanie |
services.py |
Lógica de negocio; depende de la interfaz, nunca del repositorio concreto |
schemas.py |
Modelos Pydantic v2: Base, Create, Update, InDB (Document de Beanie) y Response |
routes.py |
APIRouter: solo HTTP, delega al servicio |
Regla
Las rutas no contienen lógica de negocio y los servicios no importan repositorios concretos: los reciben ya construidos (inyección de dependencias con Depends).
Los 12 módulos¶
| Módulo | Propósito | Archivos además del patrón base |
|---|---|---|
users/ |
Registro, login, perfil, cambio y reseteo de contraseña, borrado de cuenta, estado de instalación | audit_log.py, deletion_service.py, email_templates.py, rate_limiter.py |
projects/ |
CRUD de proyectos; exportar (ZIP / Markdown para IA) e importar (archivos, ZIP, Excalidraw) | export_service.py, export_builders.py, import_service.py, import_parsers.py |
diagrams/ |
CRUD, mover, duplicar, corrección con IA, conversión entre tipos, validación de sintaxis y proxy de render hacia Kroki | fix_service.py, fix_prompts.py, conversion_service.py, syntax_validator.py, config_utils.py, kroki_client.py, rate_limiter.py |
folders/ |
CRUD de carpetas dentro de un proyecto | — |
ai_providers/ |
Proveedores de IA del usuario (keys cifradas), generación/mejora de diagramas y descripciones | clients/ (base.py, factory.py y un cliente por proveedor), prompts.py, model_catalog.json, model_catalog.py |
chat_sessions/ |
Sesiones y mensajes del chat de IA, streaming SSE, compactación de contexto | sse_events.py |
subscriptions/ |
Planes, suscripciones, límites de uso, Stripe (checkout, portal, webhooks) | plan_repository.py, plan_service.py, subscription_repository.py, subscription_service.py, billing_service.py, usage_limiter.py, webhook_handler.py, webhook_routes.py, stripe_catalog_service.py, migration_service.py, payment_providers/, constants.py, exceptions.py, logger.py |
shared_links/ |
Enlaces para compartir diagramas (públicos o con código), expiración, log de accesos | public_routes.py, rate_limiter.py |
prompt_history/ |
Historial de prompts de IA del usuario | — |
integrations/ |
Configuración (admin) de vendors de email, pago y OAuth | email_service.py, email_vendors/ (Resend), vendor_factory.py |
mfa/ |
MFA por TOTP y por código de email, códigos de recuperación, administración de MFA de usuarios | totp_service.py, admin_service.py, rate_limiter.py |
oauth/ |
Login con OAuth 2.0 / OpenID Connect (Google; extensible con IOAuthProvider) |
providers/ (google.py, factory.py) |
Dónde vive cada dato
Los datos de MFA se guardan en el propio documento del usuario (UserInDB), por eso mfa/ no registra un Document propio.
Registro en main.py¶
Un módulo nuevo no existe para la app hasta registrarlo en backend/app/main.py:
- Su
Documenteninit_beanie(document_models=[...])dentro dellifespan. Hoy se registran:UserInDB,ProjectInDB,DiagramInDB,FolderInDB,UserAISettingsInDB,PlanInDB,SubscriptionInDB,StripeConfigInDB,WebhookEventInDB,PromptHistoryInDB,ChatSessionInDB,ChatMessageInDB,SharedLinkInDB,AccessLogInDB,VendorConfigInDB,AuditLogEntryyOAuthStateToken. - Su router con
app.include_router(router, prefix=settings.API_V1_PREFIX). Los proveedores de IA se montan bajo/api/v1/ai. - Si tiene rutas públicas o de webhooks en archivos aparte (
public_routes.py,webhook_routes.py), también se registran.
Además, main.py configura:
- Middlewares: límite de tamaño del cuerpo (
MAX_REQUEST_BODY_BYTES, con un presupuesto mayor paraPOST /projects/{id}/import), cabeceras de seguridad y CORS con lista explícita de cabeceras. - Producción (
APP_ENV=production): Swagger, ReDoc yopenapi.jsondeshabilitados, HSTS y CSP en las respuestas, y un manejador que oculta los stack traces (500 Internal server error). - Sentry: solo si
SENTRY_DSNestá definido, con sanitización de datos sensibles enbefore_send.
Configuración¶
backend/app/core/config.py define Settings con pydantic-settings; los valores salen de variables de entorno o de backend/.env. Las más relevantes para desarrollar:
| Variable | Por defecto | Uso |
|---|---|---|
JWT_SECRET |
— (obligatoria) | Firma de los JWT |
MONGO_URI / DATABASE_NAME |
mongodb://mongodb:27017 / diagramahub |
Conexión a MongoDB |
APP_ENV |
production |
Usa development en local para tener Swagger |
KROKI_URL |
http://kroki:8000 |
Servicio de render |
FRONTEND_URL / BACKEND_CORS_ORIGINS |
http://localhost:5173 / vacío |
CORS (si está vacío, se usa FRONTEND_URL) |
AI_ENCRYPTION_KEY |
— | Clave Fernet para cifrar API keys de IA |
La referencia completa para operadores está en Configuración.
Calidad de código¶
docker exec diagramahub-backend poetry run black app/
docker exec diagramahub-backend poetry run ruff check app/
docker exec diagramahub-backend poetry run mypy app/
Ruff y Black usan line-length = 100. Todas las funciones llevan type hints y docstring; los errores de API se lanzan con HTTPException.