refactors

This commit is contained in:
counterweight 2025-12-25 00:59:57 +01:00
parent 139a5fbef3
commit f46d2ae8b3
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
12 changed files with 734 additions and 536 deletions

View file

@ -9,11 +9,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from auth import require_permission
from database import get_db
from exceptions import BadRequestError, NotFoundError
from invite_utils import (
generate_invite_identifier,
is_valid_identifier_format,
normalize_identifier,
)
from mappers import InviteMapper
from models import Invite, InviteStatus, Permission, User
from pagination import calculate_offset, create_paginated_response
from schemas import (
@ -31,22 +33,6 @@ admin_router = APIRouter(prefix="/api/admin", tags=["admin"])
MAX_INVITE_COLLISION_RETRIES = 3
def _to_invite_response(invite: Invite) -> InviteResponse:
"""Build an InviteResponse from an Invite with loaded relationships."""
return InviteResponse(
id=invite.id,
identifier=invite.identifier,
godfather_id=invite.godfather_id,
godfather_email=invite.godfather.email,
status=invite.status.value,
used_by_id=invite.used_by_id,
used_by_email=invite.used_by.email if invite.used_by else None,
created_at=invite.created_at,
spent_at=invite.spent_at,
revoked_at=invite.revoked_at,
)
@router.get("/{identifier}/check", response_model=InviteCheckResponse)
async def check_invite(
identifier: str,
@ -118,10 +104,7 @@ async def create_invite(
result = await db.execute(select(User.id).where(User.id == data.godfather_id))
godfather_id = result.scalar_one_or_none()
if not godfather_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Godfather user not found",
)
raise BadRequestError("Godfather user not found")
# Try to create invite with retry on collision
invite: Invite | None = None
@ -150,7 +133,7 @@ async def create_invite(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to create invite",
)
return _to_invite_response(invite)
return InviteMapper.to_response(invite)
@admin_router.get("/invites", response_model=PaginatedInviteRecords)
@ -197,7 +180,7 @@ async def list_all_invites(
invites = result.scalars().all()
# Build responses using preloaded relationships
records = [_to_invite_response(invite) for invite in invites]
records = [InviteMapper.to_response(invite) for invite in invites]
return create_paginated_response(records, total, page, per_page)
@ -213,16 +196,12 @@ async def revoke_invite(
invite = result.scalar_one_or_none()
if not invite:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Invite not found",
)
raise NotFoundError("Invite")
if invite.status != InviteStatus.READY:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Cannot revoke invite with status '{invite.status.value}'. "
"Only READY invites can be revoked.",
raise BadRequestError(
f"Cannot revoke invite with status '{invite.status.value}'. "
"Only READY invites can be revoked."
)
invite.status = InviteStatus.REVOKED
@ -230,7 +209,7 @@ async def revoke_invite(
await db.commit()
await db.refresh(invite)
return _to_invite_response(invite)
return InviteMapper.to_response(invite)
# All routers from this module for easy registration