Coverage for src/couchers/rate_limits/definitions.py: 100%
22 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-20 11:53 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-20 11:53 +0000
1"""Rate limit definitions:
2In order to add a new rate limit definition, extend RateLimitAction and RATE_LIMIT_DEFINITIONS and call
3rate_limits.check.process_rate_limits_and_check_abort in the relevant endpoint.
4"""
6from collections.abc import Callable, Sequence
7from dataclasses import dataclass
8from datetime import timedelta
10from sqlalchemy import RowMapping, func, select
11from sqlalchemy.orm import Session
13from couchers.models import (
14 Conversation,
15 FriendRelationship,
16 GroupChat,
17 GroupChatSubscription,
18 HostRequest,
19 RateLimitAction,
20 User,
21)
22from couchers.utils import now
25@dataclass
26class RateLimitDefinition:
27 warning_limit: int
28 hard_limit: int
29 count_actions_query: Callable[[Session, int], int]
30 mod_email_information_query: Callable[[Session, int], Sequence[RowMapping]]
33RATE_LIMIT_HOURS = 24
34RATE_LIMIT_INTERVAL = timedelta(hours=RATE_LIMIT_HOURS)
37def _get_user_host_requests_in_past_time_interval(session: Session, user_id: int) -> Sequence[RowMapping]:
38 return (
39 session.execute(
40 select(
41 Conversation.created.label("created"),
42 HostRequest.host_user_id.label("host ID"),
43 User.username.label("host username"),
44 User.city.label("host city"),
45 )
46 .join(Conversation, HostRequest.conversation_id == Conversation.id)
47 .join(User, HostRequest.host_user_id == User.id)
48 .where(HostRequest.surfer_user_id == user_id)
49 .where(Conversation.created >= now() - RATE_LIMIT_INTERVAL)
50 )
51 .mappings()
52 .all()
53 )
56def _get_user_friend_requests_in_past_time_interval(session: Session, user_id: int) -> Sequence[RowMapping]:
57 return (
58 session.execute(
59 select(
60 FriendRelationship.time_sent,
61 User.id.label("recipient ID"),
62 User.username.label("recipient username"),
63 FriendRelationship.status,
64 User.city.label("recipient city"),
65 )
66 .join(User, FriendRelationship.to_user_id == User.id)
67 .where(FriendRelationship.from_user_id == user_id)
68 .where(FriendRelationship.time_sent >= now() - RATE_LIMIT_INTERVAL)
69 )
70 .mappings()
71 .all()
72 )
75def _get_user_initiated_chats_in_past_time_interval(session: Session, user_id: int) -> Sequence[RowMapping]:
76 return (
77 session.execute(
78 select(
79 Conversation.id,
80 Conversation.created,
81 GroupChat.title,
82 GroupChat.is_dm,
83 func.array_agg(User.username).label("participants"),
84 func.array_agg(User.city).label("participants cities"),
85 )
86 .join(Conversation, GroupChat.conversation_id == Conversation.id)
87 .join(GroupChatSubscription, Conversation.id == GroupChatSubscription.group_chat_id)
88 .join(User, GroupChatSubscription.user_id == User.id)
89 .where(GroupChat.creator_id == user_id)
90 .where(Conversation.created >= now() - RATE_LIMIT_INTERVAL)
91 .where(GroupChatSubscription.left == None)
92 .group_by(Conversation.id, Conversation.created, GroupChat.title, GroupChat.is_dm)
93 .where(User.id != user_id)
94 )
95 .mappings()
96 .all()
97 )
100RATE_LIMIT_DEFINITIONS = {
101 RateLimitAction.host_request: RateLimitDefinition(
102 warning_limit=20,
103 hard_limit=80,
104 count_actions_query=lambda session, user_id: session.execute(
105 select(func.count())
106 .select_from(HostRequest)
107 .join(Conversation, HostRequest.conversation_id == Conversation.id)
108 .where(HostRequest.surfer_user_id == user_id)
109 .where(Conversation.created >= now() - RATE_LIMIT_INTERVAL)
110 ).scalar_one(),
111 mod_email_information_query=_get_user_host_requests_in_past_time_interval,
112 ),
113 RateLimitAction.friend_request: RateLimitDefinition(
114 warning_limit=10,
115 hard_limit=40,
116 count_actions_query=lambda session, user_id: session.execute(
117 select(func.count())
118 .select_from(FriendRelationship)
119 .where(FriendRelationship.from_user_id == user_id)
120 .where(FriendRelationship.time_sent >= now() - RATE_LIMIT_INTERVAL)
121 ).scalar_one(),
122 mod_email_information_query=_get_user_friend_requests_in_past_time_interval,
123 ),
124 RateLimitAction.chat_initiation: RateLimitDefinition(
125 warning_limit=15,
126 hard_limit=150,
127 count_actions_query=lambda session, user_id: session.execute(
128 select(func.count())
129 .select_from(GroupChat)
130 .join(Conversation, GroupChat.conversation_id == Conversation.id)
131 .where(GroupChat.creator_id == user_id)
132 .where(Conversation.created >= now() - RATE_LIMIT_INTERVAL)
133 ).scalar_one(),
134 mod_email_information_query=_get_user_initiated_chats_in_past_time_interval,
135 ),
136}