- Add ImageUploadService for public URL generation (Meta APIs) - Create PublisherManager for unified multi-platform publishing - Add /api/publish endpoints (single, multiple, thread, test) - Add compose page in dashboard for creating posts - Add connection test script (scripts/test_connections.py) - Update navigation with compose link and logout New endpoints: - POST /api/publish/single - Publish to one platform - POST /api/publish/multiple - Publish to multiple platforms - POST /api/publish/thread - Publish thread (X/Threads) - GET /api/publish/test - Test all API connections - GET /api/publish/platforms - List available platforms Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
170 lines
5.5 KiB
Python
170 lines
5.5 KiB
Python
"""
|
|
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, RedirectResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from sqlalchemy.orm import Session
|
|
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)
|
|
week_start = today_start - timedelta(days=now.weekday())
|
|
|
|
stats = {
|
|
"posts_today": db.query(Post).filter(
|
|
Post.published_at >= today_start
|
|
).count(),
|
|
"posts_week": db.query(Post).filter(
|
|
Post.published_at >= week_start
|
|
).count(),
|
|
"pending_approval": db.query(Post).filter(
|
|
Post.status == "pending_approval"
|
|
).count(),
|
|
"scheduled": db.query(Post).filter(
|
|
Post.status == "scheduled"
|
|
).count(),
|
|
"interactions_pending": db.query(Interaction).filter(
|
|
Interaction.responded == False,
|
|
Interaction.is_archived == False
|
|
).count()
|
|
}
|
|
|
|
# Posts pendientes
|
|
pending_posts = db.query(Post).filter(
|
|
Post.status == "pending_approval"
|
|
).order_by(Post.scheduled_at.asc()).limit(5).all()
|
|
|
|
# Próximas publicaciones
|
|
scheduled_posts = db.query(Post).filter(
|
|
Post.status == "scheduled",
|
|
Post.scheduled_at >= now
|
|
).order_by(Post.scheduled_at.asc()).limit(5).all()
|
|
|
|
# Interacciones recientes
|
|
recent_interactions = db.query(Interaction).filter(
|
|
Interaction.responded == False
|
|
).order_by(Interaction.interaction_at.desc()).limit(5).all()
|
|
|
|
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],
|
|
"recent_interactions": [i.to_dict() for i in recent_interactions]
|
|
})
|
|
|
|
|
|
@router.get("/compose", response_class=HTMLResponse)
|
|
async def dashboard_compose(request: Request, db: Session = Depends(get_db)):
|
|
"""Página para crear nuevas publicaciones."""
|
|
user = require_auth(request, db)
|
|
if not user:
|
|
return RedirectResponse(url="/login", status_code=302)
|
|
|
|
return templates.TemplateResponse("compose.html", {
|
|
"request": request,
|
|
"user": user.to_dict()
|
|
})
|
|
|
|
|
|
@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, 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,
|
|
"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, 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,
|
|
"user": user.to_dict()
|
|
})
|
|
|
|
|
|
@router.get("/services", response_class=HTMLResponse)
|
|
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,
|
|
"user": user.to_dict()
|
|
})
|