initial commit - LeDiscord plateforme des copains
This commit is contained in:
295
backend/api/routers/tickets.py
Normal file
295
backend/api/routers/tickets.py
Normal file
@@ -0,0 +1,295 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user