byceps.services.user.user_domain_service   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Test Coverage

Coverage 95.45%

Importance

Changes 0
Metric Value
wmc 21
eloc 245
dl 0
loc 354
ccs 84
cts 88
cp 0.9545
rs 10
c 0
b 0
f 0

16 Functions

Rating   Name   Duplication   Size   Complexity  
B update_details() 0 52 2
A _to_str_if_not_none() 0 2 2
A _build_details_updated_event() 0 9 1
A _build_account_suspended_log_entry() 0 12 1
A delete_account() 0 15 1
A _build_account_deleted_log_entry() 0 15 1
B _build_details_updated_log_entry() 0 61 3
A _build_screen_name_changed_event() 0 13 1
A change_screen_name() 0 20 1
A _build_account_suspended_event() 0 7 1
A unsuspend_account() 0 13 1
A suspend_account() 0 13 1
A _build_account_unsuspended_event() 0 7 1
A _build_account_deleted_event() 0 9 1
A _build_account_unsuspended_log_entry() 0 12 1
A _build_screen_name_changed_log_entry() 0 24 2
1
"""
2
byceps.services.user.user_domain_service
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2014-2025 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
from datetime import date, datetime
10
from typing import Any
11 1
12 1
from byceps.services.core.events import EventUser
13
from byceps.util.result import Err, Ok, Result
14 1
from byceps.util.uuid import generate_uuid7
15
16
from .errors import NothingChangedError
17
from .events import (
18
    UserAccountDeletedEvent,
19
    UserAccountSuspendedEvent,
20
    UserAccountUnsuspendedEvent,
21
    UserDetailsUpdatedEvent,
22
    UserScreenNameChangedEvent,
23
)
24
from .models.log import UserLogEntry, UserLogEntryData
25 1
from .models.user import User
26 1
27 1
28
def suspend_account(
29 1
    user: User, initiator: User, reason: str
30
) -> tuple[UserAccountSuspendedEvent, UserLogEntry]:
31
    """Suspend the user account."""
32
    occurred_at = datetime.utcnow()
33
34 1
    event = _build_account_suspended_event(occurred_at, initiator, user)
35 1
36
    log_entry = _build_account_suspended_log_entry(
37
        occurred_at, initiator, user, reason
38 1
    )
39
40
    return event, log_entry
41
42
43
def _build_account_suspended_event(
44
    occurred_at: datetime, initiator: User, user: User
45
) -> UserAccountSuspendedEvent:
46
    return UserAccountSuspendedEvent(
47
        occurred_at=occurred_at,
48
        initiator=EventUser.from_user(initiator),
49
        user=EventUser.from_user(user),
50
    )
51
52
53
def _build_account_suspended_log_entry(
54 1
    occurred_at: datetime, initiator: User, user: User, reason: str
55 1
) -> UserLogEntry:
56
    return UserLogEntry(
57
        id=generate_uuid7(),
58 1
        occurred_at=occurred_at,
59 1
        event_type='user-suspended',
60
        user_id=user.id,
61 1
        initiator_id=initiator.id,
62
        data={
63
            'initiator_id': str(initiator.id),
64 1
            'reason': reason,
65
        },
66 1
    )
67
68
69 1
def unsuspend_account(
70 1
    user: User, initiator: User, reason: str
71
) -> tuple[UserAccountUnsuspendedEvent, UserLogEntry]:
72
    """Unsuspend the user account."""
73
    occurred_at = datetime.utcnow()
74 1
75
    event = _build_account_unsuspended_event(occurred_at, initiator, user)
76
77 1
    log_entry = _build_account_unsuspended_log_entry(
78
        occurred_at, initiator, user, reason
79
    )
80
81 1
    return event, log_entry
82
83
84
def _build_account_unsuspended_event(
85
    occurred_at: datetime, initiator: User, user: User
86
) -> UserAccountUnsuspendedEvent:
87
    return UserAccountUnsuspendedEvent(
88
        occurred_at=occurred_at,
89
        initiator=EventUser.from_user(initiator),
90
        user=EventUser.from_user(user),
91 1
    )
92
93
94
def _build_account_unsuspended_log_entry(
95 1
    occurred_at: datetime, initiator: User, user: User, reason: str
96
) -> UserLogEntry:
97
    return UserLogEntry(
98
        id=generate_uuid7(),
99 1
        occurred_at=occurred_at,
100
        event_type='user-unsuspended',
101
        user_id=user.id,
102 1
        initiator_id=initiator.id,
103
        data={
104
            'initiator_id': str(initiator.id),
105
            'reason': reason,
106
        },
107
    )
108
109 1
110
def delete_account(
111
    user: User,
112
    initiator: User,
113
    reason: str,
114
) -> tuple[UserAccountDeletedEvent, UserLogEntry]:
115
    """Delete the user account."""
116
    occurred_at = datetime.utcnow()
117
118
    event = _build_account_deleted_event(occurred_at, initiator, user)
119
120 1
    log_entry = _build_account_deleted_log_entry(
121
        occurred_at, initiator, user, reason
122
    )
123
124
    return event, log_entry
125
126
127
def _build_account_deleted_event(
128 1
    occurred_at: datetime,
129
    initiator: User,
130 1
    user: User,
131
) -> UserAccountDeletedEvent:
132
    return UserAccountDeletedEvent(
133 1
        occurred_at=occurred_at,
134 1
        initiator=EventUser.from_user(initiator),
135
        user=EventUser.from_user(user),
136 1
    )
137 1
138
139 1
def _build_account_deleted_log_entry(
140 1
    occurred_at: datetime,
141
    initiator: User,
142 1
    user: User,
143
    reason: str,
144
) -> UserLogEntry:
145
    return UserLogEntry(
146
        id=generate_uuid7(),
147
        occurred_at=occurred_at,
148
        event_type='user-deleted',
149
        user_id=user.id,
150
        initiator_id=initiator.id,
151
        data={
152 1
            'initiator_id': str(initiator.id),
153
            'reason': reason,
154
        },
155
    )
156
157
158 1
def change_screen_name(
159 1
    user: User,
160
    new_screen_name: str,
161 1
    initiator: User,
162
    *,
163 1
    reason: str | None = None,
164
) -> tuple[UserScreenNameChangedEvent, UserLogEntry]:
165
    """Change the user's screen name."""
166
    occurred_at = datetime.utcnow()
167 1
    old_screen_name = user.screen_name
168
169
    event = _build_screen_name_changed_event(
170 1
        occurred_at, initiator, user, old_screen_name, new_screen_name
171
    )
172
173 1
    log_entry = _build_screen_name_changed_log_entry(
174
        occurred_at, initiator, user, old_screen_name, new_screen_name, reason
175 1
    )
176 1
177
    return event, log_entry
178 1
179
180
def _build_screen_name_changed_event(
181
    occurred_at: datetime,
182
    initiator: User,
183
    user: User,
184
    old_screen_name: str | None,
185
    new_screen_name: str | None,
186
) -> UserScreenNameChangedEvent:
187
    return UserScreenNameChangedEvent(
188 1
        occurred_at=occurred_at,
189
        initiator=EventUser.from_user(initiator),
190
        user_id=user.id,
191
        old_screen_name=old_screen_name,
192 1
        new_screen_name=new_screen_name,
193
    )
194 1
195
196 1
def _build_screen_name_changed_log_entry(
197
    occurred_at: datetime,
198
    initiator: User,
199
    user: User,
200 1
    old_screen_name: str | None,
201
    new_screen_name: str | None,
202
    reason: str | None,
203 1
) -> UserLogEntry:
204
    data = {
205
        'initiator_id': str(initiator.id),
206 1
        'old_screen_name': old_screen_name,
207
        'new_screen_name': new_screen_name,
208
    }
209
210
    if reason:
211
        data['reason'] = reason
212
213
    return UserLogEntry(
214
        id=generate_uuid7(),
215 1
        occurred_at=occurred_at,
216
        event_type='user-screen-name-changed',
217
        user_id=user.id,
218 1
        initiator_id=initiator.id,
219
        data=data,
220
    )
221
222
223
def update_details(
224
    user: User,
225
    old_first_name: str | None,
226
    new_first_name: str | None,
227
    old_last_name: str | None,
228
    new_last_name: str | None,
229
    old_date_of_birth: date | None,
230
    new_date_of_birth: date | None,
231 1
    old_country: str | None,
232
    new_country: str | None,
233
    old_postal_code: str | None,
234
    new_postal_code: str | None,
235 1
    old_city: str | None,
236
    new_city: str | None,
237 1
    old_street: str | None,
238
    new_street: str | None,
239 1
    old_phone_number: str | None,
240
    new_phone_number: str | None,
241
    initiator: User,
242
) -> Result[tuple[UserDetailsUpdatedEvent, UserLogEntry], NothingChangedError]:
243 1
    """Update the user's details."""
244
    occurred_at = datetime.utcnow()
245
246 1
    event = _build_details_updated_event(occurred_at, initiator, user)
247
248
    log_entry_result = _build_details_updated_log_entry(
249 1
        occurred_at,
250
        initiator,
251
        user,
252
        old_first_name,
253
        new_first_name,
254
        old_last_name,
255
        new_last_name,
256
        old_date_of_birth,
257
        new_date_of_birth,
258 1
        old_country,
259
        new_country,
260
        old_postal_code,
261 1
        new_postal_code,
262
        old_city,
263
        new_city,
264
        old_street,
265
        new_street,
266
        old_phone_number,
267
        new_phone_number,
268
    )
269
    if log_entry_result.is_err():
270
        return Err(log_entry_result.unwrap_err())
271
272
    log_entry = log_entry_result.unwrap()
273
274 1
    return Ok((event, log_entry))
275
276
277
def _build_details_updated_event(
278
    occurred_at: datetime,
279
    initiator: User,
280 1
    user: User,
281
) -> UserDetailsUpdatedEvent:
282 1
    return UserDetailsUpdatedEvent(
283
        occurred_at=occurred_at,
284 1
        initiator=EventUser.from_user(initiator),
285
        user=EventUser.from_user(user),
286
    )
287
288 1
289
def _build_details_updated_log_entry(
290
    occurred_at: datetime,
291 1
    initiator: User,
292
    user: User,
293
    old_first_name: str | None,
294
    new_first_name: str | None,
295
    old_last_name: str | None,
296 1
    new_last_name: str | None,
297
    old_date_of_birth: date | None,
298
    new_date_of_birth: date | None,
299
    old_country: str | None,
300
    new_country: str | None,
301
    old_postal_code: str | None,
302
    new_postal_code: str | None,
303
    old_city: str | None,
304
    new_city: str | None,
305 1
    old_street: str | None,
306
    new_street: str | None,
307
    old_phone_number: str | None,
308
    new_phone_number: str | None,
309
) -> Result[UserLogEntry, NothingChangedError]:
310
    fields: dict[str, str] = {}
311 1
312
    def _add_if_different(
313
        property_key: str,
314
        old_value: Any | None,
315
        new_value: Any | None,
316
    ) -> None:
317
        if old_value != new_value:
318
            fields[property_key] = {
319
                'old': _to_str_if_not_none(old_value),
320
                'new': _to_str_if_not_none(new_value),
321
            }
322
323
    _add_if_different('first_name', old_first_name, new_first_name)
324 1
    _add_if_different('last_name', old_last_name, new_last_name)
325
    _add_if_different('date_of_birth', old_date_of_birth, new_date_of_birth)
326
    _add_if_different('country', old_country, new_country)
327
    _add_if_different('postal_code', old_postal_code, new_postal_code)
328
    _add_if_different('city', old_city, new_city)
329
    _add_if_different('street', old_street, new_street)
330
    _add_if_different('phone_number', old_phone_number, new_phone_number)
331
332 1
    if not fields:
333 1
        return Err(NothingChangedError())
334
335 1
    data = {
336
        'fields': fields,
337
        'initiator_id': str(initiator.id),
338
    }
339 1
340
    entry = UserLogEntry(
341
        id=generate_uuid7(),
342
        occurred_at=occurred_at,
343 1
        event_type='user-details-updated',
344
        user_id=user.id,
345
        initiator_id=initiator.id,
346 1
        data=data,
347
    )
348
349
    return Ok(entry)
350
351
352
def _to_str_if_not_none(value: Any) -> str | None:
353
    return str(value) if (value is not None) else None
354