Coverage for src/couchers/notifications/settings.py: 93%

56 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-10-15 13:03 +0000

1import logging 

2from typing import List 

3 

4from couchers.db import session_scope 

5from couchers.models import ( 

6 NotificationDelivery, 

7 NotificationDeliveryType, 

8 NotificationPreference, 

9 NotificationTopicAction, 

10) 

11from couchers.notifications.utils import enum_from_topic_action 

12from couchers.sql import couchers_select as select 

13from proto import notifications_pb2 

14 

15logger = logging.getLogger(__name__) 

16 

17 

18def get_preference(session, user_id: int, topic_action: NotificationTopicAction) -> List[NotificationDeliveryType]: 

19 """ 

20 Gets the user's preference from the DB or otherwise falls back to defaults 

21 

22 Must be done in session scope 

23 

24 Returns list of delivery types 

25 """ 

26 overrides = { 

27 res.delivery_type: res.deliver 

28 for res in session.execute( 

29 select(NotificationPreference) 

30 .where(NotificationPreference.user_id == user_id) 

31 .where(NotificationPreference.topic_action == topic_action) 

32 ) 

33 .scalars() 

34 .all() 

35 } 

36 return [dt for dt in NotificationDeliveryType if overrides.get(dt, dt in topic_action.defaults)] 

37 

38 

39def reset_preference(session, user_id, topic_action, delivery_type): 

40 current_pref = session.execute( 

41 select(NotificationPreference) 

42 .where(NotificationPreference.user_id == user_id) 

43 .where(NotificationPreference.topic_action == topic_action) 

44 .where(NotificationDelivery.delivery_type == delivery_type) 

45 ).scalar_one_or_none() 

46 if current_pref: 

47 session.delete(current_pref) 

48 session.flush() 

49 

50 

51class PreferenceNotUserEditableError(Exception): 

52 pass 

53 

54 

55def set_preference(session, user_id, topic_action: NotificationTopicAction, delivery_type, deliver): 

56 if not topic_action.user_editable: 

57 raise PreferenceNotUserEditableError() 

58 current_pref = session.execute( 

59 select(NotificationPreference) 

60 .where(NotificationPreference.user_id == user_id) 

61 .where(NotificationPreference.topic_action == topic_action) 

62 .where(NotificationPreference.delivery_type == delivery_type) 

63 ).scalar_one_or_none() 

64 if current_pref: 

65 current_pref.deliver = deliver 

66 else: 

67 session.add( 

68 NotificationPreference( 

69 user_id=user_id, 

70 topic_action=topic_action, 

71 delivery_type=delivery_type, 

72 deliver=deliver, 

73 ) 

74 ) 

75 session.flush() 

76 

77 

78settings_layout = [ 

79 ( 

80 "Core Features", 

81 [ 

82 ( 

83 "host_request", 

84 "Host requests", 

85 [ 

86 ("create", "Someone sends you a host request"), 

87 ("accept", "Someone accepts your host request"), 

88 ("confirm", "Someone confirms their host request"), 

89 ("reject", "Someone declines your host request"), 

90 ("cancel", "Someone cancels their host request"), 

91 ("message", "Someone sends a message in a host request"), 

92 ("missed_messages", "You miss messages in a host request"), 

93 ], 

94 ), 

95 ( 

96 "chat", 

97 "Messaging", 

98 [ 

99 ("message", "Someone sends you a message"), 

100 ("missed_messages", "You miss messages in a chat"), 

101 ], 

102 ), 

103 ( 

104 "reference", 

105 "References", 

106 [ 

107 ("receive_hosted", "You receive a reference from someone who hosted you"), 

108 ("receive_surfed", "You receive a reference from someone you hosted"), 

109 ("receive_friend", "You received a reference from a friend"), 

110 ("reminder_hosted", "Reminder to write a reference to someone you hosted"), 

111 ("reminder_surfed", "Reminder to write a reference to someone you surfed with"), 

112 ], 

113 ), 

114 ], 

115 ), 

116 ( 

117 "Community Features", 

118 [ 

119 ( 

120 "friend_request", 

121 "Friend requests", 

122 [ 

123 ("create", "Someone sends you a friend request"), 

124 ("accept", "Someone accepts your friend request"), 

125 ], 

126 ), 

127 ( 

128 "event", 

129 "Events", 

130 [ 

131 ("create_approved", "An event that is approved by the moderators is created in your community"), 

132 ("create_any", "A user creates any event in your community (not checked by an admin)"), 

133 ("update", "An event you are attending is updated"), 

134 ("cancel", "An event you are attending is cancelled"), 

135 ("delete", "An event you are attending is deleted"), 

136 ("invite_organizer", "Someone invites you to co-organize an event"), 

137 ], 

138 ), 

139 ], 

140 ), 

141 ( 

142 "Account Settings", 

143 [ 

144 ( 

145 "onboarding", 

146 "Onboarding", 

147 [ 

148 ("reminder", "Reminder to complete your profile after signing up"), 

149 ], 

150 ), 

151 ( 

152 "badge", 

153 "Updates to Badges on your profile", 

154 [ 

155 ("add", "A badge is added to your account"), 

156 ("remove", "A badge is removed from your account"), 

157 ], 

158 ), 

159 ( 

160 "donation", 

161 "Donations", 

162 [ 

163 ("received", "Your donation is received"), 

164 ], 

165 ), 

166 ], 

167 ), 

168 ( 

169 "Account Security", 

170 [ 

171 ( 

172 "password", 

173 "Password change", 

174 [ 

175 ("change", "Your password is changed"), 

176 ], 

177 ), 

178 ( 

179 "password_reset", 

180 "Password reset", 

181 [ 

182 ("start", "Password reset is initiated"), 

183 ("complete", "Password reset is completed"), 

184 ], 

185 ), 

186 ( 

187 "email_address", 

188 "Email address change", 

189 [ 

190 ("change", "Email change is initiated"), 

191 ("verify", "Your new email is verified"), 

192 ], 

193 ), 

194 ( 

195 "account_deletion", 

196 "Account deletion", 

197 [ 

198 ("start", "You initiate account deletion"), 

199 ("complete", "Your account is deleted"), 

200 ("recovered", "Your account is recovered (undeleted)"), 

201 ], 

202 ), 

203 ( 

204 "api_key", 

205 "API keys", 

206 [ 

207 ("create", "An API key is created for your account"), 

208 ], 

209 ), 

210 ( 

211 "phone_number", 

212 "Phone number change", 

213 [ 

214 ("change", "Your phone number is changed"), 

215 ("verify", "Your phone number is verified"), 

216 ], 

217 ), 

218 ( 

219 "birthdate", 

220 "Birthdate change", 

221 [ 

222 ("change", "Your birthdate is changed"), 

223 ], 

224 ), 

225 ( 

226 "gender", 

227 "Displayed gender change", 

228 [ 

229 ("change", "The gender displayed on your profile is changed"), 

230 ], 

231 ), 

232 ( 

233 "modnote", 

234 "Moderator notes", 

235 [ 

236 ("create", "You receive a moderator note"), 

237 ], 

238 ), 

239 ], 

240 ), 

241] 

242 

243 

244def check_settings(): 

245 # check settings contain all actions+topics 

246 actions_by_topic = {} 

247 for t in NotificationTopicAction: 

248 actions_by_topic[t.topic] = actions_by_topic.get(t.topic, []) + [t.action] 

249 

250 actions_by_topic_check = {} 

251 

252 for heading, group in settings_layout: 

253 for topic, name, items in group: 

254 actions = [] 

255 for action, description in items: 

256 actions.append(action) 

257 actions_by_topic_check[topic] = actions 

258 

259 for topic, actions in actions_by_topic.items(): 

260 assert sorted(actions) == sorted( 

261 actions_by_topic_check[topic] 

262 ), f"Expected {actions} == {actions_by_topic_check[topic]} for {topic}" 

263 assert sorted(actions_by_topic.keys()) == sorted(actions_by_topic_check.keys()) 

264 

265 

266check_settings() 

267 

268 

269def get_user_setting_groups(user_id) -> List[notifications_pb2.NotificationGroup]: 

270 with session_scope() as session: 

271 groups = [] 

272 for heading, group in settings_layout: 

273 topics = [] 

274 for topic, name, items in group: 

275 actions = [] 

276 for action, description in items: 

277 topic_action = enum_from_topic_action[topic, action] 

278 delivery_types = get_preference(session, user_id, topic_action) 

279 actions.append( 

280 notifications_pb2.NotificationItem( 

281 action=action, 

282 description=description, 

283 user_editable=topic_action.user_editable, 

284 push=NotificationDeliveryType.push in delivery_types, 

285 email=NotificationDeliveryType.email in delivery_types, 

286 digest=NotificationDeliveryType.digest in delivery_types, 

287 ) 

288 ) 

289 topics.append( 

290 notifications_pb2.NotificationTopic( 

291 topic=topic, 

292 name=name, 

293 items=actions, 

294 ) 

295 ) 

296 groups.append( 

297 notifications_pb2.NotificationGroup( 

298 heading=heading, 

299 topics=topics, 

300 ) 

301 ) 

302 return groups