Moved to S3

This commit is contained in:
2025-12-16 01:25:21 +07:00
parent c7966656d8
commit 87ecd9756c
15 changed files with 446 additions and 56 deletions

View File

@@ -3,6 +3,7 @@ Assignment details and dispute system endpoints.
"""
from datetime import datetime, timedelta
from fastapi import APIRouter, HTTPException
from fastapi.responses import Response
from sqlalchemy import select
from sqlalchemy.orm import selectinload
@@ -17,6 +18,7 @@ from app.schemas import (
MessageResponse, ChallengeResponse, GameShort, ReturnedAssignmentResponse,
)
from app.schemas.user import UserPublic
from app.services.storage import storage_service
router = APIRouter(tags=["assignments"])
@@ -133,10 +135,7 @@ async def get_assignment_detail(
can_dispute = time_since_completion < timedelta(hours=DISPUTE_WINDOW_HOURS)
# Build proof URLs
proof_image_url = None
if assignment.proof_path:
# Extract filename from path
proof_image_url = f"/uploads/proofs/{assignment.proof_path.split('/')[-1]}"
proof_image_url = storage_service.get_url(assignment.proof_path, "proofs")
return AssignmentDetailResponse(
id=assignment.id,
@@ -153,7 +152,7 @@ async def get_assignment_detail(
game=GameShort(
id=game.id,
title=game.title,
cover_url=f"/uploads/covers/{game.cover_path.split('/')[-1]}" if game.cover_path else None,
cover_url=storage_service.get_url(game.cover_path, "covers"),
),
is_generated=challenge.is_generated,
created_at=challenge.created_at,
@@ -172,6 +171,58 @@ async def get_assignment_detail(
)
@router.get("/assignments/{assignment_id}/proof-image")
async def get_assignment_proof_image(
assignment_id: int,
current_user: CurrentUser,
db: DbSession,
):
"""Stream the proof image for an assignment"""
# Get assignment
result = await db.execute(
select(Assignment)
.options(
selectinload(Assignment.challenge).selectinload(Challenge.game),
)
.where(Assignment.id == assignment_id)
)
assignment = result.scalar_one_or_none()
if not assignment:
raise HTTPException(status_code=404, detail="Assignment not found")
# Check user is participant of the marathon
marathon_id = assignment.challenge.game.marathon_id
result = await db.execute(
select(Participant).where(
Participant.user_id == current_user.id,
Participant.marathon_id == marathon_id,
)
)
participant = result.scalar_one_or_none()
if not participant:
raise HTTPException(status_code=403, detail="You are not a participant of this marathon")
# Check if proof exists
if not assignment.proof_path:
raise HTTPException(status_code=404, detail="No proof image for this assignment")
# Get file from storage
file_data = await storage_service.get_file(assignment.proof_path, "proofs")
if not file_data:
raise HTTPException(status_code=404, detail="Proof image not found in storage")
content, content_type = file_data
return Response(
content=content,
media_type=content_type,
headers={
"Cache-Control": "public, max-age=31536000",
}
)
@router.post("/assignments/{assignment_id}/dispute", response_model=DisputeResponse)
async def create_dispute(
assignment_id: int,
@@ -421,7 +472,7 @@ async def get_returned_assignments(
game=GameShort(
id=a.challenge.game.id,
title=a.challenge.game.title,
cover_url=f"/uploads/covers/{a.challenge.game.cover_path.split('/')[-1]}" if a.challenge.game.cover_path else None,
cover_url=storage_service.get_url(a.challenge.game.cover_path, "covers"),
),
is_generated=a.challenge.is_generated,
created_at=a.challenge.created_at,