ipulse-shared-core-ftredge 20.0.1__py3-none-any.whl → 23.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.

Files changed (48) hide show
  1. ipulse_shared_core_ftredge/cache/shared_cache.py +1 -2
  2. ipulse_shared_core_ftredge/dependencies/auth_firebase_token_validation.py +60 -23
  3. ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +128 -157
  4. ipulse_shared_core_ftredge/exceptions/base_exceptions.py +35 -4
  5. ipulse_shared_core_ftredge/models/__init__.py +3 -7
  6. ipulse_shared_core_ftredge/models/base_data_model.py +17 -19
  7. ipulse_shared_core_ftredge/models/catalog/__init__.py +10 -0
  8. ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py +274 -0
  9. ipulse_shared_core_ftredge/models/catalog/usertype.py +177 -0
  10. ipulse_shared_core_ftredge/models/user/__init__.py +5 -0
  11. ipulse_shared_core_ftredge/models/user/user_permissions.py +66 -0
  12. ipulse_shared_core_ftredge/models/user/user_subscription.py +348 -0
  13. ipulse_shared_core_ftredge/models/{user_auth.py → user/userauth.py} +19 -10
  14. ipulse_shared_core_ftredge/models/{user_profile.py → user/userprofile.py} +53 -21
  15. ipulse_shared_core_ftredge/models/user/userstatus.py +479 -0
  16. ipulse_shared_core_ftredge/monitoring/__init__.py +0 -2
  17. ipulse_shared_core_ftredge/monitoring/tracemon.py +6 -6
  18. ipulse_shared_core_ftredge/services/__init__.py +11 -13
  19. ipulse_shared_core_ftredge/services/base/__init__.py +3 -1
  20. ipulse_shared_core_ftredge/services/base/base_firestore_service.py +77 -16
  21. ipulse_shared_core_ftredge/services/{cache_aware_firestore_service.py → base/cache_aware_firestore_service.py} +46 -32
  22. ipulse_shared_core_ftredge/services/catalog/__init__.py +14 -0
  23. ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py +277 -0
  24. ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py +376 -0
  25. ipulse_shared_core_ftredge/services/charging_processors.py +25 -25
  26. ipulse_shared_core_ftredge/services/user/__init__.py +5 -25
  27. ipulse_shared_core_ftredge/services/user/user_core_service.py +536 -510
  28. ipulse_shared_core_ftredge/services/user/user_multistep_operations.py +796 -0
  29. ipulse_shared_core_ftredge/services/user/user_permissions_operations.py +392 -0
  30. ipulse_shared_core_ftredge/services/user/user_subscription_operations.py +488 -0
  31. ipulse_shared_core_ftredge/services/user/userauth_operations.py +928 -0
  32. ipulse_shared_core_ftredge/services/user/userprofile_operations.py +166 -0
  33. ipulse_shared_core_ftredge/services/user/userstatus_operations.py +476 -0
  34. ipulse_shared_core_ftredge/services/{charging_service.py → user_charging_service.py} +9 -9
  35. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/METADATA +3 -4
  36. ipulse_shared_core_ftredge-23.1.1.dist-info/RECORD +50 -0
  37. ipulse_shared_core_ftredge/models/subscription.py +0 -190
  38. ipulse_shared_core_ftredge/models/user_status.py +0 -495
  39. ipulse_shared_core_ftredge/monitoring/microservmon.py +0 -526
  40. ipulse_shared_core_ftredge/services/user/iam_management_operations.py +0 -326
  41. ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +0 -384
  42. ipulse_shared_core_ftredge/services/user/user_account_operations.py +0 -479
  43. ipulse_shared_core_ftredge/services/user/user_auth_operations.py +0 -305
  44. ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +0 -436
  45. ipulse_shared_core_ftredge-20.0.1.dist-info/RECORD +0 -42
  46. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/WHEEL +0 -0
  47. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/licenses/LICENCE +0 -0
  48. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.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