- Estructura completa del proyecto con FastAPI - Modelos de base de datos (productos, servicios, posts, calendario, interacciones) - Publishers para X, Threads, Instagram, Facebook - Generador de contenido con DeepSeek API - Worker de Celery con tareas programadas - Dashboard básico con templates HTML - Docker Compose para despliegue - Documentación completa Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
175 lines
5.4 KiB
Python
175 lines
5.4 KiB
Python
"""
|
|
API Routes para gestión de Servicios.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from pydantic import BaseModel
|
|
|
|
from app.core.database import get_db
|
|
from app.models.service import Service
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# ===========================================
|
|
# SCHEMAS
|
|
# ===========================================
|
|
|
|
class ServiceCreate(BaseModel):
|
|
name: str
|
|
description: str
|
|
short_description: Optional[str] = None
|
|
category: str
|
|
target_sectors: Optional[List[str]] = None
|
|
benefits: Optional[List[str]] = None
|
|
features: Optional[List[str]] = None
|
|
case_studies: Optional[List[dict]] = None
|
|
icon: Optional[str] = None
|
|
images: Optional[List[str]] = None
|
|
main_image: Optional[str] = None
|
|
price_range: Optional[str] = None
|
|
has_free_demo: bool = False
|
|
tags: Optional[List[str]] = None
|
|
call_to_action: Optional[str] = None
|
|
is_featured: bool = False
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class ServiceUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
short_description: Optional[str] = None
|
|
category: Optional[str] = None
|
|
target_sectors: Optional[List[str]] = None
|
|
benefits: Optional[List[str]] = None
|
|
features: Optional[List[str]] = None
|
|
case_studies: Optional[List[dict]] = None
|
|
icon: Optional[str] = None
|
|
images: Optional[List[str]] = None
|
|
main_image: Optional[str] = None
|
|
price_range: Optional[str] = None
|
|
has_free_demo: Optional[bool] = None
|
|
tags: Optional[List[str]] = None
|
|
call_to_action: Optional[str] = None
|
|
is_active: Optional[bool] = None
|
|
is_featured: Optional[bool] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ===========================================
|
|
# ENDPOINTS
|
|
# ===========================================
|
|
|
|
@router.get("/")
|
|
async def list_services(
|
|
category: Optional[str] = Query(None),
|
|
target_sector: Optional[str] = Query(None),
|
|
is_active: Optional[bool] = Query(True),
|
|
is_featured: Optional[bool] = Query(None),
|
|
limit: int = Query(50, le=100),
|
|
offset: int = Query(0),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Listar servicios con filtros."""
|
|
query = db.query(Service)
|
|
|
|
if category:
|
|
query = query.filter(Service.category == category)
|
|
if is_active is not None:
|
|
query = query.filter(Service.is_active == is_active)
|
|
if is_featured is not None:
|
|
query = query.filter(Service.is_featured == is_featured)
|
|
if target_sector:
|
|
query = query.filter(Service.target_sectors.contains([target_sector]))
|
|
|
|
services = query.order_by(Service.created_at.desc()).offset(offset).limit(limit).all()
|
|
return [s.to_dict() for s in services]
|
|
|
|
|
|
@router.get("/categories")
|
|
async def list_categories(db: Session = Depends(get_db)):
|
|
"""Listar categorías únicas de servicios."""
|
|
categories = db.query(Service.category).distinct().all()
|
|
return [c[0] for c in categories if c[0]]
|
|
|
|
|
|
@router.get("/featured")
|
|
async def list_featured_services(db: Session = Depends(get_db)):
|
|
"""Listar servicios destacados."""
|
|
services = db.query(Service).filter(
|
|
Service.is_featured == True,
|
|
Service.is_active == True
|
|
).all()
|
|
return [s.to_dict() for s in services]
|
|
|
|
|
|
@router.get("/{service_id}")
|
|
async def get_service(service_id: int, db: Session = Depends(get_db)):
|
|
"""Obtener un servicio por ID."""
|
|
service = db.query(Service).filter(Service.id == service_id).first()
|
|
if not service:
|
|
raise HTTPException(status_code=404, detail="Servicio no encontrado")
|
|
return service.to_dict()
|
|
|
|
|
|
@router.post("/")
|
|
async def create_service(service_data: ServiceCreate, db: Session = Depends(get_db)):
|
|
"""Crear un nuevo servicio."""
|
|
service = Service(**service_data.dict())
|
|
db.add(service)
|
|
db.commit()
|
|
db.refresh(service)
|
|
return service.to_dict()
|
|
|
|
|
|
@router.put("/{service_id}")
|
|
async def update_service(service_id: int, service_data: ServiceUpdate, db: Session = Depends(get_db)):
|
|
"""Actualizar un servicio."""
|
|
service = db.query(Service).filter(Service.id == service_id).first()
|
|
if not service:
|
|
raise HTTPException(status_code=404, detail="Servicio no encontrado")
|
|
|
|
update_data = service_data.dict(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(service, field, value)
|
|
|
|
service.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(service)
|
|
return service.to_dict()
|
|
|
|
|
|
@router.delete("/{service_id}")
|
|
async def delete_service(service_id: int, db: Session = Depends(get_db)):
|
|
"""Eliminar un servicio."""
|
|
service = db.query(Service).filter(Service.id == service_id).first()
|
|
if not service:
|
|
raise HTTPException(status_code=404, detail="Servicio no encontrado")
|
|
|
|
db.delete(service)
|
|
db.commit()
|
|
return {"message": "Servicio eliminado", "service_id": service_id}
|
|
|
|
|
|
@router.post("/{service_id}/toggle-featured")
|
|
async def toggle_featured(service_id: int, db: Session = Depends(get_db)):
|
|
"""Alternar estado de servicio destacado."""
|
|
service = db.query(Service).filter(Service.id == service_id).first()
|
|
if not service:
|
|
raise HTTPException(status_code=404, detail="Servicio no encontrado")
|
|
|
|
service.is_featured = not service.is_featured
|
|
db.commit()
|
|
|
|
return {
|
|
"message": f"Servicio {'destacado' if service.is_featured else 'no destacado'}",
|
|
"is_featured": service.is_featured
|
|
}
|