# 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): ...