1
|
|
|
""" |
2
|
|
|
byceps.blueprints.admin.user.service |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2006-2021 Jochen Kupperschmidt |
6
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
7
|
|
|
""" |
8
|
|
|
|
9
|
1 |
|
from __future__ import annotations |
10
|
1 |
|
from collections import defaultdict |
11
|
1 |
|
from operator import attrgetter |
12
|
1 |
|
from typing import Any, Iterator, Sequence |
13
|
|
|
|
14
|
1 |
|
from ....services.consent import consent_service, subject_service |
15
|
1 |
|
from ....services.newsletter import service as newsletter_service |
16
|
1 |
|
from ....services.newsletter.transfer.models import List as NewsletterList |
17
|
1 |
|
from ....services.party import service as party_service |
18
|
1 |
|
from ....services.party.transfer.models import Party |
19
|
1 |
|
from ....services.shop.order import service as order_service |
20
|
1 |
|
from ....services.site import service as site_service |
21
|
1 |
|
from ....services.ticketing.dbmodels.ticket import Ticket as DbTicket |
22
|
1 |
|
from ....services.ticketing import attendance_service, ticket_service |
23
|
1 |
|
from ....services.user import event_service |
24
|
1 |
|
from ....services.user.dbmodels.event import ( |
25
|
|
|
UserEvent as DbUserEvent, |
26
|
|
|
UserEventData, |
27
|
|
|
) |
28
|
1 |
|
from ....services.user import service as user_service |
29
|
1 |
|
from ....services.user.transfer.models import User |
30
|
1 |
|
from ....services.user_avatar import service as avatar_service |
31
|
1 |
|
from ....services.user_badge import badge_service as user_badge_service |
32
|
1 |
|
from ....typing import PartyID, UserID |
33
|
|
|
|
34
|
|
|
|
35
|
1 |
|
def get_parties_and_tickets( |
36
|
|
|
user_id: UserID, |
37
|
|
|
) -> list[tuple[Party, list[DbTicket]]]: |
38
|
|
|
"""Return tickets the user uses or manages, and the related parties.""" |
39
|
1 |
|
tickets = ticket_service.find_tickets_related_to_user(user_id) |
40
|
|
|
|
41
|
1 |
|
tickets_by_party_id = _group_tickets_by_party_id(tickets) |
42
|
|
|
|
43
|
1 |
|
party_ids = set(tickets_by_party_id.keys()) |
44
|
1 |
|
parties_by_id = _get_parties_by_id(party_ids) |
45
|
|
|
|
46
|
1 |
|
parties_and_tickets = [ |
47
|
|
|
(parties_by_id[party_id], tickets) |
48
|
|
|
for party_id, tickets in tickets_by_party_id.items() |
49
|
|
|
] |
50
|
|
|
|
51
|
1 |
|
parties_and_tickets.sort(key=lambda x: x[0].starts_at, reverse=True) |
52
|
|
|
|
53
|
1 |
|
return parties_and_tickets |
54
|
|
|
|
55
|
|
|
|
56
|
1 |
|
def _group_tickets_by_party_id( |
57
|
|
|
tickets: Sequence[DbTicket], |
58
|
|
|
) -> dict[PartyID, list[DbTicket]]: |
59
|
1 |
|
tickets_by_party_id: dict[PartyID, list[DbTicket]] = defaultdict(list) |
60
|
|
|
|
61
|
1 |
|
for ticket in tickets: |
62
|
1 |
|
tickets_by_party_id[ticket.category.party_id].append(ticket) |
63
|
|
|
|
64
|
1 |
|
return tickets_by_party_id |
65
|
|
|
|
66
|
|
|
|
67
|
1 |
|
def _get_parties_by_id(party_ids: set[PartyID]) -> dict[PartyID, Party]: |
68
|
1 |
|
parties = party_service.get_parties(party_ids) |
69
|
1 |
|
return {p.id: p for p in parties} |
70
|
|
|
|
71
|
|
|
|
72
|
1 |
|
def get_attended_parties(user_id: UserID) -> list[Party]: |
73
|
|
|
"""Return the parties attended by the user, in order.""" |
74
|
1 |
|
attended_parties = attendance_service.get_attended_parties(user_id) |
75
|
1 |
|
attended_parties.sort(key=attrgetter('starts_at'), reverse=True) |
76
|
1 |
|
return attended_parties |
77
|
|
|
|
78
|
|
|
|
79
|
1 |
|
def get_newsletter_subscription_states( |
80
|
|
|
user_id: UserID, |
81
|
|
|
) -> Iterator[tuple[NewsletterList, bool]]: |
82
|
1 |
|
lists = newsletter_service.get_all_lists() |
83
|
1 |
|
for list_ in lists: |
84
|
|
|
is_subscribed = newsletter_service.is_subscribed(user_id, list_.id) |
85
|
|
|
yield list_, is_subscribed |
86
|
|
|
|
87
|
|
|
|
88
|
1 |
|
def get_events(user_id: UserID) -> Iterator[UserEventData]: |
89
|
1 |
|
events = event_service.get_events_for_user(user_id) |
90
|
1 |
|
events.extend(_fake_avatar_update_events(user_id)) |
91
|
1 |
|
events.extend(_fake_consent_events(user_id)) |
92
|
1 |
|
events.extend(_fake_newsletter_subscription_update_events(user_id)) |
93
|
1 |
|
events.extend(_fake_order_events(user_id)) |
94
|
|
|
|
95
|
1 |
|
user_ids = { |
96
|
|
|
event.data['initiator_id'] |
97
|
|
|
for event in events |
98
|
|
|
if 'initiator_id' in event.data |
99
|
|
|
} |
100
|
1 |
|
users = user_service.get_users(user_ids, include_avatars=True) |
101
|
1 |
|
users_by_id = {str(user.id): user for user in users} |
102
|
|
|
|
103
|
1 |
|
for event in events: |
104
|
1 |
|
data = { |
105
|
|
|
'event': event.event_type, |
106
|
|
|
'occurred_at': event.occurred_at, |
107
|
|
|
'data': event.data, |
108
|
|
|
} |
109
|
|
|
|
110
|
1 |
|
additional_data = _get_additional_data(event, users_by_id) |
111
|
1 |
|
data.update(additional_data) |
112
|
|
|
|
113
|
1 |
|
yield data |
114
|
|
|
|
115
|
|
|
|
116
|
1 |
|
def _fake_avatar_update_events(user_id: UserID) -> Iterator[DbUserEvent]: |
117
|
|
|
"""Yield the user's avatar updates as volatile events.""" |
118
|
1 |
|
avatar_updates = avatar_service.get_avatars_uploaded_by_user(user_id) |
119
|
|
|
|
120
|
1 |
|
for avatar_update in avatar_updates: |
121
|
|
|
data = { |
122
|
|
|
'initiator_id': str(user_id), |
123
|
|
|
'url_path': avatar_update.url_path, |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
yield DbUserEvent( |
127
|
|
|
avatar_update.occurred_at, 'user-avatar-updated', user_id, data |
128
|
|
|
) |
129
|
|
|
|
130
|
|
|
|
131
|
1 |
|
def _fake_consent_events(user_id: UserID) -> Iterator[DbUserEvent]: |
132
|
|
|
"""Yield the user's consents as volatile events.""" |
133
|
1 |
|
consents = consent_service.get_consents_by_user(user_id) |
134
|
|
|
|
135
|
1 |
|
subject_ids = {consent.subject_id for consent in consents} |
136
|
1 |
|
subjects = subject_service.get_subjects(subject_ids) |
137
|
1 |
|
subjects_titles_by_id = {subject.id: subject.title for subject in subjects} |
138
|
|
|
|
139
|
1 |
|
for consent in consents: |
140
|
|
|
data = { |
141
|
|
|
'initiator_id': str(user_id), |
142
|
|
|
'subject_title': subjects_titles_by_id[consent.subject_id], |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
yield DbUserEvent( |
146
|
|
|
consent.expressed_at, 'consent-expressed', user_id, data |
147
|
|
|
) |
148
|
|
|
|
149
|
|
|
|
150
|
1 |
|
def _fake_newsletter_subscription_update_events( |
151
|
|
|
user_id: UserID, |
152
|
|
|
) -> Iterator[DbUserEvent]: |
153
|
|
|
"""Yield the user's newsletter subscription updates as volatile events.""" |
154
|
1 |
|
lists = newsletter_service.get_all_lists() |
155
|
1 |
|
lists_by_id = {list_.id: list_ for list_ in lists} |
156
|
|
|
|
157
|
1 |
|
updates = newsletter_service.get_subscription_updates_for_user(user_id) |
158
|
|
|
|
159
|
1 |
|
for update in updates: |
160
|
|
|
event_type = f'newsletter-{update.state.name}' |
161
|
|
|
|
162
|
|
|
list_ = lists_by_id[update.list_id] |
163
|
|
|
|
164
|
|
|
data = { |
165
|
|
|
'list_': list_, |
166
|
|
|
'initiator_id': str(user_id), |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
yield DbUserEvent(update.expressed_at, event_type, user_id, data) |
170
|
|
|
|
171
|
|
|
|
172
|
1 |
|
def _fake_order_events(user_id: UserID) -> Iterator[DbUserEvent]: |
173
|
|
|
"""Yield the orders placed by the user as volatile events.""" |
174
|
1 |
|
orders = order_service.get_orders_placed_by_user(user_id) |
175
|
|
|
|
176
|
1 |
|
for order in orders: |
177
|
|
|
data = { |
178
|
|
|
'initiator_id': str(user_id), |
179
|
|
|
'order': order, |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
yield DbUserEvent(order.created_at, 'order-placed', user_id, data) |
183
|
|
|
|
184
|
|
|
|
185
|
1 |
|
def _get_additional_data( |
186
|
|
|
event: DbUserEvent, users_by_id: dict[str, User] |
187
|
|
|
) -> Iterator[tuple[str, Any]]: |
188
|
1 |
|
if event.event_type in { |
189
|
|
|
'user-created', |
190
|
|
|
'user-deleted', |
191
|
|
|
'user-details-updated', |
192
|
|
|
'user-email-address-changed', |
193
|
|
|
'user-email-address-invalidated', |
194
|
|
|
'user-initialized', |
195
|
|
|
'user-screen-name-changed', |
196
|
|
|
'user-suspended', |
197
|
|
|
'user-unsuspended', |
198
|
|
|
'password-updated', |
199
|
|
|
'user-avatar-updated', |
200
|
|
|
'consent-expressed', |
201
|
|
|
'newsletter-requested', |
202
|
|
|
'newsletter-declined', |
203
|
|
|
'order-placed', |
204
|
|
|
'orgaflag-added', |
205
|
|
|
'orgaflag-removed', |
206
|
|
|
'privacy-policy-accepted', |
207
|
|
|
'role-assigned', |
208
|
|
|
'role-deassigned', |
209
|
|
|
'user-badge-awarded', |
210
|
|
|
}: |
211
|
1 |
|
yield from _get_additional_data_for_user_initiated_event( |
212
|
|
|
event, users_by_id |
213
|
|
|
) |
214
|
|
|
|
215
|
1 |
|
if event.event_type == 'user-badge-awarded': |
216
|
1 |
|
badge = user_badge_service.find_badge(event.data['badge_id']) |
217
|
1 |
|
yield 'badge', badge |
218
|
|
|
|
219
|
1 |
|
if event.event_type == 'user-details-updated': |
220
|
|
|
details = { |
221
|
|
|
k: v |
222
|
|
|
for k, v in event.data.items() |
223
|
|
|
if k.startswith('old_') or k.startswith('new_') |
224
|
|
|
} |
225
|
|
|
yield 'details', details |
226
|
|
|
|
227
|
1 |
|
if event.event_type in {'user-created', 'user-logged-in'}: |
228
|
1 |
|
site_id = event.data.get('site_id') |
229
|
1 |
|
if site_id: |
230
|
|
|
site = site_service.find_site(site_id) |
231
|
|
|
if site is not None: |
232
|
|
|
yield 'site', site |
233
|
|
|
|
234
|
|
|
|
235
|
1 |
|
def _get_additional_data_for_user_initiated_event( |
236
|
|
|
event: DbUserEvent, users_by_id: dict[str, User] |
237
|
|
|
) -> Iterator[tuple[str, Any]]: |
238
|
1 |
|
initiator_id = event.data.get('initiator_id') |
239
|
1 |
|
if initiator_id is not None: |
240
|
|
|
yield 'initiator', users_by_id[initiator_id] |
241
|
|
|
|