feat(backend): Update models and endpoints configuration
- Enhanced ticket and comment models with proper relationships - Updated client_profile model for better data handling - Improved auth endpoint with better error handling - Updated main app configuration and imports - Added new dependencies to requirements.txt - Enhanced tickets endpoint with attachment support
This commit is contained in:
@@ -3,18 +3,24 @@ Tickets endpoints - ServiceManagerWeb
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import selectinload
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
from app.core.database import get_db
|
||||
from app.api.deps import get_current_user
|
||||
from app.api.deps import get_current_user, get_current_tenant
|
||||
from app.models.ticket import Ticket, TicketStatus, TicketPriority
|
||||
from app.models.user import User
|
||||
from app.models.category import Category # ✅ CORREGIDO: Era TicketCategory
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.category import Category
|
||||
from app.models.system import System
|
||||
from app.models.comment import TicketComment
|
||||
from app.models.attachment import TicketAttachment
|
||||
from app.api.schemas.attachment import AttachmentResponse
|
||||
from app.core.file_handler import file_handler
|
||||
import uuid
|
||||
|
||||
router = APIRouter()
|
||||
@@ -573,19 +579,60 @@ async def delete_ticket(
|
||||
return {"message": "Ticket deleted successfully"}
|
||||
|
||||
# ===================================
|
||||
# ATTACHMENT ENDPOINTS (placeholder)
|
||||
# ===================================
|
||||
# ATTACHMENT ENDPOINTS
|
||||
# ===================================
|
||||
|
||||
@router.get("/{ticket_id}/attachments")
|
||||
@router.get("/{ticket_id}/attachments", response_model=List[AttachmentResponse])
|
||||
async def get_ticket_attachments(
|
||||
ticket_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_tenant: Tenant = Depends(get_current_tenant)
|
||||
):
|
||||
"""
|
||||
Obtener adjuntos de un ticket
|
||||
"""
|
||||
return []
|
||||
"""Obtener adjuntos de un ticket"""
|
||||
try:
|
||||
ticket_uuid = uuid.UUID(ticket_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="ID de ticket inválido")
|
||||
|
||||
# Verificar que el ticket existe y pertenece al tenant
|
||||
result = await db.execute(
|
||||
select(Ticket).where(Ticket.id == ticket_uuid, Ticket.tenant_id == current_tenant.id)
|
||||
)
|
||||
ticket = result.scalar_one_or_none()
|
||||
|
||||
if not ticket:
|
||||
raise HTTPException(status_code=404, detail="Ticket no encontrado")
|
||||
|
||||
# Obtener attachments
|
||||
result = await db.execute(
|
||||
select(TicketAttachment)
|
||||
.where(TicketAttachment.ticket_id == ticket_uuid)
|
||||
.options(selectinload(TicketAttachment.uploaded_by_user))
|
||||
.order_by(TicketAttachment.created_at.desc())
|
||||
)
|
||||
attachments = result.scalars().all()
|
||||
|
||||
# Construir respuesta
|
||||
response = []
|
||||
for att in attachments:
|
||||
response.append(AttachmentResponse(
|
||||
id=att.id,
|
||||
ticket_id=att.ticket_id,
|
||||
comment_id=att.comment_id,
|
||||
uploaded_by=att.uploaded_by,
|
||||
filename=att.filename,
|
||||
original_filename=att.original_filename,
|
||||
mime_type=att.mime_type,
|
||||
file_size=att.file_size,
|
||||
file_path=att.file_path,
|
||||
uploaded_by_name=f"{att.uploaded_by_user.first_name} {att.uploaded_by_user.last_name}" if att.uploaded_by_user else "Unknown",
|
||||
created_at=att.created_at,
|
||||
download_url=f"/api/v1/tickets/{ticket_id}/attachments/{att.id}/download"
|
||||
))
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@router.post("/{ticket_id}/attachments", status_code=status.HTTP_201_CREATED)
|
||||
@@ -593,12 +640,106 @@ async def upload_attachment(
|
||||
ticket_id: str,
|
||||
file: UploadFile = File(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_tenant: Tenant = Depends(get_current_tenant)
|
||||
):
|
||||
"""
|
||||
Subir un archivo adjunto a un ticket
|
||||
"""
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
detail="File uploads not yet implemented"
|
||||
"""Subir un archivo adjunto a un ticket"""
|
||||
try:
|
||||
ticket_uuid = uuid.UUID(ticket_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="ID de ticket inválido")
|
||||
|
||||
# Verificar ticket
|
||||
result = await db.execute(
|
||||
select(Ticket).where(Ticket.id == ticket_uuid, Ticket.tenant_id == current_tenant.id)
|
||||
)
|
||||
ticket = result.scalar_one_or_none()
|
||||
|
||||
if not ticket:
|
||||
raise HTTPException(status_code=404, detail="Ticket no encontrado")
|
||||
|
||||
# Guardar archivo
|
||||
file_metadata = await file_handler.save_upload(file, current_tenant.id, ticket_uuid)
|
||||
|
||||
# Crear registro en BD
|
||||
attachment = TicketAttachment(
|
||||
id=uuid.uuid4(),
|
||||
ticket_id=ticket_uuid,
|
||||
uploaded_by=current_user.id,
|
||||
filename=file_metadata["filename"],
|
||||
original_filename=file_metadata["original_filename"],
|
||||
mime_type=file_metadata["mime_type"],
|
||||
file_size=file_metadata["file_size"],
|
||||
file_path=file_metadata["file_path"],
|
||||
md5_hash=file_metadata["md5_hash"],
|
||||
sha256_hash=file_metadata["sha256_hash"],
|
||||
created_at=datetime.utcnow()
|
||||
)
|
||||
|
||||
db.add(attachment)
|
||||
await db.commit()
|
||||
await db.refresh(attachment, ["uploaded_by_user"])
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Archivo subido exitosamente",
|
||||
"data": AttachmentResponse(
|
||||
id=attachment.id,
|
||||
ticket_id=attachment.ticket_id,
|
||||
comment_id=attachment.comment_id,
|
||||
uploaded_by=attachment.uploaded_by,
|
||||
filename=attachment.filename,
|
||||
original_filename=attachment.original_filename,
|
||||
mime_type=attachment.mime_type,
|
||||
file_size=attachment.file_size,
|
||||
file_path=attachment.file_path,
|
||||
uploaded_by_name=f"{attachment.uploaded_by_user.first_name} {attachment.uploaded_by_user.last_name}",
|
||||
created_at=attachment.created_at,
|
||||
download_url=f"/api/v1/tickets/{ticket_id}/attachments/{attachment.id}/download"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{ticket_id}/attachments/{attachment_id}/download")
|
||||
async def download_attachment(
|
||||
ticket_id: str,
|
||||
attachment_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_tenant: Tenant = Depends(get_current_tenant)
|
||||
):
|
||||
"""Descargar un archivo adjunto"""
|
||||
try:
|
||||
ticket_uuid = uuid.UUID(ticket_id)
|
||||
attachment_uuid = uuid.UUID(attachment_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="ID inválido")
|
||||
|
||||
# Verificar ticket
|
||||
result = await db.execute(
|
||||
select(Ticket).where(Ticket.id == ticket_uuid, Ticket.tenant_id == current_tenant.id)
|
||||
)
|
||||
ticket = result.scalar_one_or_none()
|
||||
|
||||
if not ticket:
|
||||
raise HTTPException(status_code=404, detail="Ticket no encontrado")
|
||||
|
||||
# Obtener attachment
|
||||
result = await db.execute(
|
||||
select(TicketAttachment)
|
||||
.where(TicketAttachment.id == attachment_uuid, TicketAttachment.ticket_id == ticket_uuid)
|
||||
)
|
||||
attachment = result.scalar_one_or_none()
|
||||
|
||||
if not attachment:
|
||||
raise HTTPException(status_code=404, detail="Adjunto no encontrado")
|
||||
|
||||
# Obtener path del archivo
|
||||
file_path = file_handler.get_file_path(attachment.file_path)
|
||||
|
||||
# Retornar archivo
|
||||
return FileResponse(
|
||||
path=file_path,
|
||||
filename=attachment.original_filename,
|
||||
media_type=attachment.mime_type
|
||||
)
|
||||
Reference in New Issue
Block a user