Coverage for functions \ flipdare \ service \ processor \ dare_email_processor.py: 36%
92 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
12from __future__ import annotations
13from flipdare.app_log import LOG
14from flipdare.constants import IS_DEBUG
15from flipdare.backend.app_logger import AppLogger
16from flipdare.mailer import AdminMailer, UserMailer
17from flipdare.firestore import DareDb, PledgeDb, UserDb, DareContext
18from flipdare.generated import BallotResult, AppErrorCode
19from flipdare.manager.db_manager import DbManager
20from flipdare.result.app_result import AppResult
21from flipdare.mailer.user.voting_email import VotingEmail
22from flipdare.wrapper import DareWrapper, GroupWrapper, UserWrapper
23from flipdare.service._email_mixin import EmailMixin
26class DareEmailProcessor(EmailMixin):
27 def __init__(
28 self,
29 db_manager: DbManager,
30 app_logger: AppLogger,
31 user_mailer: UserMailer,
32 admin_mailer: AdminMailer,
33 ) -> None:
34 self._db_manager = db_manager
35 self._user_mailer = user_mailer
36 self._admin_mailer = admin_mailer
37 self._app_logger = app_logger
39 @property
40 def user_mailer(self) -> UserMailer:
41 return self._user_mailer
43 @property
44 def admin_mailer(self) -> AdminMailer:
45 return self._admin_mailer
47 @property
48 def app_logger(self) -> AppLogger:
49 return self._app_logger
51 @property
52 def user_db(self) -> UserDb:
53 return self._db_manager.user_db
55 @property
56 def dare_db(self) -> DareDb:
57 return self._db_manager.dare_db
59 @property
60 def pledge_db(self) -> PledgeDb:
61 return self._db_manager.pledge_db
63 def notify_user_vote_start(
64 self,
65 context: DareContext,
66 ) -> AppResult[DareWrapper]:
67 return self._notify_users_vote_change(context=context, ballot_result=None)
69 def notify_user_vote_complete(
70 self,
71 context: DareContext,
72 ballot_result: BallotResult,
73 ) -> AppResult[DareWrapper]:
74 return self._notify_users_vote_change(context=context, ballot_result=ballot_result)
76 def _notify_users_vote_change( # noqa: PLR0912
77 self,
78 context: DareContext,
79 ballot_result: BallotResult | None = None,
80 ) -> AppResult[DareWrapper]:
82 dare = context.dare
83 dare_id = context.dare.doc_id
84 main_result = AppResult[DareWrapper](doc_id=dare_id, task_name="_notify_users_vote_change")
86 # send to the individual owner(s)
87 to_obj = context.to_obj
88 match to_obj:
89 case UserWrapper():
90 if ballot_result is None:
91 self._send_user_vote_start_email(to_user=to_obj, dare=dare)
92 else:
93 self._send_user_vote_complete_email(
94 to_user=to_obj,
95 dare=dare,
96 ballot_result=ballot_result,
97 )
98 case GroupWrapper():
99 if ballot_result is None:
100 self._send_group_vote_start_email(to_group=to_obj, dare=dare)
101 else:
102 self._send_group_vote_complete_email(
103 to_group=to_obj,
104 dare=dare,
105 ballot_result=ballot_result,
106 )
108 # now we need to send to all the pledgers,
109 # 1. at start of voting, so the users can vote.
110 # 2. at end of voting, so the users can see the result.
112 pledges = self.pledge_db.get_pledges_for_dare(dare_id)
113 if len(pledges) <= 0:
114 # this could be a potentially an error,
115 # but not necessarily, since some dares may not have pledges.
116 msg = f"No pledges found for dare {dare_id} during vote notification."
117 LOG().warning(msg)
118 main_result.add_warning(msg)
119 return main_result
121 if IS_DEBUG:
122 msg = f"Processing vote notification for dare {dare_id} with {len(pledges)} pledges."
123 LOG().debug(msg)
125 for pledge in pledges:
126 from_uid = pledge.from_uid
127 debug_msg = f"(user={from_uid}, dare={dare_id}, pledge={pledge.doc_id})"
129 try:
130 user = self.user_db.get(from_uid)
131 if user is None:
132 msg = f"Failed to notify user {from_uid} of vote for {debug_msg}: NOT FOUND"
133 LOG().error(msg)
134 main_result.add_error(AppErrorCode.VOTING, msg)
135 continue
137 if ballot_result is None:
138 self._send_user_vote_start_email(to_user=user, dare=dare)
139 else:
140 self._send_user_vote_complete_email(
141 to_user=user,
142 dare=dare,
143 ballot_result=ballot_result,
144 )
146 except Exception as e:
147 msg = f"ERROR notifying user {from_uid} of vote for {debug_msg}: {e}"
148 LOG().error(msg)
149 main_result.add_error(AppErrorCode.VOTING, msg)
151 if IS_DEBUG:
152 msg = f"Vote notification complete for dare {dare_id} with {len(pledges)} pledges"
153 LOG().debug(msg)
155 return main_result
157 def _send_user_vote_start_email(
158 self,
159 to_user: UserWrapper,
160 dare: DareWrapper,
161 ) -> None:
162 self.send_user_email(
163 to_user,
164 template=VotingEmail.from_dare(
165 dare=dare,
166 to_user=to_user,
167 ),
168 doc_id=dare.doc_id,
169 )
171 def _send_user_vote_complete_email(
172 self,
173 to_user: UserWrapper,
174 dare: DareWrapper,
175 ballot_result: BallotResult,
176 ) -> None:
177 self.send_user_email(
178 to_user,
179 template=VotingEmail.from_dare_result(
180 dare=dare,
181 to_user=to_user,
182 result=ballot_result,
183 ),
184 doc_id=dare.doc_id,
185 )
187 def _send_group_vote_start_email(
188 self,
189 dare: DareWrapper,
190 to_group: GroupWrapper,
191 ) -> None:
192 self.send_group_email(
193 group=to_group,
194 email_callback=lambda user, processed_dare: VotingEmail.from_dare(
195 dare=processed_dare,
196 to_user=user,
197 ),
198 doc_id=dare.doc_id,
199 collection=None,
200 processed_dare=dare,
201 )
203 def _send_group_vote_complete_email(
204 self,
205 to_group: GroupWrapper,
206 dare: DareWrapper,
207 ballot_result: BallotResult,
208 ) -> None:
209 self.send_group_email(
210 group=to_group,
211 email_callback=lambda user, processed_dare: VotingEmail.from_dare_result(
212 dare=processed_dare,
213 to_user=user,
214 result=ballot_result,
215 ),
216 doc_id=dare.doc_id,
217 collection=None,
218 processed_dare=dare,
219 )