#!/usr/bin/env python3 """ Redeem a QR code and attribute points to a team. """ import os from fastapi import APIRouter, Depends, Request, Form, HTTPException from fastapi.responses import HTMLResponse, RedirectResponse from sqlalchemy.orm import Session from datetime import datetime from ..db import SessionLocal from ..models import QRCode, User, Team, TeamMembership, TeamAchievement from ..templates_config import templates from ..league_context import get_default_league, qr_code_league_id router = APIRouter() # Get base URL from environment variable or use default BASE_URL = os.environ.get("LEAGUELEDGER_BASE_URL", "https://rover.leagueledger.net") def get_db(): db = SessionLocal() try: yield db finally: db.close() @router.get("/{code}", response_class=HTMLResponse) def redeem_code(code: str, request: Request, db: Session = Depends(get_db)): """ Display a page to let the user choose which team to apply points to. No login required. """ # Get user from session for navbar user = None user_id = request.session.get("user_id") if user_id: user = db.query(User).get(user_id) # Find the QR code record qr_code = db.query(QRCode).filter_by(code=code).first() # Handle invalid or already used codes if not qr_code: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Invalid Code", "error_message": "This QR code is invalid or does not exist.", "user": user # Add user to the context } ) if qr_code.used and (not qr_code.max_uses or qr_code.max_uses <= 1): return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Code Already Used", "error_message": "This QR code has already been redeemed.", "user": user # Add user to the context } ) # Check expiration if applicable if qr_code.expires_at and qr_code.expires_at < datetime.now(): return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Expired Code", "error_message": "This QR code has expired and can no longer be redeemed.", "user": user # Add user to the context } ) effective_league_id = qr_code_league_id(qr_code) if not effective_league_id: effective_league_id = get_default_league(db).id # Get only teams the user is a member of in the QR code's league, if logged in user_teams = [] if user: # Query teams where the user is a member using TeamMembership relation user_teams = ( db.query(Team) .join(TeamMembership, Team.id == TeamMembership.team_id) .filter( TeamMembership.user_id == user.id, Team.league_id == effective_league_id ) .all() ) # If user is not logged in or has no teams, instruct them to log in or join teams if not user: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Login Required", "error_message": "You must be logged in to redeem QR codes. Please log in and try again.", "user": None } ) elif not user_teams: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "No Teams Available", "error_message": "You are not a member of any teams in this league. Please join or create a team before redeeming this QR code.", "user": user } ) return templates.TemplateResponse("redeem.html", { "request": request, "ticket": qr_code, # Using the same template variable name for compatibility "user_teams": user_teams, "has_achievement": bool(qr_code.achievement_name), "base_url": BASE_URL, "user": user # Add user to the context }) @router.get("/scan", response_class=HTMLResponse) def scan_qr_code(request: Request, db: Session = Depends(get_db)): """ Display the QR code scanner page """ # Get user from session for navbar user = None user_id = request.session.get("user_id") if user_id: user = db.query(User).get(user_id) return templates.TemplateResponse("scan_qr.html", { "request": request, "user": user # Add user to the context }) @router.post("/apply/{code}") async def apply_code( request: Request, code: str, db: Session = Depends(get_db) ): """ Apply the QR code to a selected team and award points and/or achievements """ # Get user from session for navbar user = None user_id = request.session.get("user_id") if user_id: user = db.query(User).get(user_id) else: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Login Required", "error_message": "You must be logged in to redeem QR codes.", "user": None } ) # Get form data form_data = await request.form() team_id = int(form_data.get("team_id", 0)) if team_id <= 0: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Team Selection Required", "error_message": "Please select a team to redeem this code.", "user": user # Add user to the context } ) # Get the QR code qr_code = db.query(QRCode).filter_by(code=code).first() if not qr_code: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Invalid Code", "error_message": "This QR code is invalid or does not exist.", "user": user # Add user to the context } ) # Check if the code is already used (for single-use codes) if qr_code.used and (not qr_code.max_uses or qr_code.max_uses <= 1): return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Code Already Used", "error_message": "This QR code has already been redeemed.", "user": user # Add user to the context } ) # Check expiration if applicable if qr_code.expires_at and qr_code.expires_at < datetime.now(): return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Expired Code", "error_message": "This QR code has expired and can no longer be redeemed.", "user": user # Add user to the context } ) # Verify the team exists team = db.query(Team).filter_by(id=team_id).first() if not team: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Team Not Found", "error_message": "The selected team could not be found.", "user": user # Add user to the context } ) effective_league_id = qr_code_league_id(qr_code) if not effective_league_id: effective_league_id = get_default_league(db).id if team.league_id != effective_league_id: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Wrong League", "error_message": "This QR code can only be redeemed by a team in its league.", "user": user } ) # Verify the user is a member of the selected team is_team_member = db.query(TeamMembership).filter_by( user_id=user.id, team_id=team.id ).first() is not None if not is_team_member: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Not a Team Member", "error_message": "You can only redeem points for teams you are a member of.", "user": user # Add user to the context } ) # Mark the QR code as redeemed qr_code.league_id = effective_league_id qr_code.redeemed_by = user.id qr_code.redeemed_at_team = team.id qr_code.redeemed_at = datetime.now() qr_code.used = True # Handle achievements if present if qr_code.achievement_name: achievement = TeamAchievement( team_id=team.id, name=qr_code.achievement_name, description=qr_code.description, event_id=qr_code.event_id, qr_code_id=qr_code.id, achieved_at=datetime.now() ) db.add(achievement) db.commit() # Return the success page with appropriate information return templates.TemplateResponse( "redeem_success.html", { "request": request, "points": qr_code.points, "achievement": qr_code.achievement_name if qr_code.achievement_name else None, "team": team, "event": qr_code.event if qr_code.event else None, "base_url": BASE_URL, "user": user # Add user to the context } ) @router.post("/manual") async def manual_code_entry( request: Request, code: str = Form(...), db: Session = Depends(get_db) ): """ Handle manual code entry from the form. This redirects to the normal redeem flow after validating the code. """ # Get user from session for navbar user = None user_id = request.session.get("user_id") if user_id: user = db.query(User).get(user_id) # Check if the code exists qr_code = db.query(QRCode).filter_by(code=code).first() if not qr_code: return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Invalid Code", "error_message": "The code you entered is invalid or does not exist.", "user": user # Add user to the context } ) # Check if already used (for single-use codes) if qr_code.used and (not qr_code.max_uses or qr_code.max_uses <= 1): return templates.TemplateResponse( "error.html", { "request": request, "error_title": "Code Already Used", "error_message": "This code has already been redeemed.", "user": user # Add user to the context } ) # Redirect to the regular redeem flow return RedirectResponse(f"/redeem/{code}", status_code=303)