Coverage for app/backend/src/couchers/constants.py: 100%
59 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +0000
1from datetime import UTC, datetime, timedelta
3# terms of service version
4TOS_VERSION = 2
6# community guidelines version
7GUIDELINES_VERSION = 1
9# When updating this, also update the "activeness_probe" notification strings in
10# src/couchers/email/locales/en.json
11LATEST_RELEASE_BLOG_URL = "https://couchers.org/blog/2026/05/25/couchers-spring-release"
13# Keep the frontend in sync at app/web/utils/validation.ts
14EMAIL_REGEX = r"^[0-9a-z]([0-9a-z\-\_\+]|(\.[0-9a-z\-\_\+]))*@([0-9a-z\-]+\.)*[0-9a-z\-]+\.[a-z]{2,}$"
16# Letters, diacritics, internal spaces, quotes, dashes, commas, dots, and's for names. See tests!
17# The surrounding whitespace rule could be encoded using a lookbehind assertion,
18# but the frontend counterpart would not be supported on older browsers (Safari < 16.4).
19# Keep the frontend in sync at app/web/utils/validation.ts
20VALID_NAME_CHARACTERS_REGEX = r"""^[\p{L}\p{M}\p{Zs}\p{Pi}\p{Pf}\p{Pd},.'"·・&/|]+$"""
21VALID_NAME_NO_SURROUNDING_WHITESPACE_REGEX = r"^\P{Zs}(?:[\s\S]*\P{Zs})?$"
22VALID_NAME_MIN_LENGTH = 2
23VALID_NAME_MAX_LENGTH = 100
25# Keep the frontend in sync at app/web/utils/validation.ts
26VALID_USERNAME_REGEX = r"^[a-z][0-9a-z_]*[a-z0-9]$"
28BANNED_USERNAME_PHRASES = [
29 "admin",
30 "bot",
31 "safety",
32 "security",
33 "secure",
34 "trust",
35 "couchers",
36 "help",
37 "moderation",
38 "moderator",
39 "noreply",
40 "official",
41 "security",
42 "staff",
43 "support",
44 "system",
45 "team",
46 "verify",
47]
49# expiry time for a verified phone number
50PHONE_VERIFICATION_LIFETIME = timedelta(days=2 * 365)
52# shortest period between phone verification code requests
53PHONE_REVERIFICATION_INTERVAL = timedelta(days=2)
55# expiry time for an sms code
56SMS_CODE_LIFETIME = timedelta(hours=24)
58# max attempts to enter the sms code
59SMS_CODE_ATTEMPTS = 3
61# Postal verification constants
62POSTAL_VERIFICATION_CODE_LENGTH = 6
63# Reduced alphabet to avoid confusion (no I, O, 0, 1)
64POSTAL_VERIFICATION_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
65# Code valid for 90 days after postcard sent
66POSTAL_VERIFICATION_CODE_LIFETIME = timedelta(days=90)
67# Max wrong code attempts before lockout
68POSTAL_VERIFICATION_MAX_ATTEMPTS = 5
69# Can only initiate once per 30 days
70POSTAL_VERIFICATION_RATE_LIMIT = timedelta(days=30)
72SIGNUP_EMAIL_TOKEN_VALIDITY = timedelta(hours=48)
74DATETIME_MINUS_INFINITY = datetime(1, 1, 1, tzinfo=UTC)
75DATETIME_INFINITY = datetime(9876, 12, 31, hour=23, minute=59, second=59, tzinfo=UTC)
77# the api workers listen on API_BASE_PORT .. API_BASE_PORT + API_WORKER_COUNT - 1; must stay in sync with
78# proxy/envoy.yaml and docker-compose.prod.yml
79API_WORKER_COUNT = 4
80API_BASE_PORT = 1761
81MEDIA_PORT = 1753
83# per API worker process; kept small since we parallelize across processes (API_WORKER_COUNT), not threads
84SERVER_THREADS = 8
86# SQLAlchemy pool size, per process; sized for the API workers as the larger consumer, see db.py
87DB_POOL_SIZE = 2 * SERVER_THREADS + 4
89# on SIGTERM, how long to let in-flight RPCs drain before the server is forced down; kept under the
90# container's stop_grace_period (docker-compose stop_grace_period: 30s) so workers drain, not SIGKILLed
91GRACEFUL_SHUTDOWN_TIMEOUT = 5
93# how long the user has to undelete their account
94UNDELETE_DAYS = 7
96# expiry time for preferred language cookie
97PREFERRED_LANGUAGE_COOKIE_EXPIRY = timedelta(days=3650)
100# activeness probe settings
101# wait about 11 months before sending one out
102ACTIVENESS_PROBE_INACTIVITY_PERIOD = timedelta(days=333)
103# times at which to send notifications after inactivity (cumulative since start of probe)
104ACTIVENESS_PROBE_TIME_REMINDERS = [timedelta(days=0), timedelta(days=2, hours=8)]
105# total time from initiation after which to expire the probe
106ACTIVENESS_PROBE_EXPIRY_TIME = timedelta(days=4)
108# how long a message must go unseen before we email the user about it
109MISSED_MESSAGES_DELAY = timedelta(minutes=5)
110# ... unless we could reach them by push, in which case they've already been told about it once
111MISSED_MESSAGES_DELAY_WITH_PUSH = timedelta(hours=24)
113HOST_REQUEST_MAX_REMINDERS = 1
114HOST_REQUEST_REMINDER_INTERVAL = timedelta(days=2)
116HOST_REQUEST_DUPLICATE_WINDOW_HOURS = 3
118# Note: Javascript's string.length is in utf16 code units, Python's len(str) is in utf8 code units.
119HOST_REQUEST_MIN_LENGTH_UTF16 = 250 # Must match frontend
120PUBLIC_TRIP_DESCRIPTION_MIN_LENGTH_UTF16 = 150 # Must match frontend
122ANTIBOT_FREQ = timedelta(hours=48)
124EVENT_REMINDER_TIMEDELTA = timedelta(hours=24)
126COMMUNITIES_SEARCH_FUZZY_SIMILARITY_THRESHOLD = 0.35
128UNKNOWN_ERROR_MESSAGE = "An unknown backend error occurred. Please consider filing a bug!"
130# NOTE: these codes are on purpose not translatable
131NONEXISTENT_API_CALL_ERROR_MESSAGE = "API call does not exist. Please refresh and try again."
132MISSING_AUTH_LEVEL_ERROR_MESSAGE = "Internal authentication error."
133COOKIES_AND_AUTH_HEADER_ERROR_MESSAGE = 'Both "cookie" and "authorization" in request'
134CALL_CANCELLED_ERROR_MESSAGE = "Call cancelled."
136# NOTE: the frontend uses these (and error codes) to distinguish between jailed and logged out
137UNAUTHORIZED_ERROR_MESSAGE = "Unauthorized"
138PERMISSION_DENIED_ERROR_MESSAGE = "Permission denied"
140RATE_LIMIT_ERROR_MESSAGE = "Too many requests. Please slow down and try again shortly."
142GHOST_USERNAME = "ghost"
144# Photo gallery limits
145GALLERY_MAX_PHOTOS_NOT_VERIFIED = 2
146GALLERY_MAX_PHOTOS_VERIFIED = 5
148COMPLETED_PROFILE_MINIMUM_CHAR_LENGTH = 150
150# How long a container must run uninterrupted before /status reports stable=true
151STABLE_THRESHOLD_SECONDS = 5 * 60
153# window length for the API rate limiter's fixed-window counters, in seconds
154RATE_LIMIT_WINDOW_SECONDS = 60
156MODERATION_AUTO_APPROVE_FLAG_PRIORITY = 1000