feat(phase-1): Complete foundation setup

- Add User model and authentication system with JWT cookies
- Implement login/logout routes and protected dashboard
- Add Alembic database migration configuration
- Add create_admin.py script for initial user setup
- Make ContentGenerator and ImageGenerator lazy-initialized
- Add comprehensive API keys setup documentation
- Fix startup errors when services unavailable

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 01:30:15 +00:00
parent 049d2133f9
commit 541a8484a7
14 changed files with 1092 additions and 18 deletions

View File

@@ -3,8 +3,9 @@ API Routes para el Dashboard.
"""
from datetime import datetime, timedelta
from typing import Optional
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from sqlalchemy import func
@@ -12,15 +13,26 @@ from sqlalchemy import func
from app.core.database import get_db
from app.models.post import Post
from app.models.interaction import Interaction
from app.models.user import User
from app.api.routes.auth import get_current_user_from_cookie
router = APIRouter()
templates = Jinja2Templates(directory="dashboard/templates")
def require_auth(request: Request, db: Session) -> Optional[User]:
"""Verificar autenticación. Retorna None si no está autenticado."""
return get_current_user_from_cookie(request, db)
@router.get("/", response_class=HTMLResponse)
async def dashboard_home(request: Request, db: Session = Depends(get_db)):
"""Página principal del dashboard."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
# Estadísticas
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
@@ -63,6 +75,7 @@ async def dashboard_home(request: Request, db: Session = Depends(get_db)):
return templates.TemplateResponse("index.html", {
"request": request,
"user": user.to_dict(),
"stats": stats,
"pending_posts": [p.to_dict() for p in pending_posts],
"scheduled_posts": [p.to_dict() for p in scheduled_posts],
@@ -73,46 +86,71 @@ async def dashboard_home(request: Request, db: Session = Depends(get_db)):
@router.get("/posts", response_class=HTMLResponse)
async def dashboard_posts(request: Request, db: Session = Depends(get_db)):
"""Página de gestión de posts."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
posts = db.query(Post).order_by(Post.created_at.desc()).limit(50).all()
return templates.TemplateResponse("posts.html", {
"request": request,
"user": user.to_dict(),
"posts": [p.to_dict() for p in posts]
})
@router.get("/calendar", response_class=HTMLResponse)
async def dashboard_calendar(request: Request):
async def dashboard_calendar(request: Request, db: Session = Depends(get_db)):
"""Página de calendario."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("calendar.html", {
"request": request
"request": request,
"user": user.to_dict()
})
@router.get("/interactions", response_class=HTMLResponse)
async def dashboard_interactions(request: Request, db: Session = Depends(get_db)):
"""Página de interacciones."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
interactions = db.query(Interaction).filter(
Interaction.is_archived == False
).order_by(Interaction.interaction_at.desc()).limit(50).all()
return templates.TemplateResponse("interactions.html", {
"request": request,
"user": user.to_dict(),
"interactions": [i.to_dict() for i in interactions]
})
@router.get("/products", response_class=HTMLResponse)
async def dashboard_products(request: Request):
async def dashboard_products(request: Request, db: Session = Depends(get_db)):
"""Página de productos."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("products.html", {
"request": request
"request": request,
"user": user.to_dict()
})
@router.get("/services", response_class=HTMLResponse)
async def dashboard_services(request: Request):
async def dashboard_services(request: Request, db: Session = Depends(get_db)):
"""Página de servicios."""
user = require_auth(request, db)
if not user:
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("services.html", {
"request": request
"request": request,
"user": user.to_dict()
})