Files
LeDiscord/backend/api/routers/tickets.py

296 lines
11 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime
import os
import uuid
from pathlib import Path
from PIL import Image
from config.database import get_db
from config.settings import settings
from models.ticket import Ticket, TicketType, TicketStatus, TicketPriority
from models.user import User
from schemas.ticket import TicketCreate, TicketUpdate, TicketResponse, TicketAdminUpdate
from utils.security import get_current_active_user, get_admin_user
router = APIRouter()
@router.get("/", response_model=List[TicketResponse])
async def get_user_tickets(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Get current user's tickets."""
tickets = db.query(Ticket).filter(Ticket.user_id == current_user.id).order_by(Ticket.created_at.desc()).all()
return [format_ticket_response(ticket, db) for ticket in tickets]
@router.get("/admin", response_model=List[TicketResponse])
async def get_all_tickets_admin(
db: Session = Depends(get_db),
current_user: User = Depends(get_admin_user),
status_filter: Optional[TicketStatus] = None,
type_filter: Optional[TicketType] = None,
priority_filter: Optional[TicketPriority] = None
):
"""Get all tickets (admin only)."""
query = db.query(Ticket)
if status_filter:
query = query.filter(Ticket.status == status_filter)
if type_filter:
query = query.filter(Ticket.ticket_type == type_filter)
if priority_filter:
query = query.filter(Ticket.priority == priority_filter)
tickets = query.order_by(Ticket.created_at.desc()).all()
return [format_ticket_response(ticket, db) for ticket in tickets]
@router.get("/{ticket_id}", response_model=TicketResponse)
async def get_ticket(
ticket_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Get a specific ticket."""
ticket = db.query(Ticket).filter(Ticket.id == ticket_id).first()
if not ticket:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Ticket not found"
)
# Users can only see their own tickets, admins can see all
if ticket.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to view this ticket"
)
return format_ticket_response(ticket, db)
@router.post("/", response_model=TicketResponse)
async def create_ticket(
title: str = Form(...),
description: str = Form(...),
ticket_type: str = Form("other"), # Changed from TicketType to str
priority: str = Form("medium"), # Changed from TicketPriority to str
screenshot: Optional[UploadFile] = File(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Create a new ticket."""
print(f"DEBUG - Received ticket data: title='{title}', description='{description}', ticket_type='{ticket_type}', priority='{priority}'")
# Validate and convert ticket_type
try:
ticket_type_enum = TicketType(ticket_type)
print(f"DEBUG - Ticket type converted successfully: {ticket_type_enum}")
except ValueError as e:
print(f"DEBUG - Error converting ticket_type '{ticket_type}': {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid ticket type: {ticket_type}. Valid types: {[t.value for t in TicketType]}"
)
# Validate and convert priority
try:
priority_enum = TicketPriority(priority)
print(f"DEBUG - Priority converted successfully: {priority_enum}")
except ValueError as e:
print(f"DEBUG - Error converting priority '{priority}': {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid priority: {priority}. Valid priorities: {[p.value for p in TicketPriority]}"
)
print("DEBUG - Starting ticket creation...")
# Create tickets directory
tickets_dir = Path(settings.UPLOAD_PATH) / "tickets" / str(current_user.id)
tickets_dir.mkdir(parents=True, exist_ok=True)
print(f"DEBUG - Created tickets directory: {tickets_dir}")
screenshot_path = None
if screenshot:
print(f"DEBUG - Processing screenshot: {screenshot.filename}, content_type: {screenshot.content_type}")
# Validate file type
if not screenshot.content_type.startswith('image/'):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Screenshot must be an image file"
)
# Generate unique filename
file_extension = screenshot.filename.split(".")[-1]
filename = f"ticket_{uuid.uuid4()}.{file_extension}"
file_path = tickets_dir / filename
# Save and optimize image
contents = await screenshot.read()
with open(file_path, "wb") as f:
f.write(contents)
# Optimize image
try:
img = Image.open(file_path)
if img.size[0] > 1920 or img.size[1] > 1080:
img.thumbnail((1920, 1080), Image.Resampling.LANCZOS)
img.save(file_path, quality=85, optimize=True)
except Exception as e:
print(f"Error optimizing screenshot: {e}")
screenshot_path = f"/tickets/{current_user.id}/{filename}"
print(f"DEBUG - Screenshot saved: {screenshot_path}")
print("DEBUG - Creating ticket object...")
ticket = Ticket(
title=title,
description=description,
ticket_type=ticket_type_enum,
priority=priority_enum,
user_id=current_user.id,
screenshot_path=screenshot_path
)
print("DEBUG - Adding ticket to database...")
db.add(ticket)
db.commit()
db.refresh(ticket)
print(f"DEBUG - Ticket created successfully with ID: {ticket.id}")
return format_ticket_response(ticket, db)
@router.put("/{ticket_id}", response_model=TicketResponse)
async def update_ticket(
ticket_id: int,
ticket_update: TicketUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Update a ticket."""
ticket = db.query(Ticket).filter(Ticket.id == ticket_id).first()
if not ticket:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Ticket not found"
)
# Users can only update their own tickets, admins can update all
if ticket.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to update this ticket"
)
# Regular users can only update title, description, and type
if not current_user.is_admin:
update_data = ticket_update.dict(exclude_unset=True)
allowed_fields = ['title', 'description', 'ticket_type']
update_data = {k: v for k, v in update_data.items() if k in allowed_fields}
else:
update_data = ticket_update.dict(exclude_unset=True)
# Update resolved_at if status is resolved
if 'status' in update_data and update_data['status'] == TicketStatus.RESOLVED:
update_data['resolved_at'] = datetime.utcnow()
for field, value in update_data.items():
setattr(ticket, field, value)
ticket.updated_at = datetime.utcnow()
db.commit()
db.refresh(ticket)
return format_ticket_response(ticket, db)
@router.put("/{ticket_id}/admin", response_model=TicketResponse)
async def update_ticket_admin(
ticket_id: int,
ticket_update: TicketAdminUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_admin_user)
):
"""Update a ticket as admin."""
ticket = db.query(Ticket).filter(Ticket.id == ticket_id).first()
if not ticket:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Ticket not found"
)
update_data = ticket_update.dict(exclude_unset=True)
# Update resolved_at if status is resolved
if 'status' in update_data and update_data['status'] == TicketStatus.RESOLVED:
update_data['resolved_at'] = datetime.utcnow()
for field, value in update_data.items():
setattr(ticket, field, value)
ticket.updated_at = datetime.utcnow()
db.commit()
db.refresh(ticket)
return format_ticket_response(ticket, db)
@router.delete("/{ticket_id}")
async def delete_ticket(
ticket_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Delete a ticket."""
ticket = db.query(Ticket).filter(Ticket.id == ticket_id).first()
if not ticket:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Ticket not found"
)
# Users can only delete their own tickets, admins can delete all
if ticket.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to delete this ticket"
)
# Delete screenshot if exists
if ticket.screenshot_path:
try:
screenshot_file = settings.UPLOAD_PATH + ticket.screenshot_path
if os.path.exists(screenshot_file):
os.remove(screenshot_file)
except Exception as e:
print(f"Error deleting screenshot: {e}")
db.delete(ticket)
db.commit()
return {"message": "Ticket deleted successfully"}
def format_ticket_response(ticket: Ticket, db: Session) -> dict:
"""Format ticket response with user information."""
user = db.query(User).filter(User.id == ticket.user_id).first()
assigned_admin = None
if ticket.assigned_to:
assigned_admin = db.query(User).filter(User.id == ticket.assigned_to).first()
return {
"id": ticket.id,
"title": ticket.title,
"description": ticket.description,
"ticket_type": ticket.ticket_type,
"status": ticket.status,
"priority": ticket.priority,
"user_id": ticket.user_id,
"assigned_to": ticket.assigned_to,
"screenshot_path": ticket.screenshot_path,
"admin_notes": ticket.admin_notes,
"created_at": ticket.created_at,
"updated_at": ticket.updated_at,
"resolved_at": ticket.resolved_at,
"user_name": user.full_name if user else "Unknown",
"user_email": user.email if user else "Unknown",
"assigned_admin_name": assigned_admin.full_name if assigned_admin else None
}