A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
390 lines
10 KiB
Python
390 lines
10 KiB
Python
"""
|
|
Protected Routes Example
|
|
|
|
Shows how to structure routes under /api/protected with session-based auth.
|
|
|
|
Route structure:
|
|
/api/auth/ - Public (login, register, logout)
|
|
/api/public/ - Public (health, docs)
|
|
/api/protected/ - Authenticated (all users)
|
|
/api/protected/employee/ - Employees only
|
|
/api/protected/customer/ - Customers only
|
|
/api/admin/ - Admins only
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from typing import List, Optional
|
|
|
|
from .session_store import Session, UserType
|
|
from .session_middleware import get_current_session, get_optional_session
|
|
from .rbac_middleware import (
|
|
require_employee,
|
|
require_customer,
|
|
require_permission,
|
|
require_role,
|
|
require_any_role,
|
|
)
|
|
|
|
# =============================================
|
|
# Router Setup
|
|
# =============================================
|
|
|
|
# Protected routes - require authentication
|
|
protected_router = APIRouter(prefix="/api/protected", tags=["Protected"])
|
|
|
|
# Employee-only routes
|
|
employee_router = APIRouter(prefix="/api/protected/employee", tags=["Employee"])
|
|
|
|
# Customer-only routes
|
|
customer_router = APIRouter(prefix="/api/protected/customer", tags=["Customer"])
|
|
|
|
# Admin routes
|
|
admin_router = APIRouter(prefix="/api/admin", tags=["Admin"])
|
|
|
|
|
|
# =============================================
|
|
# Protected Routes (All Authenticated Users)
|
|
# =============================================
|
|
|
|
@protected_router.get("/profile")
|
|
async def get_profile(session: Session = Depends(get_current_session)):
|
|
"""Get current user's profile."""
|
|
return {
|
|
"user_id": session.user_id,
|
|
"email": session.email,
|
|
"user_type": session.user_type.value,
|
|
"roles": session.roles,
|
|
"permissions": session.permissions,
|
|
"tenant_id": session.tenant_id,
|
|
}
|
|
|
|
|
|
@protected_router.get("/notifications")
|
|
async def get_notifications(session: Session = Depends(get_current_session)):
|
|
"""Get user's notifications."""
|
|
# TODO: Implement actual notification fetching
|
|
return {
|
|
"notifications": [],
|
|
"unread_count": 0,
|
|
}
|
|
|
|
|
|
@protected_router.post("/logout")
|
|
async def logout(session: Session = Depends(get_current_session)):
|
|
"""Logout current session."""
|
|
from .session_store import get_session_store
|
|
|
|
store = await get_session_store()
|
|
await store.revoke_session(session.session_id)
|
|
|
|
return {"message": "Logged out successfully"}
|
|
|
|
|
|
@protected_router.post("/logout-all")
|
|
async def logout_all(session: Session = Depends(get_current_session)):
|
|
"""Logout from all devices."""
|
|
from .session_store import get_session_store
|
|
|
|
store = await get_session_store()
|
|
count = await store.revoke_all_user_sessions(session.user_id)
|
|
|
|
return {"message": f"Logged out from {count} sessions"}
|
|
|
|
|
|
@protected_router.get("/sessions")
|
|
async def get_active_sessions(session: Session = Depends(get_current_session)):
|
|
"""Get all active sessions for current user."""
|
|
from .session_store import get_session_store
|
|
|
|
store = await get_session_store()
|
|
sessions = await store.get_active_sessions(session.user_id)
|
|
|
|
return {
|
|
"sessions": [
|
|
{
|
|
"session_id": s.session_id,
|
|
"ip_address": s.ip_address,
|
|
"user_agent": s.user_agent,
|
|
"created_at": s.created_at.isoformat() if s.created_at else None,
|
|
"last_activity_at": s.last_activity_at.isoformat() if s.last_activity_at else None,
|
|
"is_current": s.session_id == session.session_id,
|
|
}
|
|
for s in sessions
|
|
]
|
|
}
|
|
|
|
|
|
# =============================================
|
|
# Employee Routes
|
|
# =============================================
|
|
|
|
@employee_router.get("/dashboard")
|
|
async def employee_dashboard(session: Session = Depends(require_employee)):
|
|
"""Employee dashboard with overview data."""
|
|
return {
|
|
"user_type": "employee",
|
|
"email": session.email,
|
|
"roles": session.roles,
|
|
"widgets": [
|
|
{"type": "today_classes", "title": "Heutige Stunden"},
|
|
{"type": "pending_corrections", "title": "Ausstehende Korrekturen"},
|
|
{"type": "absent_students", "title": "Abwesende Schueler"},
|
|
],
|
|
}
|
|
|
|
|
|
@employee_router.get("/grades")
|
|
async def get_grades(
|
|
class_id: Optional[str] = None,
|
|
session: Session = Depends(require_permission("grades:read"))
|
|
):
|
|
"""Get grades (employee only, requires grades:read permission)."""
|
|
# TODO: Implement actual grade fetching
|
|
return {
|
|
"grades": [],
|
|
"class_id": class_id,
|
|
}
|
|
|
|
|
|
@employee_router.post("/grades")
|
|
async def create_grade(
|
|
grade_data: dict,
|
|
session: Session = Depends(require_permission("grades:write"))
|
|
):
|
|
"""Create a new grade (requires grades:write permission)."""
|
|
# TODO: Implement grade creation
|
|
return {"message": "Grade created"}
|
|
|
|
|
|
@employee_router.get("/attendance")
|
|
async def get_attendance(
|
|
date: Optional[str] = None,
|
|
class_id: Optional[str] = None,
|
|
session: Session = Depends(require_permission("attendance:read"))
|
|
):
|
|
"""Get attendance records."""
|
|
return {
|
|
"attendance": [],
|
|
"date": date,
|
|
"class_id": class_id,
|
|
}
|
|
|
|
|
|
@employee_router.post("/attendance")
|
|
async def mark_attendance(
|
|
attendance_data: dict,
|
|
session: Session = Depends(require_permission("attendance:write"))
|
|
):
|
|
"""Mark student attendance."""
|
|
return {"message": "Attendance recorded"}
|
|
|
|
|
|
@employee_router.get("/students")
|
|
async def get_students(
|
|
class_id: Optional[str] = None,
|
|
session: Session = Depends(require_permission("students:read"))
|
|
):
|
|
"""Get student list."""
|
|
return {
|
|
"students": [],
|
|
"class_id": class_id,
|
|
}
|
|
|
|
|
|
@employee_router.get("/corrections")
|
|
async def get_corrections(
|
|
session: Session = Depends(require_permission("corrections:read"))
|
|
):
|
|
"""Get pending corrections."""
|
|
return {
|
|
"corrections": [],
|
|
"pending_count": 0,
|
|
}
|
|
|
|
|
|
# =============================================
|
|
# Customer Routes
|
|
# =============================================
|
|
|
|
@customer_router.get("/dashboard")
|
|
async def customer_dashboard(session: Session = Depends(require_customer)):
|
|
"""Customer dashboard."""
|
|
return {
|
|
"user_type": "customer",
|
|
"email": session.email,
|
|
"widgets": [
|
|
{"type": "my_children", "title": "Meine Kinder"},
|
|
{"type": "upcoming_meetings", "title": "Anstehende Termine"},
|
|
{"type": "recent_grades", "title": "Aktuelle Noten"},
|
|
],
|
|
}
|
|
|
|
|
|
@customer_router.get("/my-children")
|
|
async def get_my_children(
|
|
session: Session = Depends(require_permission("children:read"))
|
|
):
|
|
"""Get parent's children."""
|
|
# TODO: Implement actual children fetching
|
|
return {
|
|
"children": [],
|
|
}
|
|
|
|
|
|
@customer_router.get("/my-grades")
|
|
async def get_my_grades(
|
|
session: Session = Depends(require_permission("own_grades:read"))
|
|
):
|
|
"""Get student's own grades."""
|
|
return {
|
|
"grades": [],
|
|
"average": None,
|
|
}
|
|
|
|
|
|
@customer_router.get("/my-attendance")
|
|
async def get_my_attendance(
|
|
session: Session = Depends(require_permission("own_attendance:read"))
|
|
):
|
|
"""Get student's own attendance."""
|
|
return {
|
|
"attendance_records": [],
|
|
"absence_count": 0,
|
|
}
|
|
|
|
|
|
@customer_router.get("/children/{child_id}/grades")
|
|
async def get_child_grades(
|
|
child_id: str,
|
|
session: Session = Depends(require_permission("children:grades:read"))
|
|
):
|
|
"""Get child's grades (for parents)."""
|
|
# TODO: Verify parent-child relationship
|
|
return {
|
|
"child_id": child_id,
|
|
"grades": [],
|
|
}
|
|
|
|
|
|
@customer_router.get("/appointments")
|
|
async def get_appointments(session: Session = Depends(require_customer)):
|
|
"""Get upcoming appointments/meetings."""
|
|
return {
|
|
"appointments": [],
|
|
}
|
|
|
|
|
|
@customer_router.post("/appointments/{slot_id}/book")
|
|
async def book_appointment(
|
|
slot_id: str,
|
|
session: Session = Depends(require_permission("meetings:join"))
|
|
):
|
|
"""Book a parent meeting slot."""
|
|
return {
|
|
"message": "Appointment booked",
|
|
"slot_id": slot_id,
|
|
}
|
|
|
|
|
|
# =============================================
|
|
# Admin Routes
|
|
# =============================================
|
|
|
|
@admin_router.get("/users")
|
|
async def list_users(
|
|
page: int = 1,
|
|
limit: int = 50,
|
|
session: Session = Depends(require_permission("users:read"))
|
|
):
|
|
"""List all users (admin only)."""
|
|
return {
|
|
"users": [],
|
|
"total": 0,
|
|
"page": page,
|
|
"limit": limit,
|
|
}
|
|
|
|
|
|
@admin_router.get("/users/{user_id}")
|
|
async def get_user(
|
|
user_id: str,
|
|
session: Session = Depends(require_permission("users:read"))
|
|
):
|
|
"""Get user details."""
|
|
return {
|
|
"user_id": user_id,
|
|
"email": None,
|
|
"roles": [],
|
|
}
|
|
|
|
|
|
@admin_router.put("/users/{user_id}/roles")
|
|
async def update_user_roles(
|
|
user_id: str,
|
|
roles: List[str],
|
|
session: Session = Depends(require_permission("users:manage"))
|
|
):
|
|
"""Update user roles (admin only)."""
|
|
return {
|
|
"message": "Roles updated",
|
|
"user_id": user_id,
|
|
"roles": roles,
|
|
}
|
|
|
|
|
|
@admin_router.get("/audit-log")
|
|
async def get_audit_log(
|
|
page: int = 1,
|
|
limit: int = 100,
|
|
session: Session = Depends(require_permission("audit:read"))
|
|
):
|
|
"""Get audit log entries."""
|
|
return {
|
|
"entries": [],
|
|
"total": 0,
|
|
"page": page,
|
|
"limit": limit,
|
|
}
|
|
|
|
|
|
@admin_router.get("/rbac/roles")
|
|
async def list_roles(
|
|
session: Session = Depends(require_permission("rbac:read"))
|
|
):
|
|
"""List all RBAC roles."""
|
|
return {
|
|
"roles": [],
|
|
}
|
|
|
|
|
|
@admin_router.get("/rbac/permissions")
|
|
async def list_permissions(
|
|
session: Session = Depends(require_permission("rbac:read"))
|
|
):
|
|
"""List all permissions."""
|
|
from .rbac_middleware import EMPLOYEE_PERMISSIONS, CUSTOMER_PERMISSIONS, ADMIN_PERMISSIONS
|
|
|
|
return {
|
|
"employee_permissions": EMPLOYEE_PERMISSIONS,
|
|
"customer_permissions": CUSTOMER_PERMISSIONS,
|
|
"admin_permissions": ADMIN_PERMISSIONS,
|
|
}
|
|
|
|
|
|
# =============================================
|
|
# Router Registration Helper
|
|
# =============================================
|
|
|
|
def register_protected_routes(app):
|
|
"""
|
|
Register all protected route routers with FastAPI app.
|
|
|
|
Usage:
|
|
from session.protected_routes import register_protected_routes
|
|
register_protected_routes(app)
|
|
"""
|
|
app.include_router(protected_router)
|
|
app.include_router(employee_router)
|
|
app.include_router(customer_router)
|
|
app.include_router(admin_router)
|