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

56 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-07-22 17:19 +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 ), 

234] 

235 

236 

237def check_settings(): 

238 # check settings contain all actions+topics 

239 actions_by_topic = {} 

240 for t in NotificationTopicAction: 

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

242 

243 actions_by_topic_check = {} 

244 

245 for heading, group in settings_layout: 

246 for topic, name, items in group: 

247 actions = [] 

248 for action, description in items: 

249 actions.append(action) 

250 actions_by_topic_check[topic] = actions 

251 

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

253 assert sorted(actions) == sorted( 

254 actions_by_topic_check[topic] 

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

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

257 

258 

259check_settings() 

260 

261 

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

263 with session_scope() as session: 

264 groups = [] 

265 for heading, group in settings_layout: 

266 topics = [] 

267 for topic, name, items in group: 

268 actions = [] 

269 for action, description in items: 

270 topic_action = enum_from_topic_action[topic, action] 

271 delivery_types = get_preference(session, user_id, topic_action) 

272 actions.append( 

273 notifications_pb2.NotificationItem( 

274 action=action, 

275 description=description, 

276 user_editable=topic_action.user_editable, 

277 push=NotificationDeliveryType.push in delivery_types, 

278 email=NotificationDeliveryType.email in delivery_types, 

279 digest=NotificationDeliveryType.digest in delivery_types, 

280 ) 

281 ) 

282 topics.append( 

283 notifications_pb2.NotificationTopic( 

284 topic=topic, 

285 name=name, 

286 items=actions, 

287 ) 

288 ) 

289 groups.append( 

290 notifications_pb2.NotificationGroup( 

291 heading=heading, 

292 topics=topics, 

293 ) 

294 ) 

295 return groups