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