Implement OAuth login with Authentik integration, update user management, and enhance team functionalities
- Added OAuth login functionality using Authentik, allowing users to log in via OpenID. - Updated user registration and login processes to handle OAuth users. - Enhanced team management features, including joining and leaving teams, and displaying user-specific team information. - Improved error handling and user feedback for team actions. - Added new database migrations for OAuth-related fields in the users table. - Updated templates to reflect changes in user authentication and team management. - Refactored dashboard and leaderboard views to include user context and team memberships.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
from fastapi import APIRouter, Request, Depends, Form, HTTPException, status
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from typing import Optional
|
||||
import secrets
|
||||
import os
|
||||
import uuid
|
||||
from starlette.status import HTTP_303_SEE_OTHER, HTTP_302_FOUND
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..db import get_db
|
||||
from ..models import User
|
||||
from ..auth.oauth import authentik_oauth
|
||||
from ..templates_config import templates
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["Auth"])
|
||||
|
||||
@router.get("/login", response_class=HTMLResponse)
|
||||
async def login_page(request: Request, error: Optional[str] = None, message: Optional[str] = None):
|
||||
"""Login page route"""
|
||||
return templates.TemplateResponse(
|
||||
"auth/login.html",
|
||||
{"request": request, "error": error, "message": message,
|
||||
"show_oauth": True, "oauth_provider_name": "Authentik"}
|
||||
)
|
||||
|
||||
@router.post("/login", response_class=HTMLResponse)
|
||||
async def login_post(
|
||||
request: Request,
|
||||
username: str = Form(...),
|
||||
password: str = Form(...),
|
||||
remember: Optional[bool] = Form(False),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Handle login form submission"""
|
||||
# This is a placeholder - implement real login logic here
|
||||
error = "This login method is not fully implemented yet"
|
||||
return templates.TemplateResponse(
|
||||
"auth/login.html",
|
||||
{"request": request, "error": error, "show_oauth": True, "oauth_provider_name": "Authentik"}
|
||||
)
|
||||
|
||||
@router.get("/register", response_class=HTMLResponse)
|
||||
async def register_page(request: Request, error: Optional[str] = None):
|
||||
"""Registration page route"""
|
||||
return templates.TemplateResponse(
|
||||
"auth/register.html",
|
||||
{"request": request, "error": error}
|
||||
)
|
||||
|
||||
@router.post("/register", response_class=HTMLResponse)
|
||||
async def register_post(
|
||||
request: Request,
|
||||
username: str = Form(...),
|
||||
email: str = Form(...),
|
||||
password: str = Form(...),
|
||||
confirm_password: str = Form(...),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Handle registration form submission"""
|
||||
# This is a placeholder - implement real registration logic here
|
||||
if password != confirm_password:
|
||||
return templates.TemplateResponse(
|
||||
"auth/register.html",
|
||||
{"request": request, "error": "Passwords do not match"}
|
||||
)
|
||||
|
||||
# Check username and email uniqueness, then create user
|
||||
return templates.TemplateResponse(
|
||||
"auth/registration_success.html",
|
||||
{"request": request}
|
||||
)
|
||||
|
||||
@router.get("/oauth-login")
|
||||
async def oauth_login(request: Request):
|
||||
"""Start the OAuth login flow"""
|
||||
# Generate the redirect URI
|
||||
base_url = str(request.base_url)
|
||||
redirect_uri = f"{base_url}auth/oauth-callback"
|
||||
|
||||
# Request a login URL from the Authentik provider
|
||||
try:
|
||||
# Set a session ID to validate the callback
|
||||
if "session_id" not in request.session:
|
||||
request.session["session_id"] = str(uuid.uuid4())
|
||||
|
||||
# Get the authorization URL - make sure to await it
|
||||
auth_url = await authentik_oauth.get_login_url(request, redirect_uri)
|
||||
|
||||
# Redirect to the authorization URL
|
||||
return RedirectResponse(auth_url)
|
||||
except Exception as e:
|
||||
print(f"OAuth login error: {str(e)}")
|
||||
return RedirectResponse(
|
||||
f"/auth/login?error=OAuth+login+failed:+{str(e)}",
|
||||
status_code=HTTP_303_SEE_OTHER
|
||||
)
|
||||
|
||||
@router.get("/oauth-callback")
|
||||
async def oauth_callback(request: Request, code: Optional[str] = None, state: Optional[str] = None, error: Optional[str] = None, db: Session = Depends(get_db)):
|
||||
"""Handle the OAuth callback"""
|
||||
if error:
|
||||
return RedirectResponse(
|
||||
f"/auth/login?error=OAuth+login+failed:+{error}",
|
||||
status_code=HTTP_303_SEE_OTHER
|
||||
)
|
||||
|
||||
if not code:
|
||||
return RedirectResponse(
|
||||
"/auth/login?error=No+authorization+code+received",
|
||||
status_code=HTTP_303_SEE_OTHER
|
||||
)
|
||||
|
||||
# Generate the redirect URI that matches the one used in the initial request
|
||||
base_url = str(request.base_url)
|
||||
redirect_uri = f"{base_url}auth/oauth-callback"
|
||||
|
||||
try:
|
||||
# Get user info from the provider
|
||||
user_info = await authentik_oauth.get_user_info(request, redirect_uri, code)
|
||||
|
||||
if not user_info:
|
||||
return RedirectResponse(
|
||||
"/auth/login?error=Could+not+retrieve+user+information",
|
||||
status_code=HTTP_303_SEE_OTHER
|
||||
)
|
||||
|
||||
# Extract user details from OAuth info
|
||||
sub = user_info.get("sub", "")
|
||||
email = user_info.get("email", "")
|
||||
name = user_info.get("preferred_username", "") or user_info.get("name", "") or email.split("@")[0]
|
||||
picture = user_info.get("picture", None)
|
||||
|
||||
# Check if the user already exists
|
||||
user = db.query(User).filter(User.email == email).first()
|
||||
|
||||
if not user:
|
||||
print(f"Creating new user with email {email} and username {name}")
|
||||
# Create a new user
|
||||
user = User(
|
||||
username=name,
|
||||
email=email,
|
||||
oauth_id=sub,
|
||||
is_oauth_user=True,
|
||||
oauth_provider="authentik",
|
||||
picture=picture,
|
||||
is_verified=True # OAuth users are considered verified
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
else:
|
||||
# Update existing user's OAuth information
|
||||
if not user.is_oauth_user:
|
||||
user.is_oauth_user = True
|
||||
user.oauth_id = sub
|
||||
user.oauth_provider = "authentik"
|
||||
|
||||
# Update profile picture if available
|
||||
if picture and not user.picture:
|
||||
user.picture = picture
|
||||
|
||||
db.commit()
|
||||
|
||||
# Set session data
|
||||
request.session["user_id"] = user.id
|
||||
request.session["username"] = user.username
|
||||
request.session["is_authenticated"] = True
|
||||
request.session["is_admin"] = user.is_admin
|
||||
|
||||
return RedirectResponse("/dashboard", status_code=HTTP_303_SEE_OTHER)
|
||||
|
||||
except Exception as e:
|
||||
print(f"OAuth callback error: {str(e)}")
|
||||
return RedirectResponse(
|
||||
f"/auth/login?error=OAuth+login+failed:+{str(e)}",
|
||||
status_code=HTTP_303_SEE_OTHER
|
||||
)
|
||||
|
||||
@router.get("/logout")
|
||||
async def logout(request: Request):
|
||||
"""Log out the user"""
|
||||
request.session.clear()
|
||||
return RedirectResponse("/", status_code=HTTP_303_SEE_OTHER)
|
||||
|
||||
@router.get("/profile", response_class=HTMLResponse)
|
||||
async def profile_page(request: Request):
|
||||
"""User profile page"""
|
||||
# Get the user ID from the session
|
||||
user_id = request.session.get("user_id")
|
||||
|
||||
if not user_id:
|
||||
return RedirectResponse("/auth/login", status_code=HTTP_303_SEE_OTHER)
|
||||
|
||||
# Mock user data - in a real app, you'd fetch this from the database
|
||||
user = {
|
||||
"id": user_id,
|
||||
"username": request.session.get("username", "User"),
|
||||
"email": "user@example.com",
|
||||
"is_admin": request.session.get("is_admin", False),
|
||||
"created_at": "2023-01-01 12:00:00",
|
||||
"picture": None
|
||||
}
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"auth/profile.html",
|
||||
{"request": request, "user": user}
|
||||
)
|
||||
+38
-15
@@ -14,13 +14,17 @@ router = APIRouter()
|
||||
def user_dashboard(request: Request, db: Session = Depends(get_db)):
|
||||
"""User dashboard showing teams, events and stats"""
|
||||
try:
|
||||
# Fetch user data directly from the database
|
||||
user = db.query(models.User).filter_by(email="admin@example.com").first() # Example user lookup
|
||||
# Get user ID from session instead of using hardcoded admin
|
||||
user_id = request.session.get("user_id")
|
||||
if not user_id:
|
||||
# Redirect to login if not authenticated
|
||||
return RedirectResponse("/auth/login", status_code=303)
|
||||
|
||||
# Fetch user data from the database using session user ID
|
||||
user = db.query(models.User).get(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
user_id = user.id
|
||||
|
||||
# Initialize default values in case of errors
|
||||
team_count = 0
|
||||
total_points = 0
|
||||
@@ -28,9 +32,20 @@ def user_dashboard(request: Request, db: Session = Depends(get_db)):
|
||||
recent_events = []
|
||||
user_teams = []
|
||||
|
||||
# Check if TeamMember model exists before querying
|
||||
if hasattr(models, "TeamMember"):
|
||||
# Get the team count for this user
|
||||
# Get team memberships - check both TeamMember and TeamMembership models
|
||||
if hasattr(models, "TeamMembership"):
|
||||
# Primary check - use TeamMembership model
|
||||
team_count = db.query(func.count(models.TeamMembership.team_id))\
|
||||
.filter(models.TeamMembership.user_id == user_id)\
|
||||
.scalar() or 0
|
||||
|
||||
# Get user teams
|
||||
user_teams = db.query(models.Team)\
|
||||
.join(models.TeamMembership)\
|
||||
.filter(models.TeamMembership.user_id == user_id)\
|
||||
.all()
|
||||
elif hasattr(models, "TeamMember"):
|
||||
# Fallback to TeamMember model if TeamMembership doesn't exist
|
||||
team_count = db.query(func.count(models.TeamMember.team_id))\
|
||||
.filter(models.TeamMember.user_id == user_id)\
|
||||
.scalar() or 0
|
||||
@@ -41,15 +56,23 @@ def user_dashboard(request: Request, db: Session = Depends(get_db)):
|
||||
.filter(models.TeamMember.user_id == user_id)\
|
||||
.all()
|
||||
|
||||
# Check if UserPoints model exists before querying
|
||||
if hasattr(models, "UserPoints"):
|
||||
# Get the total points safely
|
||||
total_points_result = db.query(func.sum(models.UserPoints.points))\
|
||||
.filter(models.UserPoints.user_id == user_id)\
|
||||
.first()
|
||||
# Get total points from QRCode redemptions
|
||||
# First try directly from QRCodes tied to user
|
||||
total_points_result = db.query(func.sum(models.QRCode.points))\
|
||||
.filter(models.QRCode.redeemed_by == user_id, models.QRCode.used == True)\
|
||||
.first()
|
||||
|
||||
if total_points_result and total_points_result[0]:
|
||||
total_points = total_points_result[0]
|
||||
if total_points_result and total_points_result[0]:
|
||||
total_points = total_points_result[0]
|
||||
else:
|
||||
# Fallback to UserPoints model if available
|
||||
if hasattr(models, "UserPoints"):
|
||||
points_result = db.query(func.sum(models.UserPoints.points))\
|
||||
.filter(models.UserPoints.user_id == user_id)\
|
||||
.first()
|
||||
|
||||
if points_result and points_result[0]:
|
||||
total_points = points_result[0]
|
||||
|
||||
# Check if EventAttendee model exists before querying
|
||||
if hasattr(models, "EventAttendee") and hasattr(models, "Event"):
|
||||
|
||||
@@ -9,7 +9,7 @@ from sqlalchemy import func, desc
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ..db import SessionLocal
|
||||
from ..models import Team, TeamMembership, QRCode
|
||||
from ..models import Team, TeamMembership, QRCode, User
|
||||
from ..templates_config import templates
|
||||
|
||||
router = APIRouter()
|
||||
@@ -29,6 +29,12 @@ async def show_leaderboard(
|
||||
):
|
||||
"""Show the leaderboard with team rankings."""
|
||||
|
||||
# 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)
|
||||
|
||||
# Define cutoff date based on timeframe
|
||||
cutoff_date = None
|
||||
if timeframe == "week":
|
||||
@@ -81,6 +87,7 @@ async def show_leaderboard(
|
||||
"teams": ranked_teams,
|
||||
"top_teams": top_teams,
|
||||
"timeframe": timeframe,
|
||||
"time_label": time_label
|
||||
"time_label": time_label,
|
||||
"user": user # Add user to the context
|
||||
}
|
||||
)
|
||||
|
||||
+77
-11
@@ -2,45 +2,111 @@
|
||||
"""
|
||||
Router for static content pages like about, contact, privacy, and terms.
|
||||
"""
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi import APIRouter, Request, Depends
|
||||
from fastapi.responses import HTMLResponse
|
||||
from ..templates_config import templates
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
from ..db import get_db
|
||||
from .. import models
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
def index(request: Request):
|
||||
def index(request: Request, db: Session = Depends(get_db)):
|
||||
"""Home page."""
|
||||
# Get user from session for navbar
|
||||
user = None
|
||||
user_id = request.session.get("user_id")
|
||||
if user_id:
|
||||
user = db.query(models.User).get(user_id)
|
||||
|
||||
return templates.TemplateResponse("index.html", {
|
||||
"request": request,
|
||||
"now": datetime.now
|
||||
"now": datetime.now,
|
||||
"user": user
|
||||
})
|
||||
|
||||
@router.get("/about", response_class=HTMLResponse)
|
||||
def about(request: Request):
|
||||
def about(request: Request, db: Session = Depends(get_db)):
|
||||
"""About page."""
|
||||
# Get user from session for navbar
|
||||
user = None
|
||||
user_id = request.session.get("user_id")
|
||||
if user_id:
|
||||
user = db.query(models.User).get(user_id)
|
||||
|
||||
return templates.TemplateResponse("about.html", {
|
||||
"request": request
|
||||
"request": request,
|
||||
"user": user
|
||||
})
|
||||
|
||||
@router.get("/contact", response_class=HTMLResponse)
|
||||
def contact(request: Request):
|
||||
def contact(request: Request, db: Session = Depends(get_db)):
|
||||
"""Contact page."""
|
||||
# Get user from session for navbar
|
||||
user = None
|
||||
user_id = request.session.get("user_id")
|
||||
if user_id:
|
||||
user = db.query(models.User).get(user_id)
|
||||
|
||||
return templates.TemplateResponse("contact.html", {
|
||||
"request": request
|
||||
"request": request,
|
||||
"user": user
|
||||
})
|
||||
|
||||
@router.get("/privacy", response_class=HTMLResponse)
|
||||
def privacy(request: Request):
|
||||
def privacy(request: Request, db: Session = Depends(get_db)):
|
||||
"""Privacy policy page."""
|
||||
# Get user from session for navbar
|
||||
user = None
|
||||
user_id = request.session.get("user_id")
|
||||
if user_id:
|
||||
user = db.query(models.User).get(user_id)
|
||||
|
||||
return templates.TemplateResponse("privacy.html", {
|
||||
"request": request
|
||||
"request": request,
|
||||
"user": user
|
||||
})
|
||||
|
||||
@router.get("/terms", response_class=HTMLResponse)
|
||||
def terms(request: Request):
|
||||
def terms(request: Request, db: Session = Depends(get_db)):
|
||||
"""Terms and conditions page."""
|
||||
# Get user from session for navbar
|
||||
user = None
|
||||
user_id = request.session.get("user_id")
|
||||
if user_id:
|
||||
user = db.query(models.User).get(user_id)
|
||||
|
||||
return templates.TemplateResponse("terms.html", {
|
||||
"request": request
|
||||
"request": request,
|
||||
"user": user
|
||||
})
|
||||
|
||||
@router.get("/cookies", response_class=HTMLResponse)
|
||||
def cookies(request: Request, db: Session = Depends(get_db)):
|
||||
"""Cookie policy page."""
|
||||
# Get user from session for navbar
|
||||
user = None
|
||||
user_id = request.session.get("user_id")
|
||||
if user_id:
|
||||
user = db.query(models.User).get(user_id)
|
||||
|
||||
return templates.TemplateResponse("cookies.html", {
|
||||
"request": request,
|
||||
"user": user
|
||||
})
|
||||
|
||||
@router.get("/impressum", response_class=HTMLResponse)
|
||||
def impressum(request: Request, db: Session = Depends(get_db)):
|
||||
"""Imprint/Impressum page."""
|
||||
# Get user from session for navbar
|
||||
user = None
|
||||
user_id = request.session.get("user_id")
|
||||
if user_id:
|
||||
user = db.query(models.User).get(user_id)
|
||||
|
||||
return templates.TemplateResponse("impressum.html", {
|
||||
"request": request,
|
||||
"user": user
|
||||
})
|
||||
|
||||
+195
-49
@@ -10,7 +10,7 @@ from datetime import datetime, timedelta
|
||||
import random # For demo data
|
||||
|
||||
from ..db import SessionLocal
|
||||
from ..models import Team, TeamMembership, User, QRCode, TeamAchievement
|
||||
from ..models import Team, TeamMembership, User, QRCode, TeamAchievement, TeamMember
|
||||
from ..schemas import TeamCreate
|
||||
from ..templates_config import templates
|
||||
|
||||
@@ -30,12 +30,26 @@ def list_teams(request: Request, db: Session = Depends(get_db)):
|
||||
# Get the user's teams to highlight teams they're already in
|
||||
user_team_ids = []
|
||||
|
||||
# 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)
|
||||
# Get teams that user is a member of
|
||||
memberships = db.query(TeamMembership).filter(TeamMembership.user_id == user_id).all()
|
||||
user_team_ids = [membership.team_id for membership in memberships]
|
||||
|
||||
# Get error message if present
|
||||
error = request.query_params.get("error")
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"teams.html",
|
||||
{
|
||||
"request": request,
|
||||
"teams": teams,
|
||||
"user_team_ids": user_team_ids,
|
||||
"user": user,
|
||||
"error": error,
|
||||
"brand_colors": {
|
||||
"irish_green": "#006837",
|
||||
"golden_ale": "#FFB400",
|
||||
@@ -48,31 +62,153 @@ def list_teams(request: Request, db: Session = Depends(get_db)):
|
||||
|
||||
@router.post("/create")
|
||||
def create_team(request: Request, name: str = Form(...), db: Session = Depends(get_db)):
|
||||
# Create team
|
||||
new_team = Team(name=name)
|
||||
# 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 the user
|
||||
user = db.query(User).get(user_id)
|
||||
if not user:
|
||||
return RedirectResponse("/auth/login", status_code=303)
|
||||
|
||||
# Check if team name already exists
|
||||
existing_team = db.query(Team).filter(Team.name == name).first()
|
||||
if existing_team:
|
||||
# Return to teams page with error message
|
||||
# In a real app, you'd add error handling/flash messages
|
||||
return RedirectResponse("/teams/?error=Team+name+already+exists", status_code=303)
|
||||
|
||||
# Create team with the user as owner
|
||||
new_team = Team(name=name, owner_id=user_id)
|
||||
db.add(new_team)
|
||||
db.commit()
|
||||
db.refresh(new_team)
|
||||
|
||||
# Make the user an admin of the team in TeamMembership
|
||||
team_membership = TeamMembership(
|
||||
user_id=user_id,
|
||||
team_id=new_team.id,
|
||||
is_admin=True # User becomes admin of the team
|
||||
)
|
||||
db.add(team_membership)
|
||||
|
||||
# Check if TeamMember model exists in the database
|
||||
try:
|
||||
# Use a safer approach to check if the model exists and is usable
|
||||
if 'team_members' in inspect(db.bind).get_table_names():
|
||||
# Create TeamMember relationship as well
|
||||
team_member = TeamMember(
|
||||
user_id=user_id,
|
||||
team_id=new_team.id,
|
||||
is_captain=True # User becomes captain in TeamMember model
|
||||
)
|
||||
db.add(team_member)
|
||||
except Exception as e:
|
||||
print(f"Could not create TeamMember record: {str(e)}")
|
||||
# Continue even if this fails - TeamMembership is primary relationship
|
||||
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse("/teams/", status_code=303)
|
||||
|
||||
@router.post("/join/{team_id}")
|
||||
def join_team(request: Request, team_id: int, db: Session = Depends(get_db)):
|
||||
# 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/", status_code=303)
|
||||
return RedirectResponse("/teams/?error=Team+not+found", status_code=303)
|
||||
|
||||
# Check if membership exists
|
||||
existing = db.query(TeamMembership).filter_by(team_id=team.id).first()
|
||||
existing = db.query(TeamMembership)\
|
||||
.filter_by(user_id=user_id, team_id=team.id)\
|
||||
.first()
|
||||
|
||||
if existing:
|
||||
return RedirectResponse("/teams/", status_code=303)
|
||||
return RedirectResponse("/teams/?error=You+are+already+a+member+of+this+team", status_code=303)
|
||||
|
||||
# Create membership
|
||||
new_member = TeamMembership(team_id=team.id, is_admin=False)
|
||||
new_member = TeamMembership(
|
||||
user_id=user_id,
|
||||
team_id=team.id,
|
||||
is_admin=False
|
||||
)
|
||||
db.add(new_member)
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse("/teams/", status_code=303)
|
||||
# Redirect to the team detail page
|
||||
return RedirectResponse(f"/teams/{team_id}", status_code=303)
|
||||
|
||||
@router.post("/{team_id}/leave")
|
||||
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)
|
||||
|
||||
@router.post("/{team_id}/update")
|
||||
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,
|
||||
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)
|
||||
|
||||
@router.get("/{team_id}", response_class=HTMLResponse)
|
||||
def team_detail(request: Request, team_id: int, db: Session = Depends(get_db)):
|
||||
@@ -82,6 +218,21 @@ def team_detail(request: Request, team_id: int, db: Session = Depends(get_db)):
|
||||
if not team:
|
||||
raise HTTPException(status_code=404, detail="Team not found")
|
||||
|
||||
# Get user from session for navbar
|
||||
user = None
|
||||
user_id = request.session.get("user_id")
|
||||
is_team_member = False
|
||||
|
||||
if user_id:
|
||||
user = db.query(User).get(user_id)
|
||||
|
||||
# Check if user is a team member
|
||||
team_membership = db.query(TeamMembership)\
|
||||
.filter(TeamMembership.user_id == user_id, TeamMembership.team_id == team_id)\
|
||||
.first()
|
||||
|
||||
is_team_member = team_membership is not None
|
||||
|
||||
# Get team members with admin status
|
||||
memberships = db.query(TeamMembership).filter_by(team_id=team_id).all()
|
||||
team_members = []
|
||||
@@ -90,6 +241,10 @@ def team_detail(request: Request, team_id: int, db: Session = Depends(get_db)):
|
||||
for membership in memberships:
|
||||
member = db.query(User).filter_by(id=membership.user_id).first()
|
||||
if member:
|
||||
# Check if current user is admin of this team
|
||||
if user and user.id == membership.user_id and membership.is_admin:
|
||||
is_user_admin = True
|
||||
|
||||
# Use joined_at if available, otherwise use placeholder
|
||||
joined_date = getattr(membership, 'joined_at', None) or datetime.now() - timedelta(days=random.randint(30, 180))
|
||||
if isinstance(joined_date, datetime):
|
||||
@@ -105,21 +260,39 @@ def team_detail(request: Request, team_id: int, db: Session = Depends(get_db)):
|
||||
"joined": f"{month_name} {year}"
|
||||
})
|
||||
|
||||
# Also check if user is the owner
|
||||
is_user_owner = user and team.owner_id == user.id
|
||||
if is_user_owner:
|
||||
is_user_admin = True # Owner has admin privileges
|
||||
|
||||
# Get total points
|
||||
total_points = db.query(func.sum(QRCode.points)).filter(
|
||||
QRCode.redeemed_at_team == team_id
|
||||
).scalar() or 0
|
||||
|
||||
# Calculate rank based on points
|
||||
higher_teams = db.query(func.count(Team.id)).join(
|
||||
QRCode,
|
||||
QRCode.redeemed_at_team == Team.id,
|
||||
isouter=True
|
||||
).group_by(Team.id).having(
|
||||
func.sum(QRCode.points) > total_points
|
||||
).scalar() or 0
|
||||
|
||||
team_rank = higher_teams + 1
|
||||
# Calculate rank based on points - using a safer approach
|
||||
try:
|
||||
# First, get the aggregated points for all teams
|
||||
team_points = db.query(
|
||||
QRCode.redeemed_at_team,
|
||||
func.sum(QRCode.points).label('total')
|
||||
).filter(
|
||||
QRCode.redeemed_at_team != None
|
||||
).group_by(QRCode.redeemed_at_team).all()
|
||||
|
||||
# Sort them by points (descending)
|
||||
sorted_teams = sorted(team_points, key=lambda x: x.total or 0, reverse=True)
|
||||
|
||||
# Find our team's position
|
||||
team_rank = 1
|
||||
for idx, team_data in enumerate(sorted_teams):
|
||||
if team_data.redeemed_at_team == team_id:
|
||||
team_rank = idx + 1
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error calculating team rank: {e}")
|
||||
team_rank = 1 # Default to 1st place on error
|
||||
|
||||
# Generate points data (with fallbacks for missing columns)
|
||||
points_this_month = 65 # Default value
|
||||
@@ -205,37 +378,10 @@ def team_detail(request: Request, team_id: int, db: Session = Depends(get_db)):
|
||||
"activities": activities,
|
||||
"performance": performance,
|
||||
"is_user_admin": is_user_admin,
|
||||
"is_user_owner": is_user_owner,
|
||||
"is_team_member": is_team_member,
|
||||
"days_ago": days_ago,
|
||||
"founded_date": founded_date_str
|
||||
"founded_date": founded_date_str,
|
||||
"user": user # Add user to the context
|
||||
}
|
||||
)
|
||||
|
||||
@router.post("/{team_id}/update")
|
||||
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,
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user