Refactor user authentication and dashboard features

- Added `is_admin` field to User model for role management.
- Updated user context middleware to include current user in templates.
- Enhanced session management with improved debug middleware.
- Refactored dashboard view to fetch user-specific data and recent events.
- Improved login and registration templates for better user experience.
- Added admin routes with access control for admin users.
- Updated Docker configuration for better error logging and dependency management.
- Updated requirements to include new dependencies and specify versions.
This commit is contained in:
Christian Krakau-Louis
2025-04-13 18:49:27 +02:00
parent 0de2eb5830
commit 4a79c5cacb
20 changed files with 790 additions and 579 deletions
+9
View File
@@ -13,6 +13,7 @@ import inspect as py_inspect
from ..db import SessionLocal, Base
from ..models import User, Team, TeamMembership, QRTicket
from ..templates_config import templates
from ..auth import require_admin
router = APIRouter()
@@ -64,6 +65,7 @@ def get_relationships(model_class: Type[Base]) -> Dict[str, str]:
return relationships
@router.get("/", response_class=HTMLResponse)
@require_admin
async def admin_home(request: Request):
"""Admin dashboard home."""
model_list = [(key, name) for key, (_, name) in MODELS.items()]
@@ -73,6 +75,7 @@ async def admin_home(request: Request):
)
@router.get("/{model_name}", response_class=HTMLResponse)
@require_admin
async def list_records(
request: Request,
model_name: str,
@@ -124,6 +127,7 @@ async def list_records(
)
@router.get("/{model_name}/new", response_class=HTMLResponse)
@require_admin
async def create_record_form(
request: Request,
model_name: str,
@@ -164,6 +168,7 @@ async def create_record_form(
)
@router.post("/{model_name}/new")
@require_admin
async def create_record(
request: Request,
model_name: str,
@@ -212,6 +217,7 @@ async def create_record(
return RedirectResponse(f"/admin/{model_name}", status_code=303)
@router.get("/{model_name}/{record_id}", response_class=HTMLResponse)
@require_admin
async def edit_record_form(
request: Request,
model_name: str,
@@ -263,6 +269,7 @@ async def edit_record_form(
)
@router.post("/{model_name}/{record_id}")
@require_admin
async def update_record(
request: Request,
model_name: str,
@@ -311,7 +318,9 @@ async def update_record(
return RedirectResponse(f"/admin/{model_name}", status_code=303)
@router.get("/{model_name}/{record_id}/delete")
@require_admin
async def delete_record(
request: Request,
model_name: str,
record_id: int,
db: Session = Depends(get_db)
+75 -128
View File
@@ -1,137 +1,84 @@
#!/usr/bin/env python3
"""
Dashboard views for user-specific information.
"""
from fastapi import APIRouter, Depends, Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi import APIRouter, Request, Depends, HTTPException
from sqlalchemy.orm import Session
from sqlalchemy import func
from sqlalchemy.sql import func
from fastapi.responses import HTMLResponse, RedirectResponse
from ..db import SessionLocal
from ..models import User, Team, TeamMembership, QRTicket
from ..db import get_db
from ..templates_config import templates
from ..auth import require_login
from .. import models
router = APIRouter()
def get_db():
db = SessionLocal()
@router.get("/")
@require_login
def user_dashboard(request: Request, db: Session = Depends(get_db)):
"""User dashboard showing teams, events and stats"""
try:
yield db
finally:
db.close()
def get_or_create_default_user(db: Session):
"""Get user ID 1 or create it if it doesn't exist."""
user = db.query(User).filter_by(id=1).first()
if not user:
# Create a default user
user = User(
username="default_user",
email="default@example.com",
hashed_password="placeholder"
user = request.session.get("user")
user_id = user.get("id")
# Initialize default values in case of errors
team_count = 0
total_points = 0
event_count = 0
recent_events = []
user_teams = []
# Check if TeamMember model exists before querying
if hasattr(models, "TeamMember"):
# Get the team count for this user
team_count = db.query(func.count(models.TeamMember.team_id))\
.filter(models.TeamMember.user_id == user_id)\
.scalar() or 0
# Get user teams
user_teams = db.query(models.Team)\
.join(models.TeamMember)\
.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()
if total_points_result and total_points_result[0]:
total_points = total_points_result[0]
# Check if EventAttendee model exists before querying
if hasattr(models, "EventAttendee") and hasattr(models, "Event"):
# Get event count safely
event_count_result = db.query(func.count(models.EventAttendee.event_id))\
.filter(models.EventAttendee.user_id == user_id)\
.first()
if event_count_result and event_count_result[0]:
event_count = event_count_result[0]
# Recent events - only if both models exist
recent_events = db.query(models.Event)\
.join(models.EventAttendee)\
.filter(models.EventAttendee.user_id == user_id)\
.order_by(models.Event.event_date.desc())\
.limit(5)\
.all()
return templates.TemplateResponse(
"dashboard/index.html",
{
"request": request,
"user": user,
"team_count": team_count,
"total_points": total_points,
"event_count": event_count,
"recent_events": recent_events,
"user_teams": user_teams
}
)
db.add(user)
db.commit()
db.refresh(user)
return user
@router.get("/", response_class=HTMLResponse)
async def user_dashboard(
request: Request,
db: Session = Depends(get_db)
):
"""Show the user's dashboard with team info and recent activity."""
# Get current user - using a default user for now
# In a real app, this would come from auth system
user = get_or_create_default_user(db)
# Get user's teams
user_teams = db.query(Team).join(
TeamMembership,
TeamMembership.team_id == Team.id
).filter(
TeamMembership.user_id == user.id
).all()
# Get team memberships with admin status
team_memberships = db.query(
TeamMembership
).filter(
TeamMembership.user_id == user.id
).all()
admin_team_ids = [tm.team_id for tm in team_memberships if tm.is_admin]
# Get points per team
team_points = {}
for team in user_teams:
points = db.query(func.sum(QRTicket.points)).filter(
QRTicket.redeemed_at_team == team.id
).scalar() or 0
# Get team ranking - simplified approach
higher_teams = db.query(func.count(Team.id)).join(
QRTicket,
QRTicket.redeemed_at_team == Team.id
).group_by(
Team.id
).having(
func.sum(QRTicket.points) > points
).scalar() or 0
rank = higher_teams + 1
team_points[team.id] = {
'points': points,
'rank': rank
}
# Get recent activity
# For simplicity, we're just getting recent QR code redemptions
recent_activity = []
recent_tickets = db.query(QRTicket).filter(
QRTicket.redeemed_by == user.id
).order_by(
QRTicket.id.desc() # Assuming higher ID = newer
).limit(5).all()
for ticket in recent_tickets:
team = db.query(Team).filter(Team.id == ticket.redeemed_at_team).first()
activity = {
'type': 'qr_redeem',
'points': ticket.points,
'team_name': team.name if team else "Unknown team",
'date': "Recently" # Placeholder - would use ticket.created_at
}
recent_activity.append(activity)
# Get total points for user across all teams
total_points = sum(team_data['points'] for team_data in team_points.values())
# Get best ranking
best_rank = min(team_data['rank'] for team_data in team_points.values()) if team_points else None
return templates.TemplateResponse(
"dashboard.html",
{
"request": request,
"user": user,
"teams": user_teams,
"team_points": team_points,
"admin_team_ids": admin_team_ids,
"recent_activity": recent_activity,
"total_points": total_points,
"best_rank": best_rank,
"team_count": len(user_teams)
}
)
@router.get("/scan", response_class=HTMLResponse)
async def scan_qr(request: Request):
"""Show QR scanning interface."""
return templates.TemplateResponse(
"scan_qr.html",
{"request": request}
)
except Exception as e:
print(f"Dashboard error: {str(e)}")
raise HTTPException(status_code=500, detail=f"Dashboard error: {str(e)}")