Add 3 roles, settings for marathons
This commit is contained in:
@@ -2,8 +2,8 @@ from fastapi import APIRouter, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.api.deps import DbSession, CurrentUser
|
||||
from app.models import Marathon, MarathonStatus, Game, Challenge, Participant
|
||||
from app.api.deps import DbSession, CurrentUser, require_participant, require_organizer, get_participant
|
||||
from app.models import Marathon, MarathonStatus, Game, GameStatus, Challenge
|
||||
from app.schemas import (
|
||||
ChallengeCreate,
|
||||
ChallengeUpdate,
|
||||
@@ -33,21 +33,9 @@ async def get_challenge_or_404(db, challenge_id: int) -> Challenge:
|
||||
return challenge
|
||||
|
||||
|
||||
async def check_participant(db, user_id: int, marathon_id: int) -> Participant:
|
||||
result = await db.execute(
|
||||
select(Participant).where(
|
||||
Participant.user_id == 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")
|
||||
return participant
|
||||
|
||||
|
||||
@router.get("/games/{game_id}/challenges", response_model=list[ChallengeResponse])
|
||||
async def list_challenges(game_id: int, current_user: CurrentUser, db: DbSession):
|
||||
"""List challenges for a game. Participants can view challenges for approved games only."""
|
||||
# Get game and check access
|
||||
result = await db.execute(
|
||||
select(Game).where(Game.id == game_id)
|
||||
@@ -56,7 +44,16 @@ async def list_challenges(game_id: int, current_user: CurrentUser, db: DbSession
|
||||
if not game:
|
||||
raise HTTPException(status_code=404, detail="Game not found")
|
||||
|
||||
await check_participant(db, current_user.id, game.marathon_id)
|
||||
participant = await get_participant(db, current_user.id, game.marathon_id)
|
||||
|
||||
# Check access
|
||||
if not current_user.is_admin:
|
||||
if not participant:
|
||||
raise HTTPException(status_code=403, detail="You are not a participant of this marathon")
|
||||
# Regular participants can only see challenges for approved games or their own games
|
||||
if not participant.is_organizer:
|
||||
if game.status != GameStatus.APPROVED.value and game.proposed_by_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Game not accessible")
|
||||
|
||||
result = await db.execute(
|
||||
select(Challenge)
|
||||
@@ -91,6 +88,7 @@ async def create_challenge(
|
||||
current_user: CurrentUser,
|
||||
db: DbSession,
|
||||
):
|
||||
"""Create a challenge for a game. Organizers only."""
|
||||
# Get game and check access
|
||||
result = await db.execute(
|
||||
select(Game).where(Game.id == game_id)
|
||||
@@ -105,7 +103,12 @@ async def create_challenge(
|
||||
if marathon.status != MarathonStatus.PREPARING.value:
|
||||
raise HTTPException(status_code=400, detail="Cannot add challenges to active or finished marathon")
|
||||
|
||||
await check_participant(db, current_user.id, game.marathon_id)
|
||||
# Only organizers can add challenges
|
||||
await require_organizer(db, current_user, game.marathon_id)
|
||||
|
||||
# Can only add challenges to approved games
|
||||
if game.status != GameStatus.APPROVED.value:
|
||||
raise HTTPException(status_code=400, detail="Can only add challenges to approved games")
|
||||
|
||||
challenge = Challenge(
|
||||
game_id=game_id,
|
||||
@@ -141,7 +144,7 @@ async def create_challenge(
|
||||
|
||||
@router.post("/marathons/{marathon_id}/preview-challenges", response_model=ChallengesPreviewResponse)
|
||||
async def preview_challenges(marathon_id: int, current_user: CurrentUser, db: DbSession):
|
||||
"""Generate challenges preview for all games in marathon using GPT (without saving)"""
|
||||
"""Generate challenges preview for approved games in marathon using GPT (without saving). Organizers only."""
|
||||
# Check marathon
|
||||
result = await db.execute(select(Marathon).where(Marathon.id == marathon_id))
|
||||
marathon = result.scalar_one_or_none()
|
||||
@@ -151,16 +154,20 @@ async def preview_challenges(marathon_id: int, current_user: CurrentUser, db: Db
|
||||
if marathon.status != MarathonStatus.PREPARING.value:
|
||||
raise HTTPException(status_code=400, detail="Cannot generate challenges for active or finished marathon")
|
||||
|
||||
await check_participant(db, current_user.id, marathon_id)
|
||||
# Only organizers can generate challenges
|
||||
await require_organizer(db, current_user, marathon_id)
|
||||
|
||||
# Get all games
|
||||
# Get only APPROVED games
|
||||
result = await db.execute(
|
||||
select(Game).where(Game.marathon_id == marathon_id)
|
||||
select(Game).where(
|
||||
Game.marathon_id == marathon_id,
|
||||
Game.status == GameStatus.APPROVED.value,
|
||||
)
|
||||
)
|
||||
games = result.scalars().all()
|
||||
|
||||
if not games:
|
||||
raise HTTPException(status_code=400, detail="No games in marathon")
|
||||
raise HTTPException(status_code=400, detail="No approved games in marathon")
|
||||
|
||||
preview_challenges = []
|
||||
for game in games:
|
||||
@@ -202,7 +209,7 @@ async def save_challenges(
|
||||
current_user: CurrentUser,
|
||||
db: DbSession,
|
||||
):
|
||||
"""Save previewed challenges to database"""
|
||||
"""Save previewed challenges to database. Organizers only."""
|
||||
# Check marathon
|
||||
result = await db.execute(select(Marathon).where(Marathon.id == marathon_id))
|
||||
marathon = result.scalar_one_or_none()
|
||||
@@ -212,18 +219,22 @@ async def save_challenges(
|
||||
if marathon.status != MarathonStatus.PREPARING.value:
|
||||
raise HTTPException(status_code=400, detail="Cannot add challenges to active or finished marathon")
|
||||
|
||||
await check_participant(db, current_user.id, marathon_id)
|
||||
# Only organizers can save challenges
|
||||
await require_organizer(db, current_user, marathon_id)
|
||||
|
||||
# Verify all games belong to this marathon
|
||||
# Verify all games belong to this marathon AND are approved
|
||||
result = await db.execute(
|
||||
select(Game.id).where(Game.marathon_id == marathon_id)
|
||||
select(Game.id).where(
|
||||
Game.marathon_id == marathon_id,
|
||||
Game.status == GameStatus.APPROVED.value,
|
||||
)
|
||||
)
|
||||
valid_game_ids = set(row[0] for row in result.fetchall())
|
||||
|
||||
saved_count = 0
|
||||
for ch_data in data.challenges:
|
||||
if ch_data.game_id not in valid_game_ids:
|
||||
continue # Skip challenges for invalid games
|
||||
continue # Skip challenges for invalid/unapproved games
|
||||
|
||||
# Validate type
|
||||
ch_type = ch_data.type
|
||||
@@ -267,6 +278,7 @@ async def update_challenge(
|
||||
current_user: CurrentUser,
|
||||
db: DbSession,
|
||||
):
|
||||
"""Update a challenge. Organizers only."""
|
||||
challenge = await get_challenge_or_404(db, challenge_id)
|
||||
|
||||
# Check marathon is in preparing state
|
||||
@@ -275,7 +287,8 @@ async def update_challenge(
|
||||
if marathon.status != MarathonStatus.PREPARING.value:
|
||||
raise HTTPException(status_code=400, detail="Cannot update challenges in active or finished marathon")
|
||||
|
||||
await check_participant(db, current_user.id, challenge.game.marathon_id)
|
||||
# Only organizers can update challenges
|
||||
await require_organizer(db, current_user, challenge.game.marathon_id)
|
||||
|
||||
if data.title is not None:
|
||||
challenge.title = data.title
|
||||
@@ -316,6 +329,7 @@ async def update_challenge(
|
||||
|
||||
@router.delete("/challenges/{challenge_id}", response_model=MessageResponse)
|
||||
async def delete_challenge(challenge_id: int, current_user: CurrentUser, db: DbSession):
|
||||
"""Delete a challenge. Organizers only."""
|
||||
challenge = await get_challenge_or_404(db, challenge_id)
|
||||
|
||||
# Check marathon is in preparing state
|
||||
@@ -324,7 +338,8 @@ async def delete_challenge(challenge_id: int, current_user: CurrentUser, db: DbS
|
||||
if marathon.status != MarathonStatus.PREPARING.value:
|
||||
raise HTTPException(status_code=400, detail="Cannot delete challenges from active or finished marathon")
|
||||
|
||||
await check_participant(db, current_user.id, challenge.game.marathon_id)
|
||||
# Only organizers can delete challenges
|
||||
await require_organizer(db, current_user, challenge.game.marathon_id)
|
||||
|
||||
await db.delete(challenge)
|
||||
await db.commit()
|
||||
|
||||
Reference in New Issue
Block a user