Coverage for src / lexigram / admin / domain / aggregate.py: 0%
33 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-11 02:25 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-11 02:25 +0800
1"""AdminUserAggregate — aggregate root for admin user management."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from datetime import UTC, datetime
7from typing import Any
9from lexigram.admin.events import (
10 UserCreated,
11 UserDeactivated,
12 UserDeleted,
13 UserUpdated,
14)
15from lexigram.domain.models.aggregate import AggregateRoot
18@dataclass
19class AdminUserAggregate(AggregateRoot):
20 """Aggregate root representing an admin-managed user.
22 Encapsulates all state transitions for a user managed through the
23 admin panel, recording domain events on each mutation.
24 """
26 username: str = ""
27 email: str = ""
28 hashed_password: str = ""
29 roles: list[str] = field(default_factory=list)
30 permissions: list[str] = field(default_factory=list)
31 is_active: bool = True
32 deleted_at: datetime | None = None
33 created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
35 # ------------------------------------------------------------------
36 # Factory
37 # ------------------------------------------------------------------
39 @classmethod
40 def create(
41 cls,
42 user_id: str,
43 username: str,
44 email: str,
45 hashed_password: str,
46 roles: list[str] | None = None,
47 permissions: list[str] | None = None,
48 actor_id: str = "",
49 ) -> AdminUserAggregate:
50 """Create a new AdminUserAggregate and record a UserCreated event.
52 Args:
53 user_id: Unique identifier for the user.
54 username: Login username.
55 email: Email address.
56 hashed_password: Pre-hashed password string.
57 roles: Initial role assignments.
58 permissions: Initial permission grants.
59 actor_id: Identity of the principal performing the action.
61 Returns:
62 A fully initialised aggregate with one pending domain event.
63 """
64 aggregate = cls(
65 id=user_id,
66 username=username,
67 email=email,
68 hashed_password=hashed_password,
69 roles=roles or [],
70 permissions=permissions or [],
71 )
72 aggregate._record_event(
73 UserCreated(
74 user_id=user_id,
75 email=email,
76 actor_id=actor_id or None,
77 )
78 )
79 return aggregate
81 # ------------------------------------------------------------------
82 # Mutations
83 # ------------------------------------------------------------------
85 def update(self, changes: dict[str, Any], actor_id: str = "") -> None:
86 """Apply a partial update and record a UserUpdated event.
88 Args:
89 changes: Mapping of field names to new values.
90 actor_id: Identity of the principal performing the action.
91 """
92 for key, value in changes.items():
93 if hasattr(self, key):
94 setattr(self, key, value)
95 self._record_event(
96 UserUpdated(
97 user_id=str(self.id),
98 changes=changes,
99 actor_id=actor_id or None,
100 )
101 )
103 def deactivate(self, actor_id: str = "") -> None:
104 """Deactivate this user and record a UserDeactivated event.
106 Args:
107 actor_id: Identity of the principal performing the action.
108 """
109 self.is_active = False
110 self._record_event(
111 UserDeactivated(
112 user_id=str(self.id),
113 actor_id=actor_id or None,
114 )
115 )
117 def soft_delete(self, actor_id: str = "") -> None:
118 """Soft-delete this user and record a UserDeleted event.
120 Sets ``deleted_at`` to now and ``is_active`` to False.
122 Args:
123 actor_id: Identity of the principal performing the action.
124 """
125 self.deleted_at = datetime.now(UTC)
126 self.is_active = False
127 self._record_event(
128 UserDeleted(
129 user_id=str(self.id),
130 actor_id=actor_id or None,
131 )
132 )