Files
gh-christianlouis-leagueledger/app/views/teams/actions.py
T
Christian Krakau-Louis 15e1dc227b Add multi-league foundation
2026-05-22 20:47:28 +02:00

507 lines
17 KiB
Python

"""Team-related actions for team management"""
from fastapi import Request, Depends, Form, HTTPException, BackgroundTasks
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session
import secrets
from starlette.status import HTTP_303_SEE_OTHER
from fastapi.templating import Jinja2Templates
from ...models import Team, TeamMembership, User, TeamJoinRequest
from ...league_context import resolve_selected_league
from ...utils.auth import get_current_user
from ...utils.mail import send_team_join_request_notification, send_join_request_response
from ...templates_config import templates
from .routes import get_db
from . import utils
async def create_team_post(
request: Request,
name: str = Form(...),
description: str = Form(""),
logo_url: str = Form(""),
is_open: bool = Form(False),
league_id: int = Form(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""Handle team creation form submission"""
if not current_user:
return RedirectResponse("/auth/login", status_code=HTTP_303_SEE_OTHER)
selected_league = resolve_selected_league(db, league_id)
# Check if team name already exists in this league
existing_team = db.query(Team).filter(
Team.name == name,
Team.league_id == selected_league.id
).first()
if existing_team:
return RedirectResponse(
f"/teams/?league_id={selected_league.id}&error=Team+name+already+exists",
status_code=HTTP_303_SEE_OTHER
)
# Create team with only the fields that exist in the model
team_data = {
"name": name,
"league_id": selected_league.id,
"description": description,
"is_open": is_open,
"owner_id": current_user.id
}
# Only add logo_url if it exists in the Team model
from sqlalchemy import inspect
team_columns = [c.key for c in inspect(Team).columns]
if "logo_url" in team_columns:
team_data["logo_url"] = logo_url
team = Team(**team_data)
db.add(team)
db.commit()
db.refresh(team)
# Make the user an admin and captain of the team
team_membership = TeamMembership(
user_id=current_user.id,
team_id=team.id,
is_admin=True,
is_captain=True
)
db.add(team_membership)
db.commit()
return RedirectResponse(f"/teams/?league_id={selected_league.id}", status_code=HTTP_303_SEE_OTHER)
async def edit_team_post(
request: Request,
team_id: int,
name: str = Form(...),
description: str = Form(""),
logo_url: str = Form(""),
is_open: bool = Form(False),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""Handle team edit form submission"""
if not current_user:
return RedirectResponse("/auth/login", status_code=HTTP_303_SEE_OTHER)
team = db.query(Team).filter(Team.id == team_id).first()
if not team:
raise HTTPException(status_code=404, detail="Team not found")
# Check if user is admin
membership = db.query(TeamMembership).filter_by(
team_id=team.id,
user_id=current_user.id,
is_admin=True
).first()
if not membership:
raise HTTPException(status_code=403, detail="You don't have permission to update this team")
# Update team details
team.name = name
team.description = description
team.logo_url = logo_url
team.is_open = is_open
db.commit()
return RedirectResponse(f"/teams/{team_id}", status_code=HTTP_303_SEE_OTHER)
async def join_team_request(
request: Request,
team_id: int,
background_tasks: BackgroundTasks,
message: str = Form(""),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""Process join team request"""
if not current_user:
return RedirectResponse("/auth/login", status_code=HTTP_303_SEE_OTHER)
# Check if team exists
team = db.query(Team).filter(Team.id == team_id, Team.is_active == True).first()
if not team:
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Team not found"}
)
# Check if user is already a member
existing_membership = db.query(TeamMembership).filter(
TeamMembership.team_id == team_id,
TeamMembership.user_id == current_user.id
).first()
if existing_membership:
return RedirectResponse(f"/teams/{team_id}", status_code=HTTP_303_SEE_OTHER)
# Open team - directly add the user
if team.is_open:
# Add user to team
new_member = TeamMembership(
team_id=team_id,
user_id=current_user.id,
is_admin=False,
is_captain=False
)
db.add(new_member)
db.commit()
return RedirectResponse(
f"/teams/{team_id}?message=You+have+joined+the+team+successfully",
status_code=HTTP_303_SEE_OTHER
)
# Closed team - create join request
# Check for existing pending request
existing_request = db.query(TeamJoinRequest).filter(
TeamJoinRequest.team_id == team_id,
TeamJoinRequest.user_id == current_user.id,
TeamJoinRequest.status == "pending"
).first()
if existing_request:
return RedirectResponse(
f"/teams/{team_id}?message=Your+join+request+is+pending+approval",
status_code=HTTP_303_SEE_OTHER
)
# Create request token
request_token = secrets.token_urlsafe(32)
# Create join request
join_request = TeamJoinRequest(
team_id=team_id,
user_id=current_user.id,
message=message,
request_token=request_token
)
db.add(join_request)
db.commit()
# Get team captains to notify - Updated to use TeamMembership instead of TeamMember
captains = db.query(User).join(TeamMembership).filter(
TeamMembership.team_id == team_id,
TeamMembership.is_captain == True
).all()
if not captains:
print("No captains found for the team. Unable to send notifications.")
else:
# Send email notifications to all captains
for captain in captains:
try:
await send_team_join_request_notification(
captain_email=captain.email,
captain_name=captain.username,
requester_name=current_user.username,
team_name=team.name,
message=message,
approval_token=request_token,
background_tasks=background_tasks
)
# Log success
print(f"Team join request notification sent to {captain.email}")
except Exception as e:
# Log the error but continue
print(f"Failed to send notification to {captain.email}: {str(e)}")
return RedirectResponse(
f"/teams/{team_id}?message=Your+join+request+has+been+submitted+for+approval",
status_code=HTTP_303_SEE_OTHER
)
def direct_join_team(request: Request, team_id: int, db: Session = Depends(get_db)):
"""Handle direct team join requests"""
# Check if user is logged in
user_id = request.session.get("user_id")
if not user_id:
return RedirectResponse("/auth/login?next=/teams", status_code=303)
# Get user
user = db.query(User).get(user_id)
if not user:
return RedirectResponse("/auth/login", status_code=303)
# Find the team
team = db.query(Team).filter_by(id=team_id).first()
if not team:
return RedirectResponse("/teams/?error=Team+not+found", status_code=303)
# Check if membership exists
existing = db.query(TeamMembership)\
.filter_by(user_id=user_id, team_id=team.id)\
.first()
if existing:
return RedirectResponse("/teams/?error=You+are+already+a+member+of+this+team", status_code=303)
# Check if team is closed (not open)
if not team.is_open:
# For closed teams, redirect to the join request page
return RedirectResponse(f"/teams/{team_id}/join", status_code=303)
# Create membership for open teams
new_member = TeamMembership(
user_id=user_id,
team_id=team.id,
is_admin=False,
is_captain=False
)
db.add(new_member)
db.commit()
# Redirect to the team detail page
return RedirectResponse(f"/teams/{team_id}", status_code=303)
def leave_team(request: Request, team_id: int, db: Session = Depends(get_db)):
"""Allow a user to leave a team"""
# Check if user is logged in
user_id = request.session.get("user_id")
if not user_id:
return RedirectResponse("/auth/login?next=/teams", status_code=303)
# Get team
team = db.query(Team).filter_by(id=team_id).first()
if not team:
raise HTTPException(status_code=404, detail="Team not found")
# Can't leave if you're the owner
if team.owner_id == user_id:
return RedirectResponse(f"/teams/{team_id}?error=Team+owner+cannot+leave", status_code=303)
# Find membership
membership = db.query(TeamMembership)\
.filter(TeamMembership.user_id == user_id, TeamMembership.team_id == team_id)\
.first()
if not membership:
return RedirectResponse("/teams/?error=You+are+not+a+member+of+this+team", status_code=303)
# Delete the team membership
db.delete(membership)
db.commit()
return RedirectResponse("/teams/?message=Successfully+left+the+team", status_code=303)
def update_team(
request: Request,
team_id: int,
team_name: str = Form(...),
is_public: bool = Form(False),
db: Session = Depends(get_db)
):
"""Update team details."""
team = db.query(Team).filter_by(id=team_id).first()
if not team:
raise HTTPException(status_code=404, detail="Team not found")
# Check if user is admin
membership = db.query(TeamMembership).filter_by(
team_id=team.id,
user_id=request.session.get("user_id"),
is_admin=True
).first()
if not membership:
raise HTTPException(status_code=403, detail="You don't have permission to update this team")
# Update team details
team.name = team_name
team.is_public = is_public
db.commit()
return RedirectResponse(f"/teams/{team_id}", status_code=303)
async def approve_join_request(request: Request, token: str, db: Session = Depends(get_db)):
"""Approve a team join request using the provided token"""
# Find the join request by token
join_request = db.query(TeamJoinRequest).filter_by(request_token=token).first()
if not join_request:
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Invalid or expired join request"}
)
if join_request.status != "pending":
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "This request has already been processed"}
)
# Get the team
team = db.query(Team).filter_by(id=join_request.team_id).first()
if not team:
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Team not found"}
)
# Check if the user has permission to approve requests
# Get user from session
user_id = request.session.get("user_id")
if not user_id:
return RedirectResponse("/auth/login", status_code=303)
user = db.query(User).get(user_id)
if not user:
return RedirectResponse("/auth/login", status_code=303)
# Check if user is admin, owner or captain
is_user_admin, is_user_owner, is_captain = utils.check_user_permissions(db, user, join_request.team_id, team)
if not (is_user_admin or is_user_owner or is_captain):
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "You don't have permission to approve join requests"}
)
# Get the user who requested to join
requester = db.query(User).filter_by(id=join_request.user_id).first()
if not requester:
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Requesting user not found"}
)
# Check if user is already a member
existing_membership = db.query(TeamMembership).filter(
TeamMembership.team_id == join_request.team_id,
TeamMembership.user_id == join_request.user_id
).first()
if existing_membership:
# Update request status
join_request.status = "approved"
db.commit()
return templates.TemplateResponse(
"teams/request_processed.html",
{
"request": request,
"message": f"{requester.username} is already a member of {team.name}",
"team_id": team.id
}
)
# Create team membership
new_member = TeamMembership(
team_id=join_request.team_id,
user_id=join_request.user_id,
is_admin=False,
is_captain=False
)
db.add(new_member)
# Update request status
join_request.status = "approved"
db.commit()
# Send notification to the user (if email sending is available)
try:
background_tasks = BackgroundTasks()
await send_join_request_response(
user_email=requester.email,
user_name=requester.username,
team_name=team.name,
approved=True,
background_tasks=background_tasks
)
except Exception as e:
print(f"Failed to send approval notification: {str(e)}")
return templates.TemplateResponse(
"teams/request_processed.html",
{
"request": request,
"message": f"Successfully approved {requester.username}'s request to join {team.name}",
"team_id": team.id
}
)
async def deny_join_request(request: Request, token: str, db: Session = Depends(get_db)):
"""Deny a team join request using the provided token"""
# Find the join request by token
join_request = db.query(TeamJoinRequest).filter_by(request_token=token).first()
if not join_request:
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Invalid or expired join request"}
)
if join_request.status != "pending":
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "This request has already been processed"}
)
# Get the team
team = db.query(Team).filter_by(id=join_request.team_id).first()
if not team:
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Team not found"}
)
# Check if the user has permission to deny requests
# Get user from session
user_id = request.session.get("user_id")
if not user_id:
return RedirectResponse("/auth/login", status_code=303)
user = db.query(User).get(user_id)
if not user:
return RedirectResponse("/auth/login", status_code=303)
# Check if user is admin, owner or captain
is_user_admin, is_user_owner, is_captain = utils.check_user_permissions(db, user, join_request.team_id, team)
if not (is_user_admin or is_user_owner or is_captain):
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "You don't have permission to deny join requests"}
)
# Get the user who requested to join
requester = db.query(User).filter_by(id=join_request.user_id).first()
if not requester:
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Requesting user not found"}
)
# Update request status
join_request.status = "denied"
db.commit()
# Send notification to the user (if email sending is available)
try:
background_tasks = BackgroundTasks()
await send_join_request_response(
user_email=requester.email,
user_name=requester.username,
team_name=team.name,
approved=False,
background_tasks=background_tasks
)
except Exception as e:
print(f"Failed to send denial notification: {str(e)}")
return templates.TemplateResponse(
"teams/request_processed.html",
{
"request": request,
"message": f"Successfully denied {requester.username}'s request to join {team.name}",
"team_id": team.id
}
)