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

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 

24 

25 

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 

38 

39 @property 

40 def user_mailer(self) -> UserMailer: 

41 return self._user_mailer 

42 

43 @property 

44 def admin_mailer(self) -> AdminMailer: 

45 return self._admin_mailer 

46 

47 @property 

48 def app_logger(self) -> AppLogger: 

49 return self._app_logger 

50 

51 @property 

52 def user_db(self) -> UserDb: 

53 return self._db_manager.user_db 

54 

55 @property 

56 def dare_db(self) -> DareDb: 

57 return self._db_manager.dare_db 

58 

59 @property 

60 def pledge_db(self) -> PledgeDb: 

61 return self._db_manager.pledge_db 

62 

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) 

68 

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) 

75 

76 def _notify_users_vote_change( # noqa: PLR0912 

77 self, 

78 context: DareContext, 

79 ballot_result: BallotResult | None = None, 

80 ) -> AppResult[DareWrapper]: 

81 

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") 

85 

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 ) 

107 

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. 

111 

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 

120 

121 if IS_DEBUG: 

122 msg = f"Processing vote notification for dare {dare_id} with {len(pledges)} pledges." 

123 LOG().debug(msg) 

124 

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})" 

128 

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 

136 

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 ) 

145 

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) 

150 

151 if IS_DEBUG: 

152 msg = f"Vote notification complete for dare {dare_id} with {len(pledges)} pledges" 

153 LOG().debug(msg) 

154 

155 return main_result 

156 

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 ) 

170 

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 ) 

186 

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 ) 

202 

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 )