Coverage for app / backend / src / couchers / rate_limits / definitions.py: 100%
18 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 09:44 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 09:44 +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.recipient_user_id.label("host ID"),
43 User.gender.label("host gender"),
44 User.username.label("host username"),
45 HostRequest.status,
46 User.city.label("host city"),
47 )
48 .join(Conversation, HostRequest.conversation_id == Conversation.id)
49 .join(User, HostRequest.recipient_user_id == User.id)
50 .where(HostRequest.initiator_user_id == user_id)
51 .where(Conversation.created >= now() - RATE_LIMIT_INTERVAL)
52 )
53 .mappings()
54 .all()
55 )
58def _get_user_friend_requests_in_past_time_interval(session: Session, user_id: int) -> Sequence[RowMapping]:
59 return (
60 session.execute(
61 select(
62 FriendRelationship.time_sent,
63 User.id.label("recipient ID"),
64 User.gender.label("recipient gender"),
65 User.username.label("recipient username"),
66 FriendRelationship.status,
67 User.city.label("recipient city"),
68 )
69 .join(User, FriendRelationship.to_user_id == User.id)
70 .where(FriendRelationship.from_user_id == user_id)
71 .where(FriendRelationship.time_sent >= now() - RATE_LIMIT_INTERVAL)
72 )
73 .mappings()
74 .all()
75 )
78def _get_user_initiated_chats_in_past_time_interval(session: Session, user_id: int) -> Sequence[RowMapping]:
79 return (
80 session.execute(
81 select(
82 Conversation.id,
83 Conversation.created,
84 GroupChat.title,
85 GroupChat.is_dm,
86 func.array_agg(User.username).label("participants"),
87 func.array_agg(User.gender).label("participants genders"),
88 func.array_agg(User.city).label("participants cities"),
89 )
90 .join(Conversation, GroupChat.conversation_id == Conversation.id)
91 .join(GroupChatSubscription, Conversation.id == GroupChatSubscription.group_chat_id)
92 .join(User, GroupChatSubscription.user_id == User.id)
93 .where(GroupChat.creator_id == user_id)
94 .where(Conversation.created >= now() - RATE_LIMIT_INTERVAL)
95 .where(GroupChatSubscription.left == None)
96 .group_by(Conversation.id, Conversation.created, GroupChat.title, GroupChat.is_dm)
97 .where(User.id != user_id)
98 )
99 .mappings()
100 .all()
101 )
104RATE_LIMIT_DEFINITIONS = {
105 RateLimitAction.host_request: RateLimitDefinition(
106 warning_limit=20,
107 hard_limit=80,
108 count_actions_query=lambda session, user_id: session.execute(
109 select(func.count())
110 .select_from(HostRequest)
111 .join(Conversation, HostRequest.conversation_id == Conversation.id)
112 .where(HostRequest.initiator_user_id == user_id)
113 .where(Conversation.created >= now() - RATE_LIMIT_INTERVAL)
114 ).scalar_one(),
115 mod_email_information_query=_get_user_host_requests_in_past_time_interval,
116 ),
117 RateLimitAction.friend_request: RateLimitDefinition(
118 warning_limit=10,
119 hard_limit=40,
120 count_actions_query=lambda session, user_id: session.execute(
121 select(func.count())
122 .select_from(FriendRelationship)
123 .where(FriendRelationship.from_user_id == user_id)
124 .where(FriendRelationship.time_sent >= now() - RATE_LIMIT_INTERVAL)
125 ).scalar_one(),
126 mod_email_information_query=_get_user_friend_requests_in_past_time_interval,
127 ),
128 RateLimitAction.chat_initiation: RateLimitDefinition(
129 warning_limit=15,
130 hard_limit=150,
131 count_actions_query=lambda session, user_id: session.execute(
132 select(func.count())
133 .select_from(GroupChat)
134 .join(Conversation, GroupChat.conversation_id == Conversation.id)
135 .where(GroupChat.creator_id == user_id)
136 .where(Conversation.created >= now() - RATE_LIMIT_INTERVAL)
137 ).scalar_one(),
138 mod_email_information_query=_get_user_initiated_chats_in_past_time_interval,
139 ),
140}