Files
org-parking/app/routes/auth.py
Stefano Manfredi 7168fa4b72 Refactor to manager-centric model, add team calendar for all users
Key changes:
- Removed office-centric model (deleted offices.py, office-rules)
- Renamed to team-rules, managers are part of their own team
- Team calendar visible to all (read-only for employees)
- Admins can have a manager assigned
2025-12-02 13:30:04 +00:00

138 lines
4.0 KiB
Python

"""
Authentication Routes
Login, register, logout, and user info
"""
from fastapi import APIRouter, Depends, HTTPException, status, Response
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from database.connection import get_db
from services.auth import (
create_user, authenticate_user, create_access_token,
get_user_by_email, hash_password, verify_password
)
from utils.auth_middleware import get_current_user
from app import config
import re
router = APIRouter(prefix="/api/auth", tags=["auth"])
class RegisterRequest(BaseModel):
email: EmailStr
password: str
name: str
manager_id: str | None = None
class LoginRequest(BaseModel):
email: EmailStr
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
class UserResponse(BaseModel):
id: str
email: str
name: str | None
manager_id: str | None
role: str
manager_parking_quota: int | None = None
week_start_day: int = 0
# Notification preferences
notify_weekly_parking: int = 1
notify_daily_parking: int = 1
notify_daily_parking_hour: int = 8
notify_daily_parking_minute: int = 0
notify_parking_changes: int = 1
@router.post("/register", response_model=TokenResponse)
def register(data: RegisterRequest, db: Session = Depends(get_db)):
"""Register a new user"""
if get_user_by_email(db, data.email):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered"
)
if len(data.password) < 8:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Password must be at least 8 characters"
)
user = create_user(
db=db,
email=data.email,
password=data.password,
name=data.name,
manager_id=data.manager_id
)
token = create_access_token(user.id, user.email)
return TokenResponse(access_token=token)
@router.post("/login", response_model=TokenResponse)
def login(data: LoginRequest, response: Response, db: Session = Depends(get_db)):
"""Login with email and password"""
user = authenticate_user(db, data.email, data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials"
)
token = create_access_token(user.id, user.email)
response.set_cookie(
key="session_token",
value=token,
httponly=True,
max_age=config.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
samesite="lax"
)
return TokenResponse(access_token=token)
@router.post("/logout")
def logout(response: Response):
"""Logout and clear session"""
response.delete_cookie("session_token")
return {"message": "Logged out"}
@router.get("/me", response_model=UserResponse)
def get_me(user=Depends(get_current_user)):
"""Get current user info"""
return UserResponse(
id=user.id,
email=user.email,
name=user.name,
manager_id=user.manager_id,
role=user.role,
manager_parking_quota=user.manager_parking_quota,
week_start_day=user.week_start_day or 0,
notify_weekly_parking=user.notify_weekly_parking if user.notify_weekly_parking is not None else 1,
notify_daily_parking=user.notify_daily_parking if user.notify_daily_parking is not None else 1,
notify_daily_parking_hour=user.notify_daily_parking_hour if user.notify_daily_parking_hour is not None else 8,
notify_daily_parking_minute=user.notify_daily_parking_minute if user.notify_daily_parking_minute is not None else 0,
notify_parking_changes=user.notify_parking_changes if user.notify_parking_changes is not None else 1
)
@router.get("/holidays/{year}")
def get_holidays(year: int):
"""Get public holidays for a given year"""
from services.holidays import get_holidays_for_year
if year < 2000 or year > 2100:
raise HTTPException(status_code=400, detail="Year must be between 2000 and 2100")
return get_holidays_for_year(year)