Coverage for src/couchers/tasks.py: 97%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import logging
2from datetime import timedelta
3from typing import List
5from sqlalchemy.sql import func
7from couchers import email, urls
8from couchers.config import config
9from couchers.constants import SIGNUP_EMAIL_TOKEN_VALIDITY
10from couchers.crypto import urlsafe_secure_token
11from couchers.db import session_scope
12from couchers.models import (
13 AccountDeletionToken,
14 ClusterRole,
15 ClusterSubscription,
16 LoginToken,
17 Node,
18 Notification,
19 PasswordResetToken,
20 User,
21)
22from couchers.notifications.unsubscribe import generate_mute_all, generate_unsub_topic_action, generate_unsub_topic_key
23from couchers.sql import couchers_select as select
24from couchers.utils import now
26logger = logging.getLogger(__name__)
29def send_signup_email(flow):
30 logger.info(f"Sending signup email to {flow.email=}:")
32 # whether we've sent an email at all yet
33 email_sent_before = flow.email_sent
34 if flow.email_verified:
35 # we just send a link to continue, not a verification link
36 signup_link = urls.signup_link(token=flow.flow_token)
37 elif flow.email_token and flow.token_is_valid:
38 # if the verification email was sent and still is not expired, just resend the verification email
39 signup_link = urls.signup_link(token=flow.email_token)
40 else:
41 # otherwise send a fresh email with new token
42 token = urlsafe_secure_token()
43 flow.email_verified = False
44 flow.email_token = token
45 flow.email_token_expiry = now() + SIGNUP_EMAIL_TOKEN_VALIDITY
46 signup_link = urls.signup_link(token=flow.email_token)
48 flow.email_sent = True
50 logger.info(f"Link is: {signup_link}")
51 template = "signup_verify" if not email_sent_before else "signup_continue"
52 email.enqueue_email_from_template(flow.email, template, template_args={"flow": flow, "signup_link": signup_link})
55def send_login_email(session, user):
56 login_token = LoginToken(token=urlsafe_secure_token(), user=user, expiry=now() + timedelta(hours=2))
57 session.add(login_token)
59 logger.info(f"Sending login email to {user=}:")
60 logger.info(f"Email for {user.username=} to {user.email=}")
61 logger.info(f"Token: {login_token=} ({login_token.created=}")
62 login_link = urls.login_link(login_token=login_token.token)
63 logger.info(f"Link is: {login_link}")
64 email.enqueue_email_from_template(user.email, "login", template_args={"user": user, "login_link": login_link})
66 return login_token
69def send_api_key_email(session, user, token, expiry):
70 logger.info(f"Sending API key email to {user=}:")
71 email.enqueue_email_from_template(
72 user.email, "api_key", template_args={"user": user, "token": token, "expiry": expiry}
73 )
76def send_password_reset_email(session, user):
77 password_reset_token = PasswordResetToken(
78 token=urlsafe_secure_token(), user=user, expiry=now() + timedelta(hours=2)
79 )
80 session.add(password_reset_token)
82 logger.info(f"Sending password reset email to {user=}:")
83 password_reset_link = urls.password_reset_link(password_reset_token=password_reset_token.token)
84 logger.info(f"Link is: {password_reset_link}")
85 email.enqueue_email_from_template(
86 user.email, "password_reset", template_args={"user": user, "password_reset_link": password_reset_link}
87 )
89 return password_reset_token
92def send_new_host_request_email(host_request):
93 logger.info(f"Sending host request email to {host_request.host=}:")
94 logger.info(f"Host request sent by {host_request.surfer}")
95 logger.info(f"Email for {host_request.host.username=} sent to {host_request.host.email=}")
97 email.enqueue_email_from_template(
98 host_request.host.email,
99 "host_request",
100 template_args={
101 "host_request": host_request,
102 "host_request_link": urls.host_request_link_host(),
103 },
104 )
107def send_host_request_accepted_email_to_guest(host_request):
108 logger.info(f"Sending host request accepted email to guest: {host_request.surfer=}:")
109 logger.info(f"Email for {host_request.surfer.username=} sent to {host_request.surfer.email=}")
111 email.enqueue_email_from_template(
112 host_request.surfer.email,
113 "host_request_accepted_guest",
114 template_args={
115 "host_request": host_request,
116 "host_request_link": urls.host_request_link_guest(),
117 },
118 )
121def send_host_request_rejected_email_to_guest(host_request):
122 logger.info(f"Sending host request rejected email to guest: {host_request.surfer=}:")
123 logger.info(f"Email for {host_request.surfer.username=} sent to {host_request.surfer.email=}")
125 email.enqueue_email_from_template(
126 host_request.surfer.email,
127 "host_request_rejected_guest",
128 template_args={
129 "host_request": host_request,
130 "host_request_link": urls.host_request_link_guest(),
131 },
132 )
135def send_host_request_confirmed_email_to_host(host_request):
136 logger.info(f"Sending host request confirmed email to host: {host_request.host=}:")
137 logger.info(f"Email for {host_request.host.username=} sent to {host_request.host.email=}")
139 email.enqueue_email_from_template(
140 host_request.host.email,
141 "host_request_confirmed_host",
142 template_args={
143 "host_request": host_request,
144 "host_request_link": urls.host_request_link_host(),
145 },
146 )
149def send_host_request_cancelled_email_to_host(host_request):
150 logger.info(f"Sending host request cancelled email to host: {host_request.host=}:")
151 logger.info(f"Email for {host_request.host.username=} sent to {host_request.host.email=}")
153 email.enqueue_email_from_template(
154 host_request.host.email,
155 "host_request_cancelled_host",
156 template_args={
157 "host_request": host_request,
158 "host_request_link": urls.host_request_link_host(),
159 },
160 )
163def send_friend_request_email(friend_relationship):
164 friend_requests_link = urls.friend_requests_link()
166 logger.info(f"Sending friend request email to {friend_relationship.to_user=}:")
167 logger.info(f"Email for {friend_relationship.to_user.username=} sent to {friend_relationship.to_user.email=}")
168 logger.info(f"Friend request sent by {friend_relationship.from_user.username=}")
170 email.enqueue_email_from_template(
171 friend_relationship.to_user.email,
172 "friend_request",
173 template_args={
174 "friend_relationship": friend_relationship,
175 "friend_requests_link": friend_requests_link,
176 },
177 )
180def send_friend_request_accepted_email(friend_relationship):
181 logger.info(f"Sending friend request acceptance email to {friend_relationship.from_user=}:")
182 logger.info(f"Email for {friend_relationship.from_user.username=} sent to {friend_relationship.from_user.email=}")
184 email.enqueue_email_from_template(
185 friend_relationship.from_user.email,
186 "friend_request_accepted",
187 template_args={
188 "friend_relationship": friend_relationship,
189 "to_user_user_link": urls.user_link(username=friend_relationship.to_user.username),
190 },
191 )
194def send_host_reference_email(reference, both_written):
195 """
196 both_written == true if both the surfer and hoster wrote a reference
197 """
198 assert reference.host_request_id
200 logger.info(f"Sending host reference email to {reference.to_user=} for {reference.id=}")
202 surfed = reference.host_request.surfer_user_id != reference.from_user_id
204 email.enqueue_email_from_template(
205 reference.to_user.email,
206 "host_reference",
207 template_args={
208 "reference": reference,
209 "leave_reference_link": urls.leave_reference_link(
210 reference_type="surfed" if surfed else "hosted",
211 to_user_id=reference.from_user_id,
212 host_request_id=reference.host_request.conversation_id,
213 ),
214 # if this reference was written by the surfer, then the recipient hosted
215 "surfed": surfed,
216 "both_written": both_written,
217 },
218 )
221def send_friend_reference_email(reference):
222 assert not reference.host_request_id
224 logger.info(f"Sending friend reference email to {reference.to_user=} for {reference.id=}")
226 email.enqueue_email_from_template(
227 reference.to_user.email,
228 "friend_reference",
229 template_args={
230 "reference": reference,
231 },
232 )
235def send_reference_reminder_email(user, other_user, host_request, surfed, time_left_text):
236 logger.info(f"Sending host reference email to {user=}, they have {time_left_text} left to write a ref")
238 email.enqueue_email_from_template(
239 user.email,
240 "reference_reminder",
241 template_args={
242 "user": user,
243 "other_user": other_user,
244 "host_request": host_request,
245 "leave_reference_link": urls.leave_reference_link(
246 reference_type="surfed" if surfed else "hosted",
247 to_user_id=other_user.id,
248 host_request_id=host_request.conversation_id,
249 ),
250 "surfed": surfed,
251 "time_left_text": time_left_text,
252 },
253 )
256def send_password_changed_email(user):
257 """
258 Send the user an email saying their password has been changed.
259 """
260 logger.info(f"Sending password changed (notification) email to {user=}")
261 email.enqueue_email_from_template(user.email, "password_changed", template_args={"user": user})
264def send_email_changed_notification_email(user):
265 """
266 Send an email to user's original address notifying that it has been changed
267 """
268 logger.info(
269 f"Sending email changed (notification) email to {user=} (old email: {user.email=}, new email: {user.new_email=})"
270 )
271 email.enqueue_email_from_template(user.email, "email_changed_notification", template_args={"user": user})
274def send_email_changed_confirmation_to_old_email(user):
275 """
276 Send an email to user's original email address requesting confirmation of email change
277 """
278 logger.info(
279 f"Sending email changed (confirmation) email to {user=}'s old email address, (old email: {user.email}, new email: {user.new_email=})"
280 )
282 confirmation_link = urls.change_email_link(confirmation_token=user.old_email_token)
283 email.enqueue_email_from_template(
284 user.email,
285 "email_changed_confirmation_old_email",
286 template_args={"user": user, "confirmation_link": confirmation_link},
287 )
290def send_email_changed_confirmation_to_new_email(user):
291 """
292 Send an email to user's new email address requesting confirmation of email change
293 """
294 logger.info(
295 f"Sending email changed (confirmation) email to {user=}'s new email address, (old email: {user.email}, new email: {user.new_email=})"
296 )
298 confirmation_link = urls.change_email_link(confirmation_token=user.new_email_token)
299 email.enqueue_email_from_template(
300 user.new_email,
301 "email_changed_confirmation_new_email",
302 template_args={"user": user, "confirmation_link": confirmation_link},
303 )
306def send_onboarding_email(user, email_number):
307 email.enqueue_email_from_template(
308 user.email,
309 f"onboarding{email_number}",
310 template_args={
311 "user": user,
312 "app_link": urls.app_link(),
313 "profile_link": urls.profile_link(),
314 "edit_profile_link": urls.edit_profile_link(),
315 },
316 )
319def send_donation_email(user, amount, receipt_url):
320 email.enqueue_email_from_template(
321 user.email,
322 "donation_received",
323 template_args={"user": user, "amount": amount, "receipt_url": receipt_url},
324 )
327def send_content_report_email(content_report):
328 target_email = config["REPORTS_EMAIL_RECIPIENT"]
330 logger.info(f"Sending content report email to {target_email=}")
331 email.enqueue_email_from_template(
332 target_email,
333 "content_report",
334 template_args={
335 "report": content_report,
336 "author_user_user_link": urls.user_link(username=content_report.author_user.username),
337 "reporting_user_user_link": urls.user_link(username=content_report.reporting_user.username),
338 },
339 )
342def maybe_send_reference_report_email(reference):
343 target_email = config["REPORTS_EMAIL_RECIPIENT"]
345 if reference.should_report:
346 logger.info(f"Sending reference report email to {target_email=}")
347 email.enqueue_email_from_template(
348 target_email,
349 "reference_report",
350 template_args={
351 "reference": reference,
352 "from_user_user_link": urls.user_link(username=reference.from_user.username),
353 "to_user_user_link": urls.user_link(username=reference.to_user.username),
354 },
355 )
358def maybe_send_contributor_form_email(form):
359 target_email = config["CONTRIBUTOR_FORM_EMAIL_RECIPIENT"]
361 if form.should_notify:
362 email.enqueue_email_from_template(
363 target_email,
364 "contributor_form",
365 template_args={"form": form, "user_link": urls.user_link(username=form.user.username)},
366 )
369def send_digest_email(notifications: List[Notification]):
370 logger.info(f"Sending digest email to {notification.user=}:")
371 email.enqueue_email_from_template(
372 notification.user.email,
373 "digest",
374 template_args={"notifications": notifications},
375 )
378def send_notification_email(notification: Notification):
379 friend_requests_link = urls.friend_requests_link()
380 logger.info(f"Sending notification email to {notification.user=}:")
382 email.enqueue_email_from_template(
383 notification.user.email,
384 "notification",
385 template_args={
386 "notification": notification,
387 "unsub_all": generate_mute_all(notification.user_id),
388 "unsub_topic_key": generate_unsub_topic_key(notification),
389 "unsub_topic_action": generate_unsub_topic_action(notification),
390 },
391 )
394def enforce_community_memberships():
395 """
396 Go through all communities and make sure every user in the polygon is also a member
397 """
398 with session_scope() as session:
399 for node in session.execute(select(Node)).scalars().all():
400 existing_users = select(ClusterSubscription.user_id).where(
401 ClusterSubscription.cluster == node.official_cluster
402 )
403 users_needing_adding = (
404 session.execute(
405 select(User)
406 .where(User.is_visible)
407 .where(func.ST_Contains(node.geom, User.geom))
408 .where(~User.id.in_(existing_users))
409 )
410 .scalars()
411 .all()
412 )
413 for user in users_needing_adding:
414 node.official_cluster.cluster_subscriptions.append(
415 ClusterSubscription(
416 user=user,
417 role=ClusterRole.member,
418 )
419 )
420 session.commit()
423def send_account_deletion_confirmation_email(user):
424 logger.info(f"Sending account deletion confirmation email to {user=}.")
425 logger.info(f"Email for {user.username=} sent to {user.email}.")
426 token = AccountDeletionToken(token=urlsafe_secure_token(), user=user, expiry=now() + timedelta(hours=2))
427 deletion_link = urls.delete_account_link(account_deletion_token=token.token)
428 email.enqueue_email_from_template(
429 user.email,
430 "account_deletion_confirmation",
431 template_args={"user": user, "deletion_link": deletion_link},
432 )
434 return token
437def send_account_deletion_successful_email(user, undelete_days):
438 logger.info(f"Sending account deletion successful email to {user=}.")
439 logger.info(f"Email for {user.username=} sent to {user.email}.")
440 undelete_link = urls.recover_account_link(account_undelete_token=user.undelete_token)
441 email.enqueue_email_from_template(
442 user.email,
443 "account_deletion_successful",
444 template_args={"user": user, "undelete_link": undelete_link, "days": undelete_days},
445 )
448def send_account_recovered_email(user):
449 logger.info(f"Sending account recovered successful email to {user=}.")
450 logger.info(f"Email for {user.username=} sent to {user.email}.")
451 email.enqueue_email_from_template(
452 user.email,
453 "account_recovered_successful",
454 template_args={"user": user, "app_link": urls.app_link()},
455 )
458def send_account_deletion_report_email(reason):
459 target_email = config["REPORTS_EMAIL_RECIPIENT"]
461 logger.info(f"Sending account deletion report email to {target_email=}")
462 email.enqueue_email_from_template(
463 target_email,
464 "account_deletion_report",
465 template_args={
466 "reason": reason,
467 },
468 )
471def enforce_community_memberships_for_user(session, user):
472 """
473 Adds a given user to all the communities they belong in based on their location.
474 """
475 nodes = session.execute(select(Node).where(func.ST_Contains(Node.geom, user.geom))).scalars().all()
476 for node in nodes:
477 node.official_cluster.cluster_subscriptions.append(
478 ClusterSubscription(
479 user=user,
480 role=ClusterRole.member,
481 )
482 )
483 session.commit()