ipulse-shared-core-ftredge 20.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.

Files changed (47) hide show
  1. ipulse_shared_core_ftredge/cache/shared_cache.py +1 -2
  2. ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +4 -4
  3. ipulse_shared_core_ftredge/exceptions/base_exceptions.py +23 -0
  4. ipulse_shared_core_ftredge/models/__init__.py +3 -7
  5. ipulse_shared_core_ftredge/models/base_data_model.py +17 -19
  6. ipulse_shared_core_ftredge/models/catalog/__init__.py +10 -0
  7. ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py +273 -0
  8. ipulse_shared_core_ftredge/models/catalog/usertype.py +170 -0
  9. ipulse_shared_core_ftredge/models/user/__init__.py +5 -0
  10. ipulse_shared_core_ftredge/models/user/user_permissions.py +66 -0
  11. ipulse_shared_core_ftredge/models/{subscription.py → user/user_subscription.py} +66 -20
  12. ipulse_shared_core_ftredge/models/{user_auth.py → user/userauth.py} +19 -10
  13. ipulse_shared_core_ftredge/models/{user_profile.py → user/userprofile.py} +53 -21
  14. ipulse_shared_core_ftredge/models/user/userstatus.py +430 -0
  15. ipulse_shared_core_ftredge/monitoring/__init__.py +0 -2
  16. ipulse_shared_core_ftredge/monitoring/tracemon.py +6 -6
  17. ipulse_shared_core_ftredge/services/__init__.py +11 -13
  18. ipulse_shared_core_ftredge/services/base/__init__.py +3 -1
  19. ipulse_shared_core_ftredge/services/base/base_firestore_service.py +73 -14
  20. ipulse_shared_core_ftredge/services/{cache_aware_firestore_service.py → base/cache_aware_firestore_service.py} +46 -32
  21. ipulse_shared_core_ftredge/services/catalog/__init__.py +14 -0
  22. ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py +273 -0
  23. ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py +307 -0
  24. ipulse_shared_core_ftredge/services/charging_processors.py +25 -25
  25. ipulse_shared_core_ftredge/services/user/__init__.py +5 -25
  26. ipulse_shared_core_ftredge/services/user/firebase_auth_admin_helpers.py +160 -0
  27. ipulse_shared_core_ftredge/services/user/user_core_service.py +423 -515
  28. ipulse_shared_core_ftredge/services/user/user_multistep_operations.py +726 -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 +484 -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 +212 -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-22.1.1.dist-info}/METADATA +3 -4
  36. ipulse_shared_core_ftredge-22.1.1.dist-info/RECORD +51 -0
  37. ipulse_shared_core_ftredge/models/user_status.py +0 -495
  38. ipulse_shared_core_ftredge/monitoring/microservmon.py +0 -526
  39. ipulse_shared_core_ftredge/services/user/iam_management_operations.py +0 -326
  40. ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +0 -384
  41. ipulse_shared_core_ftredge/services/user/user_account_operations.py +0 -479
  42. ipulse_shared_core_ftredge/services/user/user_auth_operations.py +0 -305
  43. ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +0 -436
  44. ipulse_shared_core_ftredge-20.0.1.dist-info/RECORD +0 -42
  45. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/WHEEL +0 -0
  46. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/licenses/LICENCE +0 -0
  47. {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,430 @@
1
+ """ User Status model for tracking user subscription and access rights. """
2
+ from datetime import datetime, timezone
3
+ from typing import Set, Optional, Dict, List, ClassVar, Any
4
+ from pydantic import Field, ConfigDict, model_validator, field_validator
5
+ from ipulse_shared_base_ftredge import Layer, Module, list_enums_as_lower_strings, Subject
6
+ from ipulse_shared_base_ftredge.enums.enums_iam import IAMUnit
7
+ from .user_subscription import UserSubscription
8
+ from ..base_data_model import BaseDataModel
9
+ from .user_permissions import UserPermission
10
+
11
+
12
+
13
+ ############################ !!!!! ALWAYS UPDATE SCHEMA VERSION , IF SCHEMA IS BEING MODIFIED !!! #################################
14
+ class UserStatus(BaseDataModel):
15
+ """
16
+ User Status model for tracking user subscription and access rights.
17
+ """
18
+ # Set frozen=False to allow modification of attributes
19
+ model_config = ConfigDict(frozen=False, extra="forbid")
20
+
21
+ # Class constants
22
+ VERSION: ClassVar[float] = 6.0 # Major version bump for flattened IAM permissions structure
23
+ DOMAIN: ClassVar[str] = "_".join(list_enums_as_lower_strings(Layer.PULSE_APP, Module.CORE, Subject.USER))
24
+ OBJ_REF: ClassVar[str] = "userstatus"
25
+
26
+ # Centralized collection name and document ID prefix
27
+ COLLECTION_NAME: ClassVar[str] = "papp_core_user_userstatuss"
28
+
29
+
30
+ # System-managed fields
31
+ schema_version: float = Field(
32
+ default=VERSION,
33
+ frozen=True,
34
+ description="Version of this Class == version of DB Schema"
35
+ )
36
+
37
+ id: Optional[str] = Field(
38
+ default=None, # Will be auto-generated from user_uid if not provided
39
+ description=f"User ID, format: {OBJ_REF}_user_uid"
40
+ )
41
+
42
+ user_uid: str = Field(
43
+ ...,
44
+ min_length=1,
45
+ description="User UID from Firebase Auth",
46
+ frozen=True
47
+ )
48
+
49
+ # Added organizations field for consistency with UserProfile
50
+ organizations_uids: Set[str] = Field(
51
+ default_factory=set,
52
+ description="Organization UIDs the user belongs to"
53
+ )
54
+
55
+ # Simplified IAM permissions structure - flattened for easier management
56
+ iam_permissions: List[UserPermission] = Field(
57
+ default_factory=list,
58
+ description="List of all IAM permission assignments for this user"
59
+ )
60
+
61
+ # Changed from dictionary to single Optional subscription
62
+ active_subscription: Optional[UserSubscription] = Field(
63
+ default=None,
64
+ description="The user's currently active subscription, if any"
65
+ )
66
+
67
+ # Credit management fields
68
+ sbscrptn_based_insight_credits: Optional[int] = Field(
69
+ default=0,
70
+ ge=0, # Must be >= 0
71
+ description="Subscription-based insight credits (expire with subscription)"
72
+ )
73
+
74
+ sbscrptn_based_insight_credits_updtd_on: datetime = Field(
75
+ default_factory=lambda: datetime.now(timezone.utc),
76
+ description="Last update timestamp for subscription credits"
77
+ )
78
+
79
+ extra_insight_credits: Optional[int] = Field(
80
+ default=0,
81
+ ge=0, # Must be >= 0
82
+ description="Additional purchased insight credits (non-expiring)"
83
+ )
84
+
85
+ extra_insight_credits_updtd_on: datetime = Field(
86
+ default_factory=lambda: datetime.now(timezone.utc),
87
+ description="Last update timestamp for extra credits"
88
+ )
89
+
90
+ voting_credits: Optional[int] = Field(
91
+ default=0,
92
+ ge=0, # Must be >= 0
93
+ description="Voting credits for user"
94
+ )
95
+
96
+ voting_credits_updtd_on: datetime = Field(
97
+ default_factory=lambda: datetime.now(timezone.utc),
98
+ description="Last update timestamp for voting credits"
99
+ )
100
+
101
+ metadata: Dict[str, Any] = Field(
102
+ default_factory=dict,
103
+ description="Additional metadata for the user status"
104
+ )
105
+
106
+ @field_validator('user_uid')
107
+ @classmethod
108
+ def validate_user_uid(cls, v: str) -> str:
109
+ """Validate that user_uid is not empty string."""
110
+ if not v or not v.strip():
111
+ raise ValueError("user_uid cannot be empty or whitespace-only")
112
+ return v.strip()
113
+
114
+ @model_validator(mode='before')
115
+ @classmethod
116
+ def ensure_id_exists(cls, data: Dict[str, Any]) -> Dict[str, Any]:
117
+ """
118
+ Ensures the id field exists and matches expected format, or generates it from user_uid.
119
+ This runs BEFORE validation, guaranteeing id will be present for validators.
120
+ """
121
+ if not isinstance(data, dict):
122
+ return data
123
+
124
+ user_uid = data.get('user_uid')
125
+ if not user_uid:
126
+ return data # Let field validation handle missing user_uid
127
+
128
+ expected_id = f"{cls.OBJ_REF}_{user_uid}"
129
+
130
+ # If id is already provided, validate it matches expected format
131
+ if data.get('id'):
132
+ if data['id'] != expected_id:
133
+ raise ValueError(f"Invalid id format. Expected '{expected_id}', got '{data['id']}'")
134
+ return data
135
+
136
+ # If id is not provided, generate it from user_uid
137
+ data['id'] = expected_id
138
+ return data
139
+
140
+
141
+ ########################################################################
142
+ ############ ######### IAM Permission Management ######### #############
143
+ ########################################################################
144
+
145
+ def get_valid_permissions(
146
+ self,
147
+ domain: Optional[str] = None,
148
+ iam_unit_type: Optional[IAMUnit] = None,
149
+ permission_ref: Optional[str] = None
150
+ ) -> List[UserPermission]:
151
+ """
152
+ Get all valid (non-expired) permissions, optionally filtered.
153
+
154
+ Args:
155
+ domain: Filter by domain (e.g., 'papp')
156
+ iam_unit_type: Filter by IAM unit type (GROUP, ROLE, etc.)
157
+ permission_ref: Filter by permission reference name
158
+
159
+ Returns:
160
+ List of valid permissions matching the filters
161
+ """
162
+ valid_permissions = [perm for perm in self.iam_permissions if perm.is_valid()]
163
+
164
+ if domain is not None:
165
+ valid_permissions = [perm for perm in valid_permissions if perm.domain == domain]
166
+
167
+ if iam_unit_type is not None:
168
+ valid_permissions = [perm for perm in valid_permissions if perm.iam_unit_type == iam_unit_type]
169
+
170
+ if permission_ref is not None:
171
+ valid_permissions = [perm for perm in valid_permissions if perm.permission_ref == permission_ref]
172
+
173
+ return valid_permissions
174
+
175
+ def add_permission(self, permission: UserPermission) -> None:
176
+ """
177
+ Add a single permission assignment.
178
+
179
+ Args:
180
+ permission: UserPermission object to add
181
+ """
182
+ self.iam_permissions.append(permission)
183
+
184
+ def add_permission_from_fields(
185
+ self,
186
+ domain: str,
187
+ iam_unit_type: IAMUnit,
188
+ permission_ref: str,
189
+ source: str,
190
+ expires_at: Optional[datetime] = None,
191
+ granted_by: Optional[str] = None,
192
+ metadata: Optional[Dict[str, Any]] = None
193
+ ) -> None:
194
+ """
195
+ Add a permission assignment using individual fields (convenience method).
196
+
197
+ Args:
198
+ domain: The domain for the permission (e.g., 'papp')
199
+ iam_unit_type: Type of IAM assignment (GROUP, ROLE, etc.)
200
+ permission_ref: The name/identifier of the permission to add
201
+ source: Source identifier for this assignment (e.g., subscription ID)
202
+ expires_at: Optional expiration date
203
+ granted_by: Who granted this permission
204
+ metadata: Optional metadata for the assignment
205
+ """
206
+ permission = UserPermission(
207
+ domain=domain,
208
+ iam_unit_type=iam_unit_type,
209
+ permission_ref=permission_ref,
210
+ source=source,
211
+ expires_at=expires_at,
212
+ granted_by=granted_by,
213
+ metadata=metadata or {}
214
+ )
215
+ self.add_permission(permission)
216
+
217
+ def remove_permission(
218
+ self,
219
+ domain: Optional[str] = None,
220
+ iam_unit_type: Optional[IAMUnit] = None,
221
+ permission_ref: Optional[str] = None,
222
+ source: Optional[str] = None
223
+ ) -> int:
224
+ """
225
+ Remove permission assignments matching the criteria.
226
+ At least one filter criteria must be provided.
227
+
228
+ Args:
229
+ domain: Optional domain filter (e.g., 'papp')
230
+ iam_unit_type: Optional IAM assignment type filter (GROUP, ROLE, etc.)
231
+ permission_ref: Optional permission name/identifier filter
232
+ source: Optional source filter
233
+
234
+ Returns:
235
+ Number of permissions removed
236
+
237
+ Raises:
238
+ ValueError: If no filter criteria are provided
239
+ """
240
+ if not any([domain, iam_unit_type, permission_ref, source]):
241
+ raise ValueError("At least one filter criteria must be provided")
242
+
243
+ initial_count = len(self.iam_permissions)
244
+
245
+ def matches_criteria(perm: UserPermission) -> bool:
246
+ """Check if permission matches the removal criteria"""
247
+ if domain is not None and perm.domain != domain:
248
+ return False
249
+ if iam_unit_type is not None and perm.iam_unit_type != iam_unit_type:
250
+ return False
251
+ if permission_ref is not None and perm.permission_ref != permission_ref:
252
+ return False
253
+ if source is not None and perm.source != source:
254
+ return False
255
+ return True
256
+
257
+ self.iam_permissions = [perm for perm in self.iam_permissions if not matches_criteria(perm)]
258
+
259
+ return initial_count - len(self.iam_permissions)
260
+
261
+ def remove_all_permissions(self, source: Optional[str] = None) -> int:
262
+ """
263
+ Remove all permission assignments, optionally filtered by source.
264
+
265
+ Args:
266
+ source: Optional source filter (if None, removes all permissions)
267
+
268
+ Returns:
269
+ Number of permissions removed
270
+ """
271
+ initial_count = len(self.iam_permissions)
272
+
273
+ if source is None:
274
+ self.iam_permissions = []
275
+ else:
276
+ self.iam_permissions = [perm for perm in self.iam_permissions if perm.source != source]
277
+
278
+ return initial_count - len(self.iam_permissions)
279
+
280
+ def cleanup_expired_permissions(self, iam_unit_type: Optional[IAMUnit] = None) -> int:
281
+ """
282
+ Remove all expired permission assignments of a specific type or all types.
283
+
284
+ Args:
285
+ iam_unit_type: If provided, only remove this type of permissions
286
+
287
+ Returns:
288
+ Number of removed permission assignments
289
+ """
290
+ initial_count = len(self.iam_permissions)
291
+
292
+ if iam_unit_type is None:
293
+ self.iam_permissions = [perm for perm in self.iam_permissions if perm.is_valid()]
294
+ else:
295
+ self.iam_permissions = [
296
+ perm for perm in self.iam_permissions
297
+ if perm.is_valid() or perm.iam_unit_type != iam_unit_type
298
+ ]
299
+
300
+ return initial_count - len(self.iam_permissions)
301
+
302
+ ########################################################################
303
+ ############ ######### User Subscription Management ######### #############
304
+ ########################################################################
305
+
306
+ def update_user_permissions_from_subscription(self, subscription: UserSubscription, granted_by: Optional[str] = None) -> int:
307
+ """
308
+ Update user permissions based on a subscription.
309
+ Uses the new flattened permission structure.
310
+
311
+ Args:
312
+ subscription: Subscription to apply
313
+ granted_by: Who granted this permission (user ID, system process, etc.)
314
+
315
+ Returns:
316
+ Number of permission assignments added
317
+ """
318
+ added_count = 0
319
+ # Use the subscription plan_id as the source (which already contains "subscription" and version)
320
+ source = subscription.plan_id
321
+
322
+ # The granted_iam_permissions in Subscription is now List[UserPermission]
323
+ # We add each permission directly with subscription expiration
324
+
325
+ for permission in subscription.granted_iam_permissions:
326
+ # Create a new permission with subscription's expiration date and source
327
+ self.add_permission_from_fields(
328
+ domain=permission.domain,
329
+ iam_unit_type=permission.iam_unit_type,
330
+ permission_ref=permission.permission_ref,
331
+ source=source,
332
+ expires_at=subscription.cycle_end_date,
333
+ granted_by=granted_by
334
+ )
335
+ added_count += 1
336
+
337
+ return added_count
338
+
339
+ # Method instead of computed field
340
+ def is_subscription_active(self) -> bool:
341
+ """Check if the user has an active subscription."""
342
+ if self.active_subscription:
343
+ return self.active_subscription.is_active()
344
+ return False
345
+
346
+ # Method instead of computed field
347
+ def subscription_expires_in_days(self) -> Optional[int]:
348
+ """Get days until subscription expiration."""
349
+ if self.active_subscription and self.active_subscription.is_active():
350
+ return self.active_subscription.days_remaining()
351
+ return None
352
+
353
+ def apply_subscription(self, subscription: UserSubscription, add_associated_permissions: bool = True, remove_existing_subscription_permissions: bool = True, granted_by: Optional[str] = None) -> int:
354
+ """
355
+ Apply a subscription's benefits to the user status.
356
+ This updates credits, permissions, and sets the active subscription.
357
+
358
+ Args:
359
+ subscription: The subscription to apply
360
+ add_associated_permissions: If True, adds IAM permissions from the subscription
361
+ remove_existing_subscription_permissions: If True, removes IAM permissions from any existing subscription
362
+ granted_by: Who granted this permission (user ID, system process, etc.)
363
+
364
+ Returns:
365
+ Number of permissions added (0 if add_associated_permissions=False)
366
+ """
367
+ if not subscription:
368
+ return 0
369
+
370
+ permissions_added = 0
371
+
372
+ # Remove existing subscription permissions if requested
373
+ if remove_existing_subscription_permissions and self.active_subscription:
374
+ # Use the subscription plan_id as the source (which already contains "subscription" and version)
375
+ source = self.active_subscription.plan_id
376
+ removed_permissions = self.remove_all_permissions(source=source)
377
+ if removed_permissions > 0:
378
+ pass # Note: We don't return this count as it's not part of the "added" permissions
379
+
380
+ # Add IAM permissions from subscription if requested
381
+ if add_associated_permissions:
382
+ permissions_added = self.update_user_permissions_from_subscription(subscription, granted_by=granted_by)
383
+
384
+ # Update subscription-based credits
385
+ credits_per_update = subscription.subscription_based_insight_credits_per_update
386
+ if credits_per_update > 0:
387
+ self.sbscrptn_based_insight_credits = credits_per_update
388
+ self.sbscrptn_based_insight_credits_updtd_on = datetime.now(timezone.utc)
389
+
390
+ # Update voting credits directly from subscription attributes
391
+ voting_credits = subscription.voting_credits_per_update
392
+ if voting_credits > 0:
393
+ self.voting_credits = voting_credits
394
+ self.voting_credits_updtd_on = datetime.now(timezone.utc)
395
+
396
+ # Set as active subscription
397
+ self.active_subscription = subscription
398
+
399
+ return permissions_added
400
+
401
+ def revoke_subscription(self, remove_associated_permissions: bool = True) -> int:
402
+ """
403
+ Revoke the current subscription benefits.
404
+ This clears subscription-based credits and removes the active subscription.
405
+ Optionally also revokes associated IAM permissions.
406
+
407
+ Args:
408
+ remove_associated_permissions: If True, removes all IAM permissions
409
+ associated with the current subscription
410
+
411
+ Returns:
412
+ Number of permissions removed (0 if remove_associated_permissions=False or no subscription)
413
+ """
414
+ if not self.active_subscription:
415
+ return 0
416
+
417
+ permissions_removed = 0
418
+
419
+ # Revoke associated IAM permissions if requested
420
+ if remove_associated_permissions:
421
+ # Use the subscription plan_id as the source (which already contains "subscription" and version)
422
+ source = self.active_subscription.plan_id
423
+ permissions_removed = self.remove_all_permissions(source=source)
424
+
425
+ # Clear subscription-based credits and active subscription
426
+ self.sbscrptn_based_insight_credits = 0
427
+ self.sbscrptn_based_insight_credits_updtd_on = datetime.now(timezone.utc)
428
+ self.active_subscription = None
429
+
430
+ return permissions_removed
@@ -1,7 +1,5 @@
1
- from .microservmon import Microservmon
2
1
  from .tracemon import TraceMon
3
2
 
4
3
  __all__ = [
5
- 'Microservmon',
6
4
  'TraceMon',
7
5
  ]
@@ -72,7 +72,7 @@ class TraceMon:
72
72
  self._metrics = {
73
73
  "trace_id": self._id, # In TraceMon, collector_id and trace_id are the same
74
74
  "start_time": self._start_time,
75
- "status": ProgressStatus.IN_PROGRESS.name,
75
+ "status": str(ProgressStatus.IN_PROGRESS),
76
76
  "by_event_count": defaultdict(int),
77
77
  "by_level_code_count": defaultdict(int),
78
78
  "status_counts": StatusCounts()
@@ -144,7 +144,7 @@ class TraceMon:
144
144
  start_log = StructLog(
145
145
  level=LogLevel.INFO,
146
146
  description=f"Starting trace {self._id}",
147
- resource=AbstractResource.MICROSERVICE_TRACE,
147
+ resource=AbstractResource.TRACEMON,
148
148
  action=Action.EXECUTE,
149
149
  progress_status=ProgressStatus.STARTED,
150
150
  )
@@ -266,13 +266,13 @@ class TraceMon:
266
266
  level = map_progress_status_to_log_level(final_status)
267
267
 
268
268
  # Update trace status
269
- self._metrics["status"] = final_status.name
269
+ self._metrics["status"] = str(final_status)
270
270
  self._metrics["status_counts"].add_status(final_status)
271
271
 
272
272
  # Log completion
273
273
  status_source = "FORCED" if force_status is not None else "AUTO"
274
274
  summary_msg = (
275
- f"Trace {self._id} completed with status {final_status.name} ({status_source}). "
275
+ f"Trace {self._id} completed with status {str(final_status)} ({status_source}). "
276
276
  f"Duration: {self.duration_ms}ms. "
277
277
  f"Errors: {error_count}, Warnings: {warning_count}, Notices: {notice_count}"
278
278
  )
@@ -280,7 +280,7 @@ class TraceMon:
280
280
  completion_log = StructLog(
281
281
  level=level,
282
282
  description=summary_msg,
283
- resource=AbstractResource.MICROSERVICE_TRACE,
283
+ resource=AbstractResource.TRACEMON,
284
284
  action=Action.EXECUTE,
285
285
  progress_status=final_status
286
286
  )
@@ -296,7 +296,7 @@ class TraceMon:
296
296
  if count > 0:
297
297
  for level in LogLevel:
298
298
  if level.value == level_code:
299
- readable_counts[level.name] = count
299
+ readable_counts[str(level)] = count
300
300
  break
301
301
 
302
302
  return readable_counts
@@ -2,24 +2,22 @@
2
2
 
3
3
 
4
4
  # Import from base services
5
- from .base import BaseFirestoreService
6
- from .cache_aware_firestore_service import CacheAwareFirestoreService
5
+ from .base import BaseFirestoreService, CacheAwareFirestoreService
7
6
 
8
7
  from .charging_processors import ChargingProcessor
9
- from .charging_service import ChargingService
8
+ from .user_charging_service import UserChargingService
10
9
 
11
10
  # Import user services from the user package
12
11
  from .user import (
13
12
  UserCoreService,
14
- UserAccountOperations, SubscriptionManagementOperations,
15
- IAMManagementOperations, UserAuthOperations, UserHolisticOperations,
16
- SubscriptionPlanDocument, UserTypeDefaultsDocument
13
+ UserauthOperations,
14
+ UserpermissionsOperations,
15
+ UsersubscriptionOperations,
16
+ UsermultistepOperations,
17
17
  )
18
18
 
19
- __all__ = [ 'BaseFirestoreService',
20
- 'CacheAwareFirestoreService',
21
- 'ChargingProcessor', 'ChargingService', 'UserCoreService',
22
- 'UserAccountOperations', 'SubscriptionManagementOperations',
23
- 'IAMManagementOperations', 'UserAuthOperations', 'UserHolisticOperations',
24
- 'SubscriptionPlanDocument', 'UserTypeDefaultsDocument'
25
- ]
19
+ # Import catalog services
20
+ from .catalog import (
21
+ CatalogSubscriptionPlanService,
22
+ CatalogUserTypeService,
23
+ )
@@ -6,7 +6,9 @@ preventing circular import dependencies.
6
6
  """
7
7
 
8
8
  from .base_firestore_service import BaseFirestoreService
9
+ from .cache_aware_firestore_service import CacheAwareFirestoreService
9
10
 
10
11
  __all__ = [
11
- 'BaseFirestoreService'
12
+ 'BaseFirestoreService',
13
+ 'CacheAwareFirestoreService'
12
14
  ]