69 lines
2.4 KiB
Python
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): ... |