dikasterion/backend/app/routers/cases.py

179 lines
5.7 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_
from datetime import datetime, timedelta
from typing import List
import random
from app.database import get_db
from app.models import User, Case, JudgeAssignment
from app.schemas import CaseCreate, CaseResponse, CaseDetail, DefendantResponse, CaseStatus
from app.routers.auth import get_current_active_user
from app.utils.notifications import notify_user
router = APIRouter()
async def generate_case_number(db: AsyncSession) -> str:
"""Generate case number in format DIK-YYYY-NNNN"""
year = datetime.now().year
result = await db.execute(
select(Case).where(Case.case_number.like(f"DIK-{year}-%")).order_by(Case.id.desc())
)
last_case = result.scalar_one_or_none()
if last_case:
last_num = int(last_case.case_number.split("-")[-1])
new_num = last_num + 1
else:
new_num = 1
return f"DIK-{year}-{new_num:04d}"
async def assign_judges(db: AsyncSession, case_id: int, exclude_user_ids: List[int]):
"""Assign 3 random judges from pool, excluding plaintiff and defendant"""
result = await db.execute(
select(User).where(
and_(
User.is_judge == True,
User.is_active == True,
User.id.notin_(exclude_user_ids)
)
)
)
available_judges = result.scalars().all()
if len(available_judges) < 3:
raise HTTPException(
status_code=400,
detail="Not enough judges available in pool"
)
selected_judges = random.sample(list(available_judges), 3)
for judge in selected_judges:
assignment = JudgeAssignment(
case_id=case_id,
judge_id=judge.id,
status="pending"
)
db.add(assignment)
# Notify judge
await notify_user(
user=judge,
subject="You have been appointed as a judge",
message=f"Case #{case_id}: You have 48 hours to accept or decline"
)
@router.post("/", response_model=CaseResponse)
async def create_case(
case: CaseCreate,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
# Find defendant
result = await db.execute(select(User).where(User.username == case.defendant_username))
defendant = result.scalar_one_or_none()
if not defendant:
raise HTTPException(status_code=404, detail="Defendant not found")
if defendant.id == current_user.id:
raise HTTPException(status_code=400, detail="Cannot sue yourself")
# Generate case number
case_number = await generate_case_number(db)
# Create case
db_case = Case(
case_number=case_number,
plaintiff_id=current_user.id,
defendant_id=defendant.id,
title=case.title,
description=case.description,
evidence_urls=case.evidence_urls,
status="pending",
)
db.add(db_case)
await db.commit()
await db.refresh(db_case)
# Assign judges
await assign_judges(db, db_case.id, [current_user.id, defendant.id])
await db.commit()
# Notify defendant
background_tasks.add_task(
notify_user,
user=defendant,
subject=f"You have been sued: {case.title}",
message=f"Case #{case_number}: {current_user.username} has filed a case against you. Please accept or decline jurisdiction."
)
return db_case
@router.get("/", response_model=List[CaseResponse])
async def list_my_cases(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_active_user),
skip: int = 0,
limit: int = 20
):
"""List cases where user is plaintiff, defendant, or judge"""
result = await db.execute(
select(Case).where(
(Case.plaintiff_id == current_user.id) |
(Case.defendant_id == current_user.id) |
(Case.judge_assignments.any(JudgeAssignment.judge_id == current_user.id))
).offset(skip).limit(limit).order_by(Case.created_at.desc())
)
return result.scalars().all()
@router.get("/{case_id}", response_model=CaseDetail)
async def get_case(
case_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
result = await db.execute(select(Case).where(Case.id == case_id))
case = result.scalar_one_or_none()
if not case:
raise HTTPException(status_code=404, detail="Case not found")
return case
@router.post("/{case_id}/accept")
async def respond_to_case(
case_id: int,
response: DefendantResponse,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
result = await db.execute(select(Case).where(Case.id == case_id))
case = result.scalar_one_or_none()
if not case:
raise HTTPException(status_code=404, detail="Case not found")
if case.defendant_id != current_user.id:
raise HTTPException(status_code=403, detail="Only defendant can respond")
if case.status != "pending":
raise HTTPException(status_code=400, detail="Case is no longer pending")
if response.accept_jurisdiction:
case.status = "hearing"
case.hearing_deadline = datetime.now() + timedelta(hours=72)
case.defendant_response = response.response_text
else:
case.status = "declined"
await db.commit()
return {
"status": "accepted" if response.accept_jurisdiction else "declined",
"case_id": case_id,
"next_deadline": case.hearing_deadline if response.accept_jurisdiction else None
}