Initial commit: CI Agent competitive-intelligence monitoring app
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).
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.enums import ThrottleAction
|
||||
from app.models.ip_ban import IpBan
|
||||
from app.models.ip_throttle_state import IpThrottleState
|
||||
|
||||
|
||||
class IpThrottleRepository:
|
||||
def __init__(self, db: AsyncSession) -> None:
|
||||
self.db = db
|
||||
|
||||
async def get_state(self, ip_address: str, action: ThrottleAction) -> IpThrottleState | None:
|
||||
result = await self.db.execute(
|
||||
select(IpThrottleState).where(
|
||||
IpThrottleState.ip_address == ip_address, IpThrottleState.action == action
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_or_create_state(self, ip_address: str, action: ThrottleAction) -> IpThrottleState:
|
||||
state = await self.get_state(ip_address, action)
|
||||
if state is not None:
|
||||
return state
|
||||
state = IpThrottleState(ip_address=ip_address, action=action)
|
||||
self.db.add(state)
|
||||
await self.db.flush()
|
||||
return state
|
||||
|
||||
async def get_ban(self, ip_address: str) -> IpBan | None:
|
||||
result = await self.db.execute(select(IpBan).where(IpBan.ip_address == ip_address))
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def create_ban(self, ip_address: str, reason: str, banned_at: datetime) -> IpBan:
|
||||
ban = IpBan(ip_address=ip_address, reason=reason, banned_at=banned_at)
|
||||
self.db.add(ban)
|
||||
await self.db.flush()
|
||||
return ban
|
||||
|
||||
async def list_bans(self) -> list[IpBan]:
|
||||
result = await self.db.execute(select(IpBan).order_by(IpBan.banned_at.desc()))
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def clear_ban_and_state(self, ip_address: str) -> bool:
|
||||
"""Full pardon for an admin-approved unban - removes the ban record
|
||||
and resets every throttle-state row for this IP to a clean slate
|
||||
(not just lifting the terminal ban while leaving them one offense
|
||||
from another)."""
|
||||
ban_result = await self.db.execute(delete(IpBan).where(IpBan.ip_address == ip_address))
|
||||
result = await self.db.execute(
|
||||
select(IpThrottleState).where(IpThrottleState.ip_address == ip_address)
|
||||
)
|
||||
states = result.scalars().all()
|
||||
for state in states:
|
||||
state.attempt_count = 0
|
||||
state.next_allowed_at = None
|
||||
state.timeout_until = None
|
||||
state.offense_count = 0
|
||||
await self.db.flush()
|
||||
return ban_result.rowcount > 0 or len(states) > 0
|
||||
Reference in New Issue
Block a user