Automated Action 9e56bda916 Implement Multimodal Ticketing System with FastAPI and SQLite
This commit includes:
- Project structure and FastAPI setup
- SQLAlchemy models for users, vehicles, schedules, and tickets
- Alembic migrations
- User authentication and management
- Vehicle and schedule management
- Ticket purchase and cancellation with time restrictions
- Comprehensive API documentation
2025-06-17 11:08:42 +00:00

238 lines
6.2 KiB
Python

from datetime import datetime
from typing import Any, List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.core.auth import get_current_active_user
from app.db.session import get_db
from app.models.ticket import Ticket, TicketStatus
from app.models.user import User
from app.models.vehicle import Schedule, Vehicle, VehicleType
from app.schemas.ticket import (
Ticket as TicketSchema,
)
from app.schemas.ticket import (
TicketCreate,
)
from app.services.ticket_service import (
assign_seat_number,
generate_ticket_number,
validate_cancellation_time,
validate_purchase_time,
)
router = APIRouter()
@router.post("/", response_model=TicketSchema, status_code=status.HTTP_201_CREATED)
def create_ticket(
*,
db: Session = Depends(get_db),
ticket_in: TicketCreate,
current_user: User = Depends(get_current_active_user),
) -> Any:
"""
Purchase a ticket.
"""
# Check if schedule exists and is active
schedule = db.query(Schedule).join(Vehicle).filter(
Schedule.id == ticket_in.schedule_id,
Schedule.is_active == True,
Vehicle.is_active == True
).first()
if not schedule:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Schedule not found",
)
# Check if there are available seats
if schedule.available_seats <= 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No available seats for this schedule",
)
# Validate purchase time (at least 10 minutes before departure)
validate_purchase_time(schedule)
# For trains, assign a seat number
vehicle_type = schedule.vehicle.vehicle_type
if vehicle_type == VehicleType.TRAIN:
seat_number = assign_seat_number(db, schedule, vehicle_type)
else:
seat_number = ticket_in.seat_number
# Create ticket
ticket = Ticket(
user_id=current_user.id,
schedule_id=ticket_in.schedule_id,
seat_number=seat_number,
purchase_time=datetime.utcnow(),
status=TicketStatus.ACTIVE,
is_active=True,
ticket_number=generate_ticket_number(),
)
# Update available seats in schedule
schedule.available_seats -= 1
db.add(ticket)
db.add(schedule)
db.commit()
db.refresh(ticket)
return ticket
@router.get("/", response_model=List[TicketSchema])
def list_tickets(
*,
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
status: Optional[TicketStatus] = None,
current_user: User = Depends(get_current_active_user),
) -> Any:
"""
Retrieve all tickets for the current user.
"""
query = db.query(Ticket).filter(
Ticket.user_id == current_user.id,
Ticket.is_active == True
)
if status:
query = query.filter(Ticket.status == status)
tickets = query.offset(skip).limit(limit).all()
return tickets
@router.get("/active", response_model=List[TicketSchema])
def list_active_tickets(
*,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user),
) -> Any:
"""
Retrieve all active tickets for the current user.
"""
tickets = db.query(Ticket).filter(
Ticket.user_id == current_user.id,
Ticket.status == TicketStatus.ACTIVE,
Ticket.is_active == True
).all()
return tickets
@router.get("/history", response_model=List[TicketSchema])
def list_ticket_history(
*,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user),
) -> Any:
"""
Retrieve ticket history (used, cancelled, expired) for the current user.
"""
tickets = db.query(Ticket).filter(
Ticket.user_id == current_user.id,
Ticket.status != TicketStatus.ACTIVE,
Ticket.is_active == True
).all()
return tickets
@router.get("/{ticket_id}", response_model=TicketSchema)
def get_ticket(
*,
db: Session = Depends(get_db),
ticket_id: int,
current_user: User = Depends(get_current_active_user),
) -> Any:
"""
Get ticket by ID.
"""
ticket = db.query(Ticket).filter(
Ticket.id == ticket_id,
Ticket.user_id == current_user.id,
Ticket.is_active == True
).first()
if not ticket:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Ticket not found",
)
return ticket
@router.get("/by-ticket-number/{ticket_number}", response_model=TicketSchema)
def get_ticket_by_number(
*,
db: Session = Depends(get_db),
ticket_number: str,
current_user: User = Depends(get_current_active_user),
) -> Any:
"""
Get ticket by ticket number.
"""
ticket = db.query(Ticket).filter(
Ticket.ticket_number == ticket_number,
Ticket.user_id == current_user.id,
Ticket.is_active == True
).first()
if not ticket:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Ticket not found",
)
return ticket
@router.put("/{ticket_id}/cancel", response_model=TicketSchema)
def cancel_ticket(
*,
db: Session = Depends(get_db),
ticket_id: int,
current_user: User = Depends(get_current_active_user),
) -> Any:
"""
Cancel a ticket.
"""
ticket = db.query(Ticket).filter(
Ticket.id == ticket_id,
Ticket.user_id == current_user.id,
Ticket.is_active == True,
Ticket.status == TicketStatus.ACTIVE
).first()
if not ticket:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Active ticket not found",
)
# Validate cancellation time (at least 3 minutes before departure)
validate_cancellation_time(ticket)
# Update ticket status
ticket.status = TicketStatus.CANCELLED
# Increment available seats in schedule
schedule = db.query(Schedule).filter(Schedule.id == ticket.schedule_id).first()
schedule.available_seats += 1
db.add(ticket)
db.add(schedule)
db.commit()
db.refresh(ticket)
return ticket