[docs]asyncdefcheck_rate_limit(user_id:int,action_type:str)->Tuple[bool,str]:"""Check if user action is within rate limits. :param user_id: Internal user ID (not telegram_id) :param action_type: Type of action being performed :returns: Tuple of (allowed: bool, reason: str) """now=datetime.now()# Rate limits by action typelimits={"task_create":{"minute":2,"hour":10,"day":50},"command":{"minute":10,"hour":100,"day":500},"message":{"minute":20,"hour":200,"day":1000},}action_limits=limits.get(action_type,limits["message"])asyncwithSessionLocal()assession:result=awaitsession.execute(select(RateLimitRecord).where(and_(RateLimitRecord.user_id==user_id,RateLimitRecord.action_type==action_type,)))record=result.scalar_one_or_none()ifrecordisNone:# Create new rate limit recordrecord=RateLimitRecord(user_id=user_id,action_type=action_type,count_per_minute=1,count_per_hour=1,count_per_day=1,)session.add(record)awaitsession.commit()returnTrue,"OK"# Reset counters if time windows have passedif(now-record.minute_reset_at).total_seconds()>=60:record.count_per_minute=0record.minute_reset_at=nowif(now-record.hour_reset_at).total_seconds()>=3600:record.count_per_hour=0record.hour_reset_at=nowif(now-record.day_reset_at).total_seconds()>=86400:record.count_per_day=0record.day_reset_at=now# Check limitsifrecord.count_per_minute>=action_limits["minute"]:return(False,f"Rate limit exceeded: {action_limits['minute']}{action_type} per minute",)ifrecord.count_per_hour>=action_limits["hour"]:return(False,f"Rate limit exceeded: {action_limits['hour']}{action_type} per hour",)ifrecord.count_per_day>=action_limits["day"]:return(False,f"Rate limit exceeded: {action_limits['day']}{action_type} per day",)# Increment countersrecord.count_per_minute+=1record.count_per_hour+=1record.count_per_day+=1record.last_action_at=nowrecord.updated_at=nowawaitsession.commit()returnTrue,"OK"