ipulse-shared-core-ftredge 19.0.1__py3-none-any.whl → 22.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ipulse-shared-core-ftredge might be problematic. Click here for more details.
- ipulse_shared_core_ftredge/cache/shared_cache.py +1 -2
- ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +4 -4
- ipulse_shared_core_ftredge/exceptions/base_exceptions.py +23 -0
- ipulse_shared_core_ftredge/models/__init__.py +3 -7
- ipulse_shared_core_ftredge/models/base_data_model.py +17 -19
- ipulse_shared_core_ftredge/models/catalog/__init__.py +10 -0
- ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py +273 -0
- ipulse_shared_core_ftredge/models/catalog/usertype.py +170 -0
- ipulse_shared_core_ftredge/models/user/__init__.py +5 -0
- ipulse_shared_core_ftredge/models/user/user_permissions.py +66 -0
- ipulse_shared_core_ftredge/models/{subscription.py → user/user_subscription.py} +66 -20
- ipulse_shared_core_ftredge/models/{user_auth.py → user/userauth.py} +19 -10
- ipulse_shared_core_ftredge/models/{user_profile.py → user/userprofile.py} +53 -21
- ipulse_shared_core_ftredge/models/user/userstatus.py +430 -0
- ipulse_shared_core_ftredge/monitoring/__init__.py +2 -2
- ipulse_shared_core_ftredge/monitoring/tracemon.py +320 -0
- ipulse_shared_core_ftredge/services/__init__.py +11 -13
- ipulse_shared_core_ftredge/services/base/__init__.py +3 -1
- ipulse_shared_core_ftredge/services/base/base_firestore_service.py +73 -14
- ipulse_shared_core_ftredge/services/{cache_aware_firestore_service.py → base/cache_aware_firestore_service.py} +46 -32
- ipulse_shared_core_ftredge/services/catalog/__init__.py +14 -0
- ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py +273 -0
- ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py +307 -0
- ipulse_shared_core_ftredge/services/charging_processors.py +25 -25
- ipulse_shared_core_ftredge/services/user/__init__.py +5 -25
- ipulse_shared_core_ftredge/services/user/firebase_auth_admin_helpers.py +160 -0
- ipulse_shared_core_ftredge/services/user/user_core_service.py +423 -515
- ipulse_shared_core_ftredge/services/user/user_multistep_operations.py +726 -0
- ipulse_shared_core_ftredge/services/user/user_permissions_operations.py +392 -0
- ipulse_shared_core_ftredge/services/user/user_subscription_operations.py +484 -0
- ipulse_shared_core_ftredge/services/user/userauth_operations.py +928 -0
- ipulse_shared_core_ftredge/services/user/userprofile_operations.py +166 -0
- ipulse_shared_core_ftredge/services/user/userstatus_operations.py +212 -0
- ipulse_shared_core_ftredge/services/{charging_service.py → user_charging_service.py} +9 -9
- {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/METADATA +3 -4
- ipulse_shared_core_ftredge-22.1.1.dist-info/RECORD +51 -0
- ipulse_shared_core_ftredge/models/user_status.py +0 -495
- ipulse_shared_core_ftredge/monitoring/microservmon.py +0 -483
- ipulse_shared_core_ftredge/services/user/iam_management_operations.py +0 -326
- ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +0 -384
- ipulse_shared_core_ftredge/services/user/user_account_operations.py +0 -479
- ipulse_shared_core_ftredge/services/user/user_auth_operations.py +0 -305
- ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +0 -436
- ipulse_shared_core_ftredge-19.0.1.dist-info/RECORD +0 -41
- {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/WHEEL +0 -0
- {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/licenses/LICENCE +0 -0
- {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/top_level.txt +0 -0
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
User Management Operations - CRUD operations for UserProfile and UserStatus
|
|
3
|
-
"""
|
|
4
|
-
import os
|
|
5
|
-
import logging
|
|
6
|
-
from typing import Dict, Any, Optional, Set, List, Tuple
|
|
7
|
-
from google.cloud import firestore
|
|
8
|
-
from pydantic import ValidationError as PydanticValidationError
|
|
9
|
-
|
|
10
|
-
from ...models.user_profile import UserProfile
|
|
11
|
-
from ...models.user_status import UserStatus, IAMUnitRefAssignment
|
|
12
|
-
from ...exceptions import ServiceError, ResourceNotFoundError, UserProfileError, UserStatusError, UserValidationError, UserCreationError, UserDeletionError
|
|
13
|
-
from ..base import BaseFirestoreService
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class UserAccountOperations:
|
|
17
|
-
"""
|
|
18
|
-
Handles CRUD operations for UserProfile and UserStatus documents
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
firestore_client: firestore.Client,
|
|
24
|
-
logger: Optional[logging.Logger] = None,
|
|
25
|
-
timeout: float = 10.0,
|
|
26
|
-
profile_collection: Optional[str] = None,
|
|
27
|
-
status_collection: Optional[str] = None
|
|
28
|
-
):
|
|
29
|
-
self.db = firestore_client
|
|
30
|
-
self.logger = logger or logging.getLogger(__name__)
|
|
31
|
-
self.timeout = timeout
|
|
32
|
-
|
|
33
|
-
self.profile_collection_name = profile_collection or UserProfile.get_collection_name()
|
|
34
|
-
self.status_collection_name = status_collection or UserStatus.get_collection_name()
|
|
35
|
-
|
|
36
|
-
# Archival configuration
|
|
37
|
-
self.archive_profile_on_delete = os.getenv('ARCHIVE_USER_PROFILE_ON_DELETE', 'true').lower() == 'true'
|
|
38
|
-
self.archive_status_on_delete = os.getenv('ARCHIVE_USER_STATUS_ON_DELETE', 'true').lower() == 'true'
|
|
39
|
-
|
|
40
|
-
# Archive collection names
|
|
41
|
-
self.archive_profile_collection_name = os.getenv(
|
|
42
|
-
'ARCHIVE_PROFILE_COLLECTION_NAME',
|
|
43
|
-
f"~archive_core_user_userprofiles"
|
|
44
|
-
)
|
|
45
|
-
self.archive_status_collection_name = os.getenv(
|
|
46
|
-
'ARCHIVE_STATUS_COLLECTION_NAME',
|
|
47
|
-
f"~archive_core_user_userstatuss"
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
# Initialize DB services
|
|
51
|
-
self._profile_db_service = BaseFirestoreService[UserProfile](
|
|
52
|
-
db=self.db,
|
|
53
|
-
collection_name=self.profile_collection_name,
|
|
54
|
-
resource_type=UserProfile.OBJ_REF,
|
|
55
|
-
logger=self.logger,
|
|
56
|
-
timeout=self.timeout
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
self._status_db_service = BaseFirestoreService[UserStatus](
|
|
60
|
-
db=self.db,
|
|
61
|
-
collection_name=self.status_collection_name,
|
|
62
|
-
resource_type=UserStatus.OBJ_REF,
|
|
63
|
-
logger=self.logger,
|
|
64
|
-
timeout=self.timeout
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# UserProfile Operations
|
|
68
|
-
|
|
69
|
-
async def get_userprofile(self, user_uid: str) -> Optional[UserProfile]:
|
|
70
|
-
"""Get a user profile by user UID"""
|
|
71
|
-
profile_id = f"{UserProfile.OBJ_REF}_{user_uid}"
|
|
72
|
-
try:
|
|
73
|
-
profile_data = await self._profile_db_service.get_document(profile_id)
|
|
74
|
-
return UserProfile(**profile_data) if profile_data else None
|
|
75
|
-
except ResourceNotFoundError:
|
|
76
|
-
self.logger.info(f"UserProfile not found for user_uid: {user_uid}")
|
|
77
|
-
return None
|
|
78
|
-
except Exception as e:
|
|
79
|
-
self.logger.error(f"Error fetching UserProfile for {user_uid}: {e}", exc_info=True)
|
|
80
|
-
raise UserProfileError(
|
|
81
|
-
detail=f"Failed to fetch user profile: {str(e)}",
|
|
82
|
-
user_uid=user_uid,
|
|
83
|
-
operation="get_userprofile",
|
|
84
|
-
original_error=e
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
async def create_userprofile(self, user_profile: UserProfile) -> UserProfile:
|
|
88
|
-
"""Create a new user profile"""
|
|
89
|
-
try:
|
|
90
|
-
creator_uid = user_profile.created_by or f"UserManagement.create_profile:uid_{user_profile.user_uid}"
|
|
91
|
-
created_data = await self._profile_db_service.create_document(
|
|
92
|
-
user_profile.id,
|
|
93
|
-
user_profile,
|
|
94
|
-
creator_uid=creator_uid
|
|
95
|
-
)
|
|
96
|
-
self.logger.info(f"UserProfile created for {user_profile.user_uid}")
|
|
97
|
-
return UserProfile(**created_data)
|
|
98
|
-
except Exception as e:
|
|
99
|
-
self.logger.error(f"Error creating UserProfile for {user_profile.user_uid}: {e}", exc_info=True)
|
|
100
|
-
raise UserProfileError(
|
|
101
|
-
detail=f"Failed to create user profile: {str(e)}",
|
|
102
|
-
user_uid=user_profile.user_uid,
|
|
103
|
-
operation="create_userprofile",
|
|
104
|
-
original_error=e
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
async def update_userprofile(self, user_uid: str, profile_data: Dict[str, Any], updater_uid: str) -> UserProfile:
|
|
108
|
-
"""Update a user profile"""
|
|
109
|
-
profile_id = f"{UserProfile.OBJ_REF}_{user_uid}"
|
|
110
|
-
|
|
111
|
-
# Remove system fields that shouldn't be updated
|
|
112
|
-
update_data = profile_data.copy()
|
|
113
|
-
update_data.pop('user_uid', None)
|
|
114
|
-
update_data.pop('id', None)
|
|
115
|
-
update_data.pop('created_at', None)
|
|
116
|
-
update_data.pop('created_by', None)
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
updated_doc_dict = await self._profile_db_service.update_document(
|
|
120
|
-
profile_id,
|
|
121
|
-
update_data,
|
|
122
|
-
updater_uid=updater_uid
|
|
123
|
-
)
|
|
124
|
-
self.logger.info(f"UserProfile for {user_uid} updated successfully by {updater_uid}")
|
|
125
|
-
return UserProfile(**updated_doc_dict)
|
|
126
|
-
except ResourceNotFoundError:
|
|
127
|
-
raise UserProfileError(
|
|
128
|
-
detail="User profile not found",
|
|
129
|
-
user_uid=user_uid,
|
|
130
|
-
operation="update_userprofile"
|
|
131
|
-
)
|
|
132
|
-
except Exception as e:
|
|
133
|
-
self.logger.error(f"Error updating UserProfile for {user_uid}: {e}", exc_info=True)
|
|
134
|
-
raise UserProfileError(
|
|
135
|
-
detail=f"Failed to update user profile: {str(e)}",
|
|
136
|
-
user_uid=user_uid,
|
|
137
|
-
operation="update_userprofile",
|
|
138
|
-
original_error=e
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
async def delete_userprofile(self, user_uid: str, deleted_by: str = "system_deletion") -> bool:
|
|
142
|
-
"""Delete (archive and delete) user profile"""
|
|
143
|
-
import os
|
|
144
|
-
|
|
145
|
-
profile_doc_id = f"userprofile_{user_uid}"
|
|
146
|
-
|
|
147
|
-
# Archival configuration
|
|
148
|
-
archive_profile_on_delete = os.getenv('ARCHIVE_USER_PROFILE_ON_DELETE', 'true').lower() == 'true'
|
|
149
|
-
archive_profile_collection_name = os.getenv(
|
|
150
|
-
'ARCHIVE_PROFILE_COLLECTION_NAME',
|
|
151
|
-
f"~archive_core_user_userprofiles"
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
try:
|
|
155
|
-
# Get profile data for archival
|
|
156
|
-
profile_data = await self._profile_db_service.get_document(profile_doc_id)
|
|
157
|
-
|
|
158
|
-
if profile_data:
|
|
159
|
-
# Archive if enabled
|
|
160
|
-
if archive_profile_on_delete:
|
|
161
|
-
await self._profile_db_service.archive_document(
|
|
162
|
-
document_data=profile_data,
|
|
163
|
-
doc_id=profile_doc_id,
|
|
164
|
-
archive_collection=archive_profile_collection_name,
|
|
165
|
-
archived_by=deleted_by
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
# Delete the original document
|
|
169
|
-
await self._profile_db_service.delete_document(profile_doc_id)
|
|
170
|
-
self.logger.info(f"Successfully deleted user profile: {profile_doc_id}")
|
|
171
|
-
return True
|
|
172
|
-
else:
|
|
173
|
-
self.logger.warning(f"User profile {profile_doc_id} not found for deletion")
|
|
174
|
-
return True # Consider non-existent as successfully deleted
|
|
175
|
-
|
|
176
|
-
except Exception as e:
|
|
177
|
-
self.logger.error(f"Failed to delete user profile {profile_doc_id}: {e}", exc_info=True)
|
|
178
|
-
raise UserProfileError(
|
|
179
|
-
detail=f"Failed to delete user profile: {str(e)}",
|
|
180
|
-
user_uid=user_uid,
|
|
181
|
-
operation="delete_userprofile",
|
|
182
|
-
original_error=e
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
# UserStatus Operations
|
|
186
|
-
|
|
187
|
-
async def get_userstatus(self, user_uid: str) -> Optional[UserStatus]:
|
|
188
|
-
"""Get a user status by user UID"""
|
|
189
|
-
status_id = f"{UserStatus.OBJ_REF}_{user_uid}"
|
|
190
|
-
try:
|
|
191
|
-
status_data = await self._status_db_service.get_document(status_id)
|
|
192
|
-
return UserStatus(**status_data) if status_data else None
|
|
193
|
-
except ResourceNotFoundError:
|
|
194
|
-
self.logger.info(f"UserStatus not found for user_uid: {user_uid}")
|
|
195
|
-
return None
|
|
196
|
-
except Exception as e:
|
|
197
|
-
self.logger.error(f"Error fetching UserStatus for {user_uid}: {e}", exc_info=True)
|
|
198
|
-
raise UserStatusError(
|
|
199
|
-
detail=f"Failed to fetch user status: {str(e)}",
|
|
200
|
-
user_uid=user_uid,
|
|
201
|
-
operation="get_userstatus",
|
|
202
|
-
original_error=e
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
async def create_userstatus(
|
|
206
|
-
self,
|
|
207
|
-
user_uid: str,
|
|
208
|
-
organizations_uids: Optional[Set[str]] = None,
|
|
209
|
-
iam_domain_permissions: Optional[Dict[str, Dict[str, Dict[str, IAMUnitRefAssignment]]]] = None,
|
|
210
|
-
sbscrptn_based_insight_credits: int = 0,
|
|
211
|
-
extra_insight_credits: int = 0,
|
|
212
|
-
voting_credits: int = 0,
|
|
213
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
214
|
-
created_by: Optional[str] = None
|
|
215
|
-
) -> UserStatus:
|
|
216
|
-
"""Create a new user status"""
|
|
217
|
-
user_status_id = f"{UserStatus.OBJ_REF}_{user_uid}"
|
|
218
|
-
effective_created_by = created_by or f"UserManagement.create_status:uid_{user_uid}"
|
|
219
|
-
|
|
220
|
-
user_status_data = {
|
|
221
|
-
'id': user_status_id,
|
|
222
|
-
'user_uid': user_uid,
|
|
223
|
-
'organizations_uids': organizations_uids or set(),
|
|
224
|
-
'iam_domain_permissions': iam_domain_permissions or {},
|
|
225
|
-
'subscriptions_history': {},
|
|
226
|
-
'active_subscription': None,
|
|
227
|
-
'sbscrptn_based_insight_credits': sbscrptn_based_insight_credits,
|
|
228
|
-
'extra_insight_credits': extra_insight_credits,
|
|
229
|
-
'voting_credits': voting_credits,
|
|
230
|
-
'metadata': metadata or {},
|
|
231
|
-
'created_by': effective_created_by,
|
|
232
|
-
'updated_by': effective_created_by,
|
|
233
|
-
'schema_version': UserStatus.VERSION
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
try:
|
|
237
|
-
user_status = UserStatus(**user_status_data)
|
|
238
|
-
except PydanticValidationError as e:
|
|
239
|
-
self.logger.error(f"Pydantic validation error for UserStatus {user_uid}: {e}", exc_info=True)
|
|
240
|
-
raise UserValidationError(
|
|
241
|
-
detail=f"Validation failed: {e.errors() if hasattr(e, 'errors') else str(e)}",
|
|
242
|
-
user_uid=user_uid,
|
|
243
|
-
validation_field="user_status_data",
|
|
244
|
-
original_error=e
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
try:
|
|
248
|
-
await self._status_db_service.create_document(
|
|
249
|
-
doc_id=user_status.id,
|
|
250
|
-
data=user_status,
|
|
251
|
-
creator_uid=effective_created_by
|
|
252
|
-
)
|
|
253
|
-
self.logger.info(f"UserStatus created for {user_uid} with id {user_status.id}")
|
|
254
|
-
|
|
255
|
-
# Return the latest status to ensure consistency
|
|
256
|
-
final_user_status = await self.get_userstatus(user_uid)
|
|
257
|
-
if not final_user_status:
|
|
258
|
-
raise UserStatusError(
|
|
259
|
-
detail="UserStatus disappeared after creation",
|
|
260
|
-
user_uid=user_uid,
|
|
261
|
-
operation="create_userstatus"
|
|
262
|
-
)
|
|
263
|
-
return final_user_status
|
|
264
|
-
|
|
265
|
-
except Exception as e:
|
|
266
|
-
self.logger.error(f"Failed to create UserStatus for {user_uid}: {e}", exc_info=True)
|
|
267
|
-
raise UserStatusError(
|
|
268
|
-
detail=f"Failed to create user status: {str(e)}",
|
|
269
|
-
user_uid=user_uid,
|
|
270
|
-
operation="create_userstatus",
|
|
271
|
-
original_error=e
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
async def update_userstatus(self, user_uid: str, status_data: Dict[str, Any], updater_uid: str) -> UserStatus:
|
|
275
|
-
"""Update a user status"""
|
|
276
|
-
user_status_id = f"{UserStatus.OBJ_REF}_{user_uid}"
|
|
277
|
-
|
|
278
|
-
# Remove system fields that shouldn't be updated
|
|
279
|
-
update_data = status_data.copy()
|
|
280
|
-
update_data.pop('user_uid', None)
|
|
281
|
-
update_data.pop('id', None)
|
|
282
|
-
update_data.pop('created_at', None)
|
|
283
|
-
update_data.pop('created_by', None)
|
|
284
|
-
|
|
285
|
-
try:
|
|
286
|
-
updated_doc_dict = await self._status_db_service.update_document(
|
|
287
|
-
user_status_id,
|
|
288
|
-
update_data,
|
|
289
|
-
updater_uid=updater_uid
|
|
290
|
-
)
|
|
291
|
-
self.logger.info(f"UserStatus for {user_uid} updated successfully by {updater_uid}")
|
|
292
|
-
return UserStatus(**updated_doc_dict)
|
|
293
|
-
except ResourceNotFoundError:
|
|
294
|
-
raise UserStatusError(
|
|
295
|
-
detail="User status not found",
|
|
296
|
-
user_uid=user_uid,
|
|
297
|
-
operation="update_userstatus"
|
|
298
|
-
)
|
|
299
|
-
except Exception as e:
|
|
300
|
-
self.logger.error(f"Error updating UserStatus for {user_uid}: {e}", exc_info=True)
|
|
301
|
-
raise UserStatusError(
|
|
302
|
-
detail=f"Failed to update user status: {str(e)}",
|
|
303
|
-
user_uid=user_uid,
|
|
304
|
-
operation="update_userstatus",
|
|
305
|
-
original_error=e
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
async def delete_userstatus(self, user_uid: str, deleted_by: str = "system_deletion") -> bool:
|
|
309
|
-
"""Delete (archive and delete) user status"""
|
|
310
|
-
import os
|
|
311
|
-
|
|
312
|
-
status_doc_id = f"userstatus_{user_uid}"
|
|
313
|
-
|
|
314
|
-
# Archival configuration
|
|
315
|
-
archive_status_on_delete = os.getenv('ARCHIVE_USER_STATUS_ON_DELETE', 'true').lower() == 'true'
|
|
316
|
-
archive_status_collection_name = os.getenv(
|
|
317
|
-
'ARCHIVE_STATUS_COLLECTION_NAME',
|
|
318
|
-
f"~archive_core_user_userstatuss"
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
try:
|
|
322
|
-
# Get status data for archival
|
|
323
|
-
status_data = await self._status_db_service.get_document(status_doc_id)
|
|
324
|
-
|
|
325
|
-
if status_data:
|
|
326
|
-
# Archive if enabled
|
|
327
|
-
if archive_status_on_delete:
|
|
328
|
-
await self._status_db_service.archive_document(
|
|
329
|
-
document_data=status_data,
|
|
330
|
-
doc_id=status_doc_id,
|
|
331
|
-
archive_collection=archive_status_collection_name,
|
|
332
|
-
archived_by=deleted_by
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
# Delete the original document
|
|
336
|
-
await self._status_db_service.delete_document(status_doc_id)
|
|
337
|
-
self.logger.info(f"Successfully deleted user status: {status_doc_id}")
|
|
338
|
-
return True
|
|
339
|
-
else:
|
|
340
|
-
self.logger.warning(f"User status {status_doc_id} not found for deletion")
|
|
341
|
-
return True # Consider non-existent as successfully deleted
|
|
342
|
-
|
|
343
|
-
except Exception as e:
|
|
344
|
-
self.logger.error(f"Failed to delete user status {status_doc_id}: {e}", exc_info=True)
|
|
345
|
-
raise UserStatusError(
|
|
346
|
-
detail=f"Failed to delete user status: {str(e)}",
|
|
347
|
-
user_uid=user_uid,
|
|
348
|
-
operation="delete_userstatus",
|
|
349
|
-
original_error=e
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
# Combined Operations
|
|
353
|
-
|
|
354
|
-
async def get_user_core_docs(self, user_uid: str) -> Tuple[Optional[UserProfile], Optional[UserStatus]]:
|
|
355
|
-
"""Get both user profile and status in one call"""
|
|
356
|
-
try:
|
|
357
|
-
profile = await self.get_userprofile(user_uid)
|
|
358
|
-
status = await self.get_userstatus(user_uid)
|
|
359
|
-
return profile, status
|
|
360
|
-
except Exception as e:
|
|
361
|
-
self.logger.error(f"Error fetching complete user data for {user_uid}: {e}", exc_info=True)
|
|
362
|
-
raise UserCreationError(
|
|
363
|
-
detail=f"Failed to fetch complete user data: {str(e)}",
|
|
364
|
-
user_uid=user_uid,
|
|
365
|
-
original_error=e
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
async def user_core_docs_exist(self, user_uid: str) -> Tuple[bool, bool]:
|
|
369
|
-
"""Check if user profile and/or status exist"""
|
|
370
|
-
try:
|
|
371
|
-
profile, status = await self.get_user_core_docs(user_uid)
|
|
372
|
-
return profile is not None, status is not None
|
|
373
|
-
except Exception as e:
|
|
374
|
-
self.logger.error(f"Error checking user existence for {user_uid}: {e}", exc_info=True)
|
|
375
|
-
return False, False
|
|
376
|
-
|
|
377
|
-
# Restoration Operations
|
|
378
|
-
|
|
379
|
-
async def restore_user_from_archive(
|
|
380
|
-
self,
|
|
381
|
-
user_uid: str,
|
|
382
|
-
restore_profile: bool = True,
|
|
383
|
-
restore_status: bool = True,
|
|
384
|
-
restored_by: str = "system_restore"
|
|
385
|
-
) -> Dict[str, bool]:
|
|
386
|
-
"""Restore user documents from archive collections"""
|
|
387
|
-
results = {"profile_restored": False, "status_restored": False}
|
|
388
|
-
|
|
389
|
-
# Restore profile
|
|
390
|
-
if restore_profile:
|
|
391
|
-
try:
|
|
392
|
-
profile_doc_id = f"userprofile_{user_uid}"
|
|
393
|
-
profile_restored = await self._profile_db_service.restore_document(
|
|
394
|
-
doc_id=profile_doc_id,
|
|
395
|
-
source_collection=self.archive_profile_collection_name,
|
|
396
|
-
target_collection=self.profile_collection_name,
|
|
397
|
-
restored_by=restored_by
|
|
398
|
-
)
|
|
399
|
-
results["profile_restored"] = profile_restored
|
|
400
|
-
if profile_restored:
|
|
401
|
-
self.logger.info(f"Restored UserProfile {profile_doc_id} from archive")
|
|
402
|
-
except Exception as e:
|
|
403
|
-
self.logger.error(f"Failed to restore UserProfile for {user_uid}: {e}", exc_info=True)
|
|
404
|
-
|
|
405
|
-
# Restore status
|
|
406
|
-
if restore_status:
|
|
407
|
-
try:
|
|
408
|
-
status_doc_id = f"userstatus_{user_uid}"
|
|
409
|
-
status_restored = await self._status_db_service.restore_document(
|
|
410
|
-
doc_id=status_doc_id,
|
|
411
|
-
source_collection=self.archive_status_collection_name,
|
|
412
|
-
target_collection=self.status_collection_name,
|
|
413
|
-
restored_by=restored_by
|
|
414
|
-
)
|
|
415
|
-
results["status_restored"] = status_restored
|
|
416
|
-
if status_restored:
|
|
417
|
-
self.logger.info(f"Restored UserStatus {status_doc_id} from archive")
|
|
418
|
-
except Exception as e:
|
|
419
|
-
self.logger.error(f"Failed to restore UserStatus for {user_uid}: {e}", exc_info=True)
|
|
420
|
-
|
|
421
|
-
return results
|
|
422
|
-
|
|
423
|
-
async def restore_userprofile_from_archive(
|
|
424
|
-
self,
|
|
425
|
-
user_uid: str,
|
|
426
|
-
restored_by: str = "system_restore"
|
|
427
|
-
) -> bool:
|
|
428
|
-
"""Restore user profile from archive"""
|
|
429
|
-
try:
|
|
430
|
-
profile_doc_id = f"userprofile_{user_uid}"
|
|
431
|
-
return await self._profile_db_service.restore_document(
|
|
432
|
-
doc_id=profile_doc_id,
|
|
433
|
-
source_collection=self.archive_profile_collection_name,
|
|
434
|
-
target_collection=self.profile_collection_name,
|
|
435
|
-
restored_by=restored_by
|
|
436
|
-
)
|
|
437
|
-
except Exception as e:
|
|
438
|
-
self.logger.error(f"Failed to restore UserProfile for {user_uid}: {e}", exc_info=True)
|
|
439
|
-
return False
|
|
440
|
-
|
|
441
|
-
async def restore_userstatus_from_archive(
|
|
442
|
-
self,
|
|
443
|
-
user_uid: str,
|
|
444
|
-
restored_by: str = "system_restore"
|
|
445
|
-
) -> bool:
|
|
446
|
-
"""Restore user status from archive"""
|
|
447
|
-
try:
|
|
448
|
-
status_doc_id = f"userstatus_{user_uid}"
|
|
449
|
-
return await self._status_db_service.restore_document(
|
|
450
|
-
doc_id=status_doc_id,
|
|
451
|
-
source_collection=self.archive_status_collection_name,
|
|
452
|
-
target_collection=self.status_collection_name,
|
|
453
|
-
restored_by=restored_by
|
|
454
|
-
)
|
|
455
|
-
except Exception as e:
|
|
456
|
-
self.logger.error(f"Failed to restore UserStatus for {user_uid}: {e}", exc_info=True)
|
|
457
|
-
return False
|
|
458
|
-
|
|
459
|
-
async def validate_user_core_data(
|
|
460
|
-
self,
|
|
461
|
-
profile_data: Optional[Dict[str, Any]] = None,
|
|
462
|
-
status_data: Optional[Dict[str, Any]] = None
|
|
463
|
-
) -> Tuple[bool, List[str]]:
|
|
464
|
-
"""Validate user profile and status data without creating documents"""
|
|
465
|
-
errors = []
|
|
466
|
-
|
|
467
|
-
if profile_data:
|
|
468
|
-
try:
|
|
469
|
-
UserProfile(**profile_data)
|
|
470
|
-
except PydanticValidationError as e:
|
|
471
|
-
errors.append(f"Profile validation error: {str(e)}")
|
|
472
|
-
|
|
473
|
-
if status_data:
|
|
474
|
-
try:
|
|
475
|
-
UserStatus(**status_data)
|
|
476
|
-
except PydanticValidationError as e:
|
|
477
|
-
errors.append(f"Status validation error: {str(e)}")
|
|
478
|
-
|
|
479
|
-
return len(errors) == 0, errors
|