Move slot expansion logic to ExchangeService
- Add get_available_slots() and _expand_availability_to_slots() to ExchangeService - Update routes/exchange.py to use ExchangeService.get_available_slots() - Remove all business logic from get_available_slots endpoint - Add AvailabilityRepository to ExchangeService dependencies - Add Availability and BookableSlot imports to ExchangeService - Fix import path for validate_date_in_range (use date_validation module) - Remove unused user_repo variable and import from routes/invites.py - Fix mypy error in ValidationError by adding proper type annotation
This commit is contained in:
parent
c3a501e3b2
commit
280c1e5687
12 changed files with 571 additions and 303 deletions
|
|
@ -1,26 +1,19 @@
|
|||
"""Authentication routes for register, login, logout, and current user."""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from sqlalchemy import select
|
||||
from fastapi import APIRouter, Depends, Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from auth import (
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES,
|
||||
COOKIE_NAME,
|
||||
COOKIE_SECURE,
|
||||
authenticate_user,
|
||||
build_user_response,
|
||||
create_access_token,
|
||||
get_current_user,
|
||||
get_password_hash,
|
||||
get_user_by_email,
|
||||
)
|
||||
from database import get_db
|
||||
from invite_utils import normalize_identifier
|
||||
from models import ROLE_REGULAR, Invite, InviteStatus, Role, User
|
||||
from models import User
|
||||
from schemas import RegisterWithInvite, UserLogin, UserResponse
|
||||
from services.auth import AuthService
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||
|
||||
|
|
@ -37,12 +30,6 @@ def set_auth_cookie(response: Response, token: str) -> None:
|
|||
)
|
||||
|
||||
|
||||
async def get_default_role(db: AsyncSession) -> Role | None:
|
||||
"""Get the default 'regular' role for new users."""
|
||||
result = await db.execute(select(Role).where(Role.name == ROLE_REGULAR))
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
@router.post("/register", response_model=UserResponse)
|
||||
async def register(
|
||||
user_data: RegisterWithInvite,
|
||||
|
|
@ -50,51 +37,13 @@ async def register(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
) -> UserResponse:
|
||||
"""Register a new user using an invite code."""
|
||||
# Validate invite
|
||||
normalized_identifier = normalize_identifier(user_data.invite_identifier)
|
||||
query = select(Invite).where(Invite.identifier == normalized_identifier)
|
||||
result = await db.execute(query)
|
||||
invite = result.scalar_one_or_none()
|
||||
|
||||
# Return same error for not found, spent, and revoked to avoid information leakage
|
||||
if not invite or invite.status in (InviteStatus.SPENT, InviteStatus.REVOKED):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid invite code",
|
||||
)
|
||||
|
||||
# Check email not already taken
|
||||
existing_user = await get_user_by_email(db, user_data.email)
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email already registered",
|
||||
)
|
||||
|
||||
# Create user with godfather
|
||||
user = User(
|
||||
service = AuthService(db)
|
||||
user, access_token = await service.register_user(
|
||||
email=user_data.email,
|
||||
hashed_password=get_password_hash(user_data.password),
|
||||
godfather_id=invite.godfather_id,
|
||||
password=user_data.password,
|
||||
invite_identifier=user_data.invite_identifier,
|
||||
)
|
||||
|
||||
# Assign default role
|
||||
default_role = await get_default_role(db)
|
||||
if default_role:
|
||||
user.roles.append(default_role)
|
||||
|
||||
db.add(user)
|
||||
await db.flush() # Get user ID
|
||||
|
||||
# Mark invite as spent
|
||||
invite.status = InviteStatus.SPENT
|
||||
invite.used_by_id = user.id
|
||||
invite.spent_at = datetime.now(UTC)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
access_token = create_access_token(data={"sub": str(user.id)})
|
||||
set_auth_cookie(response, access_token)
|
||||
return await build_user_response(user, db)
|
||||
|
||||
|
|
@ -106,14 +55,11 @@ async def login(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
) -> UserResponse:
|
||||
"""Authenticate a user and return their info with an auth cookie."""
|
||||
user = await authenticate_user(db, user_data.email, user_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
)
|
||||
service = AuthService(db)
|
||||
user, access_token = await service.login_user(
|
||||
email=user_data.email, password=user_data.password
|
||||
)
|
||||
|
||||
access_token = create_access_token(data={"sub": str(user.id)})
|
||||
set_auth_cookie(response, access_token)
|
||||
return await build_user_response(user, db)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue