- 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>
181 lines
5.6 KiB
Python
181 lines
5.6 KiB
Python
"""
|
|
API Routes para gestión de Productos.
|
|
"""
|
|
|
|
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.product import Product
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# ===========================================
|
|
# SCHEMAS
|
|
# ===========================================
|
|
|
|
class ProductCreate(BaseModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
short_description: Optional[str] = None
|
|
category: str
|
|
subcategory: Optional[str] = None
|
|
brand: Optional[str] = None
|
|
model: Optional[str] = None
|
|
price: float
|
|
price_usd: Optional[float] = None
|
|
stock: int = 0
|
|
specs: Optional[dict] = None
|
|
images: Optional[List[str]] = None
|
|
main_image: Optional[str] = None
|
|
tags: Optional[List[str]] = None
|
|
highlights: Optional[List[str]] = None
|
|
is_featured: bool = False
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class ProductUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
short_description: Optional[str] = None
|
|
category: Optional[str] = None
|
|
subcategory: Optional[str] = None
|
|
brand: Optional[str] = None
|
|
model: Optional[str] = None
|
|
price: Optional[float] = None
|
|
price_usd: Optional[float] = None
|
|
stock: Optional[int] = None
|
|
is_available: Optional[bool] = None
|
|
specs: Optional[dict] = None
|
|
images: Optional[List[str]] = None
|
|
main_image: Optional[str] = None
|
|
tags: Optional[List[str]] = None
|
|
highlights: Optional[List[str]] = None
|
|
is_featured: Optional[bool] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ===========================================
|
|
# ENDPOINTS
|
|
# ===========================================
|
|
|
|
@router.get("/")
|
|
async def list_products(
|
|
category: Optional[str] = Query(None),
|
|
brand: Optional[str] = Query(None),
|
|
is_available: Optional[bool] = Query(None),
|
|
is_featured: Optional[bool] = Query(None),
|
|
min_price: Optional[float] = Query(None),
|
|
max_price: Optional[float] = Query(None),
|
|
limit: int = Query(50, le=100),
|
|
offset: int = Query(0),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Listar productos con filtros."""
|
|
query = db.query(Product)
|
|
|
|
if category:
|
|
query = query.filter(Product.category == category)
|
|
if brand:
|
|
query = query.filter(Product.brand == brand)
|
|
if is_available is not None:
|
|
query = query.filter(Product.is_available == is_available)
|
|
if is_featured is not None:
|
|
query = query.filter(Product.is_featured == is_featured)
|
|
if min_price is not None:
|
|
query = query.filter(Product.price >= min_price)
|
|
if max_price is not None:
|
|
query = query.filter(Product.price <= max_price)
|
|
|
|
products = query.order_by(Product.created_at.desc()).offset(offset).limit(limit).all()
|
|
return [p.to_dict() for p in products]
|
|
|
|
|
|
@router.get("/categories")
|
|
async def list_categories(db: Session = Depends(get_db)):
|
|
"""Listar categorías únicas de productos."""
|
|
categories = db.query(Product.category).distinct().all()
|
|
return [c[0] for c in categories if c[0]]
|
|
|
|
|
|
@router.get("/featured")
|
|
async def list_featured_products(db: Session = Depends(get_db)):
|
|
"""Listar productos destacados."""
|
|
products = db.query(Product).filter(
|
|
Product.is_featured == True,
|
|
Product.is_available == True
|
|
).all()
|
|
return [p.to_dict() for p in products]
|
|
|
|
|
|
@router.get("/{product_id}")
|
|
async def get_product(product_id: int, db: Session = Depends(get_db)):
|
|
"""Obtener un producto por ID."""
|
|
product = db.query(Product).filter(Product.id == product_id).first()
|
|
if not product:
|
|
raise HTTPException(status_code=404, detail="Producto no encontrado")
|
|
return product.to_dict()
|
|
|
|
|
|
@router.post("/")
|
|
async def create_product(product_data: ProductCreate, db: Session = Depends(get_db)):
|
|
"""Crear un nuevo producto."""
|
|
product = Product(**product_data.dict())
|
|
db.add(product)
|
|
db.commit()
|
|
db.refresh(product)
|
|
return product.to_dict()
|
|
|
|
|
|
@router.put("/{product_id}")
|
|
async def update_product(product_id: int, product_data: ProductUpdate, db: Session = Depends(get_db)):
|
|
"""Actualizar un producto."""
|
|
product = db.query(Product).filter(Product.id == product_id).first()
|
|
if not product:
|
|
raise HTTPException(status_code=404, detail="Producto no encontrado")
|
|
|
|
update_data = product_data.dict(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(product, field, value)
|
|
|
|
product.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(product)
|
|
return product.to_dict()
|
|
|
|
|
|
@router.delete("/{product_id}")
|
|
async def delete_product(product_id: int, db: Session = Depends(get_db)):
|
|
"""Eliminar un producto."""
|
|
product = db.query(Product).filter(Product.id == product_id).first()
|
|
if not product:
|
|
raise HTTPException(status_code=404, detail="Producto no encontrado")
|
|
|
|
db.delete(product)
|
|
db.commit()
|
|
return {"message": "Producto eliminado", "product_id": product_id}
|
|
|
|
|
|
@router.post("/{product_id}/toggle-featured")
|
|
async def toggle_featured(product_id: int, db: Session = Depends(get_db)):
|
|
"""Alternar estado de producto destacado."""
|
|
product = db.query(Product).filter(Product.id == product_id).first()
|
|
if not product:
|
|
raise HTTPException(status_code=404, detail="Producto no encontrado")
|
|
|
|
product.is_featured = not product.is_featured
|
|
db.commit()
|
|
|
|
return {
|
|
"message": f"Producto {'destacado' if product.is_featured else 'no destacado'}",
|
|
"is_featured": product.is_featured
|
|
}
|