doe/be/app/crud/invite.py
2025-03-30 19:42:32 +02:00

69 lines
2.4 KiB
Python

# app/crud/invite.py
import secrets
from datetime import datetime, timedelta, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy import delete # Import delete statement
from typing import Optional
from app.models import Invite as InviteModel
# Invite codes should be reasonably unique, but handle potential collision
MAX_CODE_GENERATION_ATTEMPTS = 5
async def create_invite(db: AsyncSession, group_id: int, creator_id: int, expires_in_days: int = 7) -> Optional[InviteModel]:
"""Creates a new invite code for a group."""
expires_at = datetime.now(timezone.utc) + timedelta(days=expires_in_days)
code = None
attempts = 0
# Generate a unique code, retrying if a collision occurs (highly unlikely but safe)
while attempts < MAX_CODE_GENERATION_ATTEMPTS:
attempts += 1
potential_code = secrets.token_urlsafe(16)
# Check if an *active* invite with this code already exists
existing = await db.execute(
select(InviteModel.id).where(InviteModel.code == potential_code, InviteModel.is_active == True).limit(1)
)
if existing.scalar_one_or_none() is None:
code = potential_code
break
if code is None:
# Failed to generate a unique code after several attempts
return None
db_invite = InviteModel(
code=code,
group_id=group_id,
created_by_id=creator_id,
expires_at=expires_at,
is_active=True
)
db.add(db_invite)
await db.commit()
await db.refresh(db_invite)
return db_invite
async def get_active_invite_by_code(db: AsyncSession, code: str) -> Optional[InviteModel]:
"""Gets an active and non-expired invite by its code."""
now = datetime.now(timezone.utc)
result = await db.execute(
select(InviteModel).where(
InviteModel.code == code,
InviteModel.is_active == True,
InviteModel.expires_at > now
)
)
return result.scalars().first()
async def deactivate_invite(db: AsyncSession, invite: InviteModel) -> InviteModel:
"""Marks an invite as inactive (used)."""
invite.is_active = False
db.add(invite) # Add to session to track change
await db.commit()
await db.refresh(invite)
return invite
# Optional: Function to periodically delete old, inactive invites
# async def cleanup_old_invites(db: AsyncSession, older_than_days: int = 30): ...