296 lines
11 KiB
Python
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
|
|
}
|