FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
54 lines
1.9 KiB
Python
54 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import UTC, datetime, timedelta
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.db.base import ensure_aware_utc
|
|
from app.models.unban_request import UnbanRequest
|
|
|
|
|
|
class UnbanRequestRepository:
|
|
def __init__(self, db: AsyncSession) -> None:
|
|
self.db = db
|
|
|
|
async def get(self, request_id: uuid.UUID) -> UnbanRequest | None:
|
|
result = await self.db.execute(select(UnbanRequest).where(UnbanRequest.id == request_id))
|
|
return result.scalar_one_or_none()
|
|
|
|
async def delete(self, request_id: uuid.UUID) -> None:
|
|
request = await self.get(request_id)
|
|
if request is not None:
|
|
await self.db.delete(request)
|
|
await self.db.flush()
|
|
|
|
async def most_recent_for_ip(self, ip_address: str) -> UnbanRequest | None:
|
|
result = await self.db.execute(
|
|
select(UnbanRequest)
|
|
.where(UnbanRequest.ip_address == ip_address)
|
|
.order_by(UnbanRequest.created_at.desc())
|
|
.limit(1)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def within_cooldown(self, ip_address: str, cooldown_hours: int = 24) -> bool:
|
|
latest = await self.most_recent_for_ip(ip_address)
|
|
if latest is None:
|
|
return False
|
|
cutoff = datetime.now(UTC) - timedelta(hours=cooldown_hours)
|
|
return ensure_aware_utc(latest.created_at) > cutoff
|
|
|
|
async def create(self, ip_address: str, message: str | None) -> UnbanRequest:
|
|
record = UnbanRequest(ip_address=ip_address, message=message)
|
|
self.db.add(record)
|
|
await self.db.flush()
|
|
return record
|
|
|
|
async def list_all(self, limit: int = 100) -> list[UnbanRequest]:
|
|
result = await self.db.execute(
|
|
select(UnbanRequest).order_by(UnbanRequest.created_at.desc()).limit(limit)
|
|
)
|
|
return list(result.scalars().all())
|