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
@@ -4,47 +4,32 @@ Enhanced UserCoreService - Comprehensive user management orchestration
4
4
  This service orchestrates all user-related operations by composing specialized
5
5
  operation classes for different concerns:
6
6
  - Firebase Auth User Management
7
- - User Account Management (UserProfile and UserStatus CRUD operations)
7
+ - User Profile and Status Management
8
8
  - User Deletion Operations
9
9
  - Subscription Management
10
10
  - IAM Management
11
- - Default Values by UserType
11
+ - Configuration by UserType
12
12
 
13
- Can be used by Firebase Cloud Functions, Core microservice APIs, admin tools, and tests.
13
+ Can be used by Firebase Cloud Functions, admin tools, service APIs, admin tools, and tests.
14
14
  """
15
15
  import logging
16
- from typing import Any, Dict, List, Optional, Set, Tuple
16
+ from typing import Any, Dict, List, Optional, Tuple, Union
17
+ from datetime import datetime
17
18
  from google.cloud import firestore
18
- from pydantic import BaseModel
19
+ from firebase_admin.auth import UserRecord
20
+ from ipulse_shared_base_ftredge import ApprovalStatus, IAMUnit, IAMUserType
21
+ from ...models import UserProfile, UserStatus, UserAuth, UserSubscription, UserType, SubscriptionPlan, UserPermission
19
22
 
20
- from ...models.user_profile import UserProfile
21
- from ...models.user_status import UserStatus, IAMUnitRefAssignment
22
- from ...models.user_auth import UserAuth
23
- from ...models.subscription import Subscription
24
- from ...exceptions import ServiceError, ResourceNotFoundError, UserCreationError
25
- from ..base import BaseFirestoreService
26
- from ipulse_shared_base_ftredge.enums.enums_iam import IAMUnitType
27
23
 
28
24
  # Import specialized operation classes
29
- from .user_account_operations import UserAccountOperations
30
- from .subscription_management_operations import SubscriptionManagementOperations, SubscriptionPlanDocument
31
- from .iam_management_operations import IAMManagementOperations
32
- from .user_auth_operations import UserAuthOperations
33
- from .user_holistic_operations import UserHolisticOperations
34
-
35
-
36
- # Model for user type defaults from Firestore
37
- class UserTypeDefaultsDocument(BaseModel):
38
- """Model for user type defaults documents stored in Firestore"""
39
- id: str
40
- default_iam_domain_permissions: Optional[Dict[str, Dict[str, Dict[str, IAMUnitRefAssignment]]]] = None
41
- default_subscription_based_insight_credits: Optional[int] = None
42
- default_extra_insight_credits: Optional[int] = None
43
- default_voting_credits: Optional[int] = None
44
- default_subscription_plan_if_unpaid: Optional[str] = None
45
- default_secondary_usertypes: Optional[List[str]] = None
46
- default_organizations_uids: Optional[List[str]] = None
47
- default_subscription_plan_id: Optional[str] = None
25
+ from .userprofile_operations import UserprofileOperations
26
+ from .userstatus_operations import UserstatusOperations
27
+ from .user_subscription_operations import UsersubscriptionOperations
28
+ from .user_permissions_operations import UserpermissionsOperations
29
+ from .userauth_operations import UserauthOperations
30
+ from .user_multistep_operations import UsermultistepOperations
31
+ from ..catalog.catalog_usertype_service import CatalogUserTypeService
32
+ from ..catalog.catalog_subscriptionplan_service import CatalogSubscriptionPlanService
48
33
 
49
34
 
50
35
  class UserCoreService:
@@ -62,9 +47,7 @@ class UserCoreService:
62
47
  logger: Optional[logging.Logger] = None,
63
48
  default_timeout: float = 10.0,
64
49
  profile_collection: Optional[str] = None,
65
- status_collection: Optional[str] = None,
66
- subscriptionplans_defaults_collection: str = "papp_core_configs_subscriptionplans_defaults",
67
- users_defaults_collection: str = "papp_core_configs_users_defaults"
50
+ status_collection: Optional[str] = None
68
51
  ):
69
52
  """
70
53
  Initialize the Enhanced UserCoreService
@@ -75,8 +58,8 @@ class UserCoreService:
75
58
  default_timeout: Default timeout for Firestore operations
76
59
  profile_collection: Collection name for user profiles
77
60
  status_collection: Collection name for user statuses
78
- subscriptionplans_defaults_collection: Collection name for subscription plans
79
- users_defaults_collection: Collection name for user defaults
61
+ subscription_plans_collection: Collection name for subscription plans
62
+ user_types_collection: Collection name for user types
80
63
  """
81
64
  self.db = firestore_client
82
65
  self.logger = logger or logging.getLogger(__name__)
@@ -86,566 +69,609 @@ class UserCoreService:
86
69
  self.status_collection_name = status_collection or UserStatus.get_collection_name()
87
70
 
88
71
  # Initialize specialized operation classes in dependency order
89
- self.user_auth_ops = UserAuthOperations(
72
+ self.userauth_ops = UserauthOperations(
90
73
  logger=self.logger
91
74
  )
92
75
 
93
- self.user_account_ops = UserAccountOperations(
76
+ self.userprofile_ops = UserprofileOperations(
94
77
  firestore_client=self.db,
95
78
  logger=self.logger,
96
79
  timeout=self.timeout,
97
80
  profile_collection=self.profile_collection_name,
98
- status_collection=self.status_collection_name
99
81
  )
100
82
 
101
- self.subscription_ops = SubscriptionManagementOperations(
83
+ self.userstatus_ops = UserstatusOperations(
102
84
  firestore_client=self.db,
103
- user_account_ops=self.user_account_ops,
104
85
  logger=self.logger,
105
86
  timeout=self.timeout,
106
- subscription_plans_collection=subscriptionplans_defaults_collection
87
+ status_collection=self.status_collection_name,
107
88
  )
108
89
 
109
- self.iam_ops = IAMManagementOperations(
110
- user_account_ops=self.user_account_ops,
90
+ self.iam_ops = UserpermissionsOperations(
91
+ userstatus_ops=self.userstatus_ops,
111
92
  logger=self.logger
112
93
  )
113
94
 
114
- # Initialize holistic operations last as it depends on other operations
115
- self.user_holistic_ops = UserHolisticOperations(
116
- user_account_ops=self.user_account_ops,
117
- user_auth_ops=self.user_auth_ops,
118
- subscription_ops=self.subscription_ops,
119
- iam_ops=self.iam_ops,
95
+ self.subscription_ops = UsersubscriptionOperations(
96
+ firestore_client=self.db,
97
+ userstatus_ops=self.userstatus_ops,
98
+ permissions_ops=self.iam_ops,
99
+ logger=self.logger,
100
+ timeout=self.timeout
101
+ )
102
+
103
+ # Initialize catalog services
104
+ self.catalog_usertype_service = CatalogUserTypeService(
105
+ firestore_client=self.db,
120
106
  logger=self.logger
121
107
  )
122
108
 
123
- # Initialize defaults values per usertype service
124
- self.usertype_defaults_collection_name = users_defaults_collection
125
- self._user_defaults_db_service = BaseFirestoreService[UserTypeDefaultsDocument](
126
- db=self.db,
127
- collection_name=self.usertype_defaults_collection_name,
128
- resource_type="UserTypeDefault",
129
- logger=self.logger,
130
- timeout=self.timeout
109
+ self.catalog_subscriptionplan_service = CatalogSubscriptionPlanService(
110
+ firestore_client=self.db,
111
+ logger=self.logger
112
+ )
113
+
114
+ # Initialize multistep operations last as it depends on other operations
115
+ self.usermultistep_ops = UsermultistepOperations(
116
+ userprofile_ops=self.userprofile_ops,
117
+ userstatus_ops=self.userstatus_ops,
118
+ userauth_ops=self.userauth_ops,
119
+ usersubscription_ops=self.subscription_ops,
120
+ useriam_ops=self.iam_ops,
121
+ catalog_usertype_service=self.catalog_usertype_service,
122
+ catalog_subscriptionplan_service=self.catalog_subscriptionplan_service,
123
+ logger=self.logger
131
124
  )
132
125
 
133
126
 
134
127
  ######################################################################
135
- ######################### UserAuth Operation #########################
128
+ ######################### Complete User Creation ####################
129
+ ######################################################################
136
130
 
131
+ async def create_user_from_models(
132
+ self,
133
+ userprofile: UserProfile,
134
+ userstatus: UserStatus,
135
+ userauth: Optional[UserAuth] = None,
136
+ validate_userauth_consistency: bool = False,
137
+ validate_userauth_exists: bool = False
138
+ ) -> Tuple[str, UserProfile, UserStatus]:
139
+ """
140
+ Create a complete user from ready UserAuth, UserProfile, and UserStatus models.
137
141
 
142
+ Delegates to UsermultistepOperations for the actual creation.
143
+ """
144
+ return await self.usermultistep_ops.create_user_from_models(
145
+ userprofile=userprofile,
146
+ userstatus=userstatus,
147
+ userauth=userauth,
148
+ validate_userauth_consistency=validate_userauth_consistency,
149
+ validate_userauth_exists=validate_userauth_exists
150
+ )
138
151
 
152
+ async def create_user_from_manual_usertype(
153
+ self,
154
+ userprofile: UserProfile,
155
+ usertype: UserType,
156
+ userauth: Optional[UserAuth] = None,
157
+ extra_insight_credits_override: Optional[int] = None,
158
+ voting_credits_override: Optional[int] = None,
159
+ subscriptionplan_id_override: Optional[str] = None,
160
+ created_by: Optional[str] = None,
161
+ apply_usertype_associated_subscriptionplan: bool = True,
162
+ validate_userauth_consistency: bool = False,
163
+ validate_userauth_exists: bool = False
164
+ ) -> Tuple[str, UserProfile, UserStatus]:
165
+ """
166
+ Create a complete user with manual UserType configuration.
139
167
 
168
+ Delegates to UsermultistepOperations for the actual creation.
169
+ """
170
+ return await self.usermultistep_ops.create_user_from_manual_usertype(
171
+ userprofile=userprofile,
172
+ usertype=usertype,
173
+ userauth=userauth,
174
+ extra_insight_credits_override=extra_insight_credits_override,
175
+ voting_credits_override=voting_credits_override,
176
+ subscriptionplan_id_override=subscriptionplan_id_override,
177
+ creator_uid=created_by,
178
+ apply_usertype_associated_subscriptionplan=apply_usertype_associated_subscriptionplan,
179
+ validate_userauth_consistency=validate_userauth_consistency,
180
+ validate_userauth_exists=validate_userauth_exists
181
+ )
140
182
 
141
- def generate_firebase_custom_claims(
183
+ async def create_user_from_catalog_usertype(
142
184
  self,
143
- primary_usertype: str,
144
- secondary_usertypes: Optional[List[str]] = None,
145
- organizations_uids: Optional[List[str]] = None,
146
- user_approval_status: str = "pending"
147
- ) -> Dict[str, Any]:
148
- """Generate Firebase custom claims for a user"""
149
- return self.user_auth_ops.generate_firebase_custom_claims(
150
- primary_usertype=primary_usertype,
151
- secondary_usertypes=secondary_usertypes,
152
- organizations_uids=organizations_uids,
153
- user_approval_status=user_approval_status
154
- )
185
+ usertype_id: str,
186
+ userprofile: UserProfile,
187
+ userauth: Optional[UserAuth] = None,
188
+ extra_insight_credits_override: Optional[int] = None,
189
+ voting_credits_override: Optional[int] = None,
190
+ subscriptionplan_id_override: Optional[str] = None,
191
+ created_by: Optional[str] = None,
192
+ apply_usertype_associated_subscriptionplan: bool = True,
193
+ validate_userauth_consistency: bool = False,
194
+ validate_userauth_exists: bool = False
195
+ ) -> Tuple[str, UserProfile, UserStatus]:
196
+ """
197
+ Create a complete user based on UserType catalog configuration.
155
198
 
199
+ Delegates to UsermultistepOperations for the actual creation.
200
+ """
201
+ return await self.usermultistep_ops.create_user_from_catalog_usertype(
202
+ usertype_id=usertype_id,
203
+ userprofile=userprofile,
204
+ userauth=userauth,
205
+ extra_insight_credits_override=extra_insight_credits_override,
206
+ voting_credits_override=voting_credits_override,
207
+ subscriptionplan_id_override=subscriptionplan_id_override,
208
+ creator_uid=created_by,
209
+ apply_usertype_associated_subscriptionplan=apply_usertype_associated_subscriptionplan,
210
+ validate_userauth_consistency=validate_userauth_consistency,
211
+ validate_userauth_exists=validate_userauth_exists
212
+ )
156
213
 
214
+ ######################################################################
215
+ ######################### User Validation ############################
216
+ ######################################################################
157
217
 
158
- ################################################################################################
159
- ######################### Fetching Default User/Subscription Values #########################
160
-
161
- async def fetch_user_defaults(self, usertype_name: str) -> Optional[Dict[str, Any]]:
162
- """Fetch user type defaults from Firestore"""
163
- try:
164
- user_defaults_data = await self._user_defaults_db_service.get_document(usertype_name)
165
- if user_defaults_data:
166
- self.logger.info(f"User defaults found for usertype: {usertype_name}")
167
- # Convert UserTypeDefaultsDocument to dict for backward compatibility
168
- if isinstance(user_defaults_data, UserTypeDefaultsDocument):
169
- return user_defaults_data.model_dump()
170
- return user_defaults_data
171
- else:
172
- self.logger.warning(f"User defaults not found for usertype: {usertype_name}")
173
- return None
174
- except ResourceNotFoundError:
175
- self.logger.warning(f"User defaults not found for usertype: {usertype_name}")
176
- return None
177
- except Exception as e:
178
- self.logger.error(f"Error fetching user defaults for {usertype_name}: {e}", exc_info=True)
179
- raise ServiceError(
180
- operation="fetch_user_defaults",
181
- resource_type="UserTypeDefault",
182
- resource_id=usertype_name,
183
- error=e
184
- )
185
-
186
- async def _fetch_subscription_plan_details(self, plan_id: str) -> Optional[SubscriptionPlanDocument]:
187
- """Fetch subscription plan details (backward compatibility)"""
188
- return await self.subscription_ops.fetch_subscription_plan_details(plan_id)
189
-
190
- ###############################################################################
191
- ############################## User Account Operation #########################
218
+ async def user_exists_fully(self, user_uid: str) -> Dict[str, bool]:
219
+ """Check if complete user exists (Auth, Profile, Status)"""
220
+ return await self.usermultistep_ops.user_exists_fully(user_uid=user_uid)
192
221
 
193
- async def get_userprofile(self, user_uid: str) -> Optional[UserProfile]:
194
- """Get a user profile by user UID"""
195
- return await self.user_account_ops.get_userprofile(user_uid)
222
+ async def validate_user_fully_enabled(
223
+ self,
224
+ user_uid: str,
225
+ email_verified_must: bool = True,
226
+ approved_must: bool = True,
227
+ active_subscription_must: bool = True,
228
+ valid_permissions_must: bool = True
229
+ ) -> Dict[str, Any]:
230
+ """
231
+ Validate complete user integrity and operational readiness with configurable requirements.
196
232
 
197
- async def get_userstatus(self, user_uid: str) -> Optional[UserStatus]:
198
- """Get a user status by user UID"""
199
- return await self.user_account_ops.get_userstatus(user_uid)
233
+ Delegates to UsermultistepOperations for comprehensive validation.
234
+ """
235
+ return await self.usermultistep_ops.validate_user_fully_enabled(
236
+ user_uid=user_uid,
237
+ email_verified_must=email_verified_must,
238
+ approved_must=approved_must,
239
+ active_subscription_must=active_subscription_must,
240
+ valid_permissions_must=valid_permissions_must
241
+ )
200
242
 
201
- async def create_userprofile(self, user_profile: UserProfile) -> UserProfile:
202
- """Create a new user profile"""
203
- return await self.user_account_ops.create_userprofile(user_profile)
243
+ ######################################################################
244
+ ######################### User Deletion ##############################
245
+ ######################################################################
204
246
 
205
- async def create_userstatus(
247
+ async def delete_user(
206
248
  self,
207
249
  user_uid: str,
208
- primary_usertype: str,
209
- organizations_uids: Optional[Set[str]] = None,
210
- secondary_usertypes: Optional[List[str]] = None,
211
- initial_subscription_plan_id: Optional[str] = None,
212
- iam_domain_permissions: Optional[Dict[str, Dict[str, Dict[str, IAMUnitRefAssignment]]]] = None,
213
- sbscrptn_based_insight_credits: int = 0,
214
- extra_insight_credits: int = 0,
215
- voting_credits: int = 0,
216
- metadata: Optional[Dict[str, Any]] = None,
217
- created_by: Optional[str] = None
218
- ) -> UserStatus:
219
- """Create a new user status with optional initial subscription"""
220
- user_status = await self.user_account_ops.create_userstatus(
250
+ updater_uid: str,
251
+ delete_auth_user: bool = True,
252
+ delete_profile: bool = True,
253
+ delete_status: bool = True,
254
+ archive: bool = True
255
+ ) -> Dict[str, Any]:
256
+ """
257
+ Delete a user holistically, including their auth, profile, and status.
258
+
259
+ Delegates to UsermultistepOperations for the actual deletion.
260
+ """
261
+ return await self.usermultistep_ops.delete_user(
221
262
  user_uid=user_uid,
222
- primary_usertype=primary_usertype,
223
- organizations_uids=organizations_uids,
224
- secondary_usertypes=secondary_usertypes,
225
- iam_domain_permissions=iam_domain_permissions,
226
- sbscrptn_based_insight_credits=sbscrptn_based_insight_credits,
227
- extra_insight_credits=extra_insight_credits,
228
- voting_credits=voting_credits,
229
- metadata=metadata,
230
- created_by=created_by
263
+ delete_auth_user=delete_auth_user,
264
+ delete_profile=delete_profile,
265
+ delete_status=delete_status,
266
+ updater_uid=updater_uid,
267
+ archive=archive
231
268
  )
232
269
 
233
- # Apply initial subscription if provided
234
- if initial_subscription_plan_id:
235
- try:
236
- await self.subscription_ops.apply_subscription_plan(
237
- user_uid=user_uid,
238
- plan_id=initial_subscription_plan_id,
239
- source=f"initial_setup_by_{created_by or 'system'}"
240
- )
241
- # Fetch updated status with subscription
242
- updated_status = await self.get_userstatus(user_uid)
243
- return updated_status or user_status
244
- except Exception as e:
245
- self.logger.error(f"Failed to apply initial subscription plan {initial_subscription_plan_id} for {user_uid}: {e}")
246
- # Return the status without subscription rather than failing completely
247
-
248
- return user_status
249
-
250
- async def update_userprofile(self, user_uid: str, profile_data: Dict[str, Any], updater_uid: str) -> UserProfile:
251
- """Update a user profile"""
252
- return await self.user_account_ops.update_userprofile(user_uid, profile_data, updater_uid)
253
-
254
- async def update_userstatus(self, user_uid: str, status_data: Dict[str, Any], updater_uid: str) -> UserStatus:
255
- """Update a user status"""
256
- return await self.user_account_ops.update_userstatus(user_uid, status_data, updater_uid)
257
-
258
- ##########################################################################################
259
- ################################ Hollistic User Creation #################################
260
-
261
- async def create_user(
270
+ async def batch_delete_users(
262
271
  self,
263
- email: str,
264
- provider_id: str,
265
- primary_usertype_name: str,
266
- initial_subscription_plan_id: Optional[str] = None,
267
- organizations_uids: Optional[Set[str]] = None,
268
- secondary_usertype_names: Optional[List[str]] = None,
269
- profile_custom_data: Optional[Dict[str, Any]] = None,
270
- status_custom_data: Optional[Dict[str, Any]] = None,
271
- created_by: Optional[str] = None,
272
- use_firestore_defaults: bool = True,
273
- password: Optional[str] = None,
274
- email_verified: bool = False,
275
- disabled: bool = False
276
- ) -> Tuple[Optional[UserProfile], Optional[UserStatus], Optional[str]]:
272
+ user_uids: List[str],
273
+ updater_uid: str,
274
+ delete_auth_user: bool,
275
+ delete_profile: bool = True,
276
+ delete_status: bool = True,
277
+ archive: bool = True
278
+ ) -> Dict[str, Dict[str, Any]]:
277
279
  """
278
- Create a complete user with Firebase Auth, UserProfile, and UserStatus
279
-
280
- This method creates the complete user including Firebase Auth user with custom claims.
281
- It delegates to UserHolisticOperations for coordinated user creation.
280
+ Batch delete multiple users holistically.
282
281
 
283
- Returns:
284
- Tuple of (UserProfile, UserStatus, error_message)
285
- If successful, error_message is None
286
- If failed, one or both models may be None with error_message describing the failure
282
+ Delegates to UsermultistepOperations for the actual deletion.
287
283
  """
288
- self.logger.info(f"Starting complete user creation for Email: {email}, Type: {primary_usertype_name}")
289
- effective_creator = created_by or f"UserCoreService.create_user:email_{email}"
290
-
291
- # Fetch user type defaults if enabled
292
- usertype_defaults: Dict[str, Any] = {}
293
- if use_firestore_defaults:
294
- fetched_defaults = await self.fetch_user_defaults(primary_usertype_name)
295
- if fetched_defaults:
296
- usertype_defaults = fetched_defaults
297
- else:
298
- #### TODO : FIX HOW ERRORS ARE ACTUALLY REPORTED. SERVICEMON ?
299
- self.logger.error(f"User defaults not found for '{primary_usertype_name}', proceeding with empty defaults")
300
- raise UserCreationError(f"User defaults not found for '{primary_usertype_name}'")
301
-
302
- # Prepare data with defaults
303
- final_secondary_usertypes = secondary_usertype_names or usertype_defaults.get('default_secondary_usertypes', [])
304
- final_organizations_uids = organizations_uids or set(usertype_defaults.get('default_organizations_uids', []))
305
-
306
- # Extract custom data fields
307
- iam_domain_permissions = (status_custom_data or {}).get('iam_domain_permissions')
308
- if iam_domain_permissions is None and use_firestore_defaults:
309
- iam_domain_permissions = usertype_defaults.get('default_iam_domain_permissions', {})
310
-
311
- effective_initial_plan_id = initial_subscription_plan_id
312
- if effective_initial_plan_id is None and use_firestore_defaults:
313
- effective_initial_plan_id = usertype_defaults.get('default_subscription_plan_id')
314
-
315
- # Credits from defaults or custom data
316
- default_sbscrptn_credits = usertype_defaults.get('default_subscription_based_insight_credits', 0) if use_firestore_defaults else 0
317
- default_extra_credits = usertype_defaults.get('default_extra_insight_credits', 0) if use_firestore_defaults else 0
318
- default_voting_credits = usertype_defaults.get('default_voting_credits', 0) if use_firestore_defaults else 0
319
-
320
- sbscrptn_based_insight_credits = (status_custom_data or {}).get('sbscrptn_based_insight_credits', default_sbscrptn_credits)
321
- extra_insight_credits = (status_custom_data or {}).get('extra_insight_credits', default_extra_credits)
322
- voting_credits = (status_custom_data or {}).get('voting_credits', default_voting_credits)
323
- metadata = (status_custom_data or {}).get('metadata', {})
324
-
325
- # Extract profile fields from profile_custom_data
326
- first_name = (profile_custom_data or {}).get('first_name')
327
- last_name = (profile_custom_data or {}).get('last_name')
328
- username = (profile_custom_data or {}).get('username')
329
- mobile = (profile_custom_data or {}).get('mobile')
330
-
331
- try:
332
- # Delegate to UserHolisticOperations for complete user creation
333
- created_user_uid, created_profile, created_status = await self.user_holistic_ops.create_user(
334
- email=email,
335
- primary_usertype=primary_usertype_name,
336
- password=password,
337
- organizations_uids=final_organizations_uids,
338
- secondary_usertypes=final_secondary_usertypes,
339
- first_name=first_name,
340
- last_name=last_name,
341
- username=username,
342
- mobile=mobile,
343
- provider_id=provider_id,
344
- email_verified=email_verified,
345
- disabled=disabled,
346
- initial_subscription_plan_id=effective_initial_plan_id,
347
- iam_domain_permissions=iam_domain_permissions,
348
- sbscrptn_based_insight_credits=sbscrptn_based_insight_credits,
349
- extra_insight_credits=extra_insight_credits,
350
- voting_credits=voting_credits,
351
- metadata=metadata,
352
- created_by=effective_creator,
353
- set_custom_claims=True # Always set custom claims for complete user creation
354
- )
355
-
356
- self.logger.info(f"Successfully created complete user {created_user_uid}")
357
- return created_profile, created_status, None
358
-
359
- except Exception as e:
360
- error_msg = f"Failed to create complete user for email {email}: {str(e)}"
361
- self.logger.error(error_msg, exc_info=True)
362
- return None, None, error_msg
363
-
364
-
365
- ##########################################################################################
366
- ################################ Subscription Operations #################################
367
-
368
- async def get_user_active_subscription(self, user_uid: str) -> Optional[Subscription]:
369
- """Get the user's currently active subscription"""
370
- return await self.subscription_ops.get_user_active_subscription(user_uid)
371
-
372
- async def change_user_subscription(self, user_uid: str, new_plan_id: str, source: Optional[str] = None) -> Optional[Subscription]:
373
- """Change a user's subscription to a new plan"""
374
- return await self.subscription_ops.change_user_subscription(user_uid, new_plan_id, source)
375
-
376
- async def cancel_user_subscription(self, user_uid: str, reason: Optional[str] = None, cancelled_by: Optional[str] = None) -> bool:
377
- """Cancel a user's active subscription"""
378
- return await self.subscription_ops.cancel_user_subscription(user_uid, reason, cancelled_by)
284
+ return await self.usermultistep_ops.batch_delete_users(
285
+ user_uids=user_uids,
286
+ delete_auth_user=delete_auth_user,
287
+ delete_profile=delete_profile,
288
+ delete_status=delete_status,
289
+ updater_uid=updater_uid,
290
+ archive=archive
291
+ )
379
292
 
293
+ ######################################################################
294
+ ######################### UserAuth Operations ########################
295
+ ######################################################################
380
296
 
381
- async def _apply_subscription_plan(self, user_uid: str, plan_id: str, source: str = "system_default_config") -> Subscription:
382
- """Apply subscription plan (backward compatibility)"""
383
- return await self.subscription_ops.apply_subscription_plan(user_uid, plan_id, source)
297
+ async def create_userauth(self, userauth: UserAuth) -> str:
298
+ """Create a Firebase Auth user"""
299
+ return await self.userauth_ops.create_userauth(user_auth=userauth)
384
300
 
385
- ##########################################################################################
386
- ################################ IAM Operations Operations #################################
301
+ async def get_userauth(self, user_uid: str, get_model: bool = False) -> Union[UserRecord, UserAuth, None]:
302
+ """Get Firebase Auth user details"""
303
+ return await self.userauth_ops.get_userauth(user_uid=user_uid, get_model=get_model)
387
304
 
305
+ async def get_userauth_by_email(self, email: str, get_model: bool = False) -> Union[UserRecord, UserAuth, None]:
306
+ """Get Firebase Auth user by email"""
307
+ return await self.userauth_ops.get_userauth_by_email(email=email, get_model=get_model)
388
308
 
389
- # Deletion Operations (delegated to UserHolisticOperations)
309
+ async def userauth_exists(self, user_uid: str) -> bool:
310
+ """Check if Firebase Auth user exists"""
311
+ return await self.userauth_ops.userauth_exists(user_uid=user_uid)
390
312
 
391
- async def delete_user_account_docs(
313
+ async def update_userauth(
392
314
  self,
393
315
  user_uid: str,
394
- deleted_by: str = "system_deletion"
395
- ) -> Tuple[bool, bool, Optional[str]]:
396
- """Delete user documents (profile and status)"""
397
- # Use the new user_holistic_ops for coordinated deletion
398
- result = await self.user_holistic_ops.delete_user(
316
+ email: Optional[str] = None,
317
+ display_name: Optional[str] = None,
318
+ password: Optional[str] = None,
319
+ email_verified: Optional[bool] = None,
320
+ disabled: Optional[bool] = None
321
+ ) -> UserRecord:
322
+ """Update Firebase Auth user"""
323
+ return await self.userauth_ops.update_userauth(
399
324
  user_uid=user_uid,
400
- delete_auth_user=False, # Only delete profile and status
401
- delete_profile=True,
402
- delete_status=True,
403
- deleted_by=deleted_by
404
- )
405
- return (
406
- result["profile_deleted_successfully"],
407
- result["status_deleted_successfully"],
408
- result["errors"][0] if result["errors"] else None
325
+ email=email,
326
+ display_name=display_name,
327
+ password=password,
328
+ email_verified=email_verified,
329
+ disabled=disabled
409
330
  )
410
331
 
411
- async def delete_user_holistically_with_auth(
332
+ async def delete_userauth(self, user_uid: str, archive: bool = True) -> bool:
333
+ """Delete Firebase Auth user"""
334
+ return await self.userauth_ops.delete_userauth(user_uid=user_uid, archive=archive)
335
+
336
+ async def set_userauth_custom_claims(self, user_uid: str, custom_claims: Dict[str, Any]) -> bool:
337
+ """Set custom claims for Firebase Auth user"""
338
+ return await self.userauth_ops.set_userauth_custom_claims(user_uid=user_uid, custom_claims=custom_claims)
339
+
340
+ async def enable_userauth(self, user_uid: str, notes: str = "") -> bool:
341
+ """Enable Firebase Auth user"""
342
+ return await self.userauth_ops.enable_userauth(user_uid=user_uid, user_notes=notes)
343
+
344
+ async def disable_userauth(self, user_uid: str, notes: str = "") -> bool:
345
+ """Disable Firebase Auth user"""
346
+ return await self.userauth_ops.disable_userauth(user_uid=user_uid, user_notes=notes)
347
+
348
+ async def revoke_refresh_tokens(self, user_uid: str) -> bool:
349
+ """Revoke all refresh tokens for a user"""
350
+ return await self.userauth_ops.revoke_refresh_tokens(user_uid=user_uid)
351
+
352
+ async def set_user_approval_status(self, user_uid: str, status: ApprovalStatus, notes: str = "") -> bool:
353
+ """Set user approval status in custom claims"""
354
+ return await self.userauth_ops.set_user_approval_status(user_uid=user_uid, approval_status=status, updated_by=notes)
355
+
356
+ # Token and Security Operations
357
+
358
+ async def create_custom_token(
412
359
  self,
413
360
  user_uid: str,
414
- delete_auth_user: bool = True,
415
- deleted_by: str = "system_full_deletion"
361
+ additional_claims: Optional[Dict[str, Any]] = None
362
+ ) -> str:
363
+ """Creates a custom token for a user with optional additional claims"""
364
+ return await self.userauth_ops.create_custom_token(
365
+ user_uid=user_uid,
366
+ additional_claims=additional_claims
367
+ )
368
+
369
+ async def verify_id_token(
370
+ self,
371
+ token: str,
372
+ check_revoked: bool = False
416
373
  ) -> Dict[str, Any]:
417
- """Complete user deletion including Firebase Auth deletion"""
418
- return await self.user_holistic_ops.delete_user(user_uid, delete_auth_user, deleted_by=deleted_by)
374
+ """Verifies an ID token and returns the token claims"""
375
+ return await self.userauth_ops.verify_id_token(
376
+ token=token,
377
+ check_revoked=check_revoked
378
+ )
419
379
 
420
- async def batch_delete_user_account_docs(
380
+ async def get_user_auth_token(
421
381
  self,
422
- user_uids: List[str],
423
- deleted_by: str = "system_batch_deletion"
424
- ) -> Dict[str, Tuple[bool, bool, Optional[str]]]:
425
- """Batch delete multiple users' documents"""
426
- results_holistic = await self.user_holistic_ops.batch_delete_users(user_uids, delete_auth_user=False, deleted_by=deleted_by)
427
-
428
- # Convert to expected format
429
- converted_results = {}
430
- for user_uid, result in results_holistic.items():
431
- error_msg = result["errors"][0] if result["errors"] else None
432
- converted_results[user_uid] = (
433
- result["profile_deleted_successfully"],
434
- result["status_deleted_successfully"],
435
- error_msg
436
- )
437
-
438
- return converted_results
439
-
440
- # Utility Methods
441
-
442
- async def get_user_account_docs(self, user_uid: str) -> Tuple[Optional[UserProfile], Optional[UserStatus]]:
443
- """Get both user profile and status"""
444
- return await self.user_account_ops.get_user_core_docs(user_uid)
445
-
446
- async def user_core_docs_exist(self, user_uid: str) -> Tuple[bool, bool]:
447
- """Check if user profile and/or status exist"""
448
- return await self.user_account_ops.user_core_docs_exist(user_uid)
449
-
450
- async def validate_user_account_data(
382
+ email: str,
383
+ password: str,
384
+ api_key: str
385
+ ) -> Optional[str]:
386
+ """
387
+ Gets a user authentication token using the Firebase REST API.
388
+
389
+ Note: This method requires the Firebase Web API key and should be used
390
+ for testing or specific admin scenarios. For production authentication,
391
+ prefer using the Firebase client SDKs.
392
+ """
393
+ return await self.userauth_ops.get_user_auth_token(
394
+ email=email,
395
+ password=password,
396
+ api_key=api_key
397
+ )
398
+
399
+ async def generate_password_reset_link(
451
400
  self,
452
- profile_data: Optional[Dict[str, Any]] = None,
453
- status_data: Optional[Dict[str, Any]] = None
454
- ) -> Tuple[bool, List[str]]:
455
- """Validate user profile and status data without creating documents"""
456
- return await self.user_account_ops.validate_user_core_data(profile_data, status_data)
401
+ email: str,
402
+ action_code_settings: Optional[Dict[str, Any]] = None
403
+ ) -> str:
404
+ """Generates a password reset link for a user"""
405
+ return await self.userauth_ops.generate_password_reset_link(
406
+ email=email,
407
+ action_code_settings=action_code_settings
408
+ )
457
409
 
458
- # Enhanced Methods for Advanced Operations
410
+ async def generate_email_verification_link(
411
+ self,
412
+ email: str,
413
+ action_code_settings: Optional[Dict[str, Any]] = None
414
+ ) -> str:
415
+ """Generates an email verification link for a user"""
416
+ return await self.userauth_ops.generate_email_verification_link(
417
+ email=email,
418
+ action_code_settings=action_code_settings
419
+ )
420
+
421
+ ######################################################################
422
+ ######################### UserProfile Operations ####################
423
+ ######################################################################
424
+
425
+ async def create_userprofile(self, userprofile: UserProfile, creator_uid: Optional[str] = None) -> UserProfile:
426
+ """Create a UserProfile document"""
427
+ return await self.userprofile_ops.create_userprofile(userprofile=userprofile, creator_uid=creator_uid)
428
+
429
+ async def get_userprofile(self, user_uid: str) -> Optional[UserProfile]:
430
+ """Get a UserProfile by user UID"""
431
+ return await self.userprofile_ops.get_userprofile(user_uid=user_uid)
432
+
433
+ async def update_userprofile(self, user_uid: str, updates: Dict[str, Any], updater_uid: str) -> UserProfile:
434
+ """Update a UserProfile"""
435
+ return await self.userprofile_ops.update_userprofile(user_uid=user_uid, profile_data=updates, updater_uid=updater_uid)
436
+
437
+ async def delete_userprofile(self, user_uid: str, updater_uid: str, archive: bool = True) -> bool:
438
+ """Delete a UserProfile"""
439
+ return await self.userprofile_ops.delete_userprofile(user_uid=user_uid, updater_uid=updater_uid, archive=archive)
440
+
441
+ async def userprofile_exists(self, user_uid: str) -> bool:
442
+ """Check if a UserProfile exists"""
443
+ return await self.userprofile_ops.userprofile_exists(user_uid=user_uid)
459
444
 
460
- async def get_users_by_usertype(
445
+ async def validate_userprofile_data(
461
446
  self,
462
- primary_usertype: str,
463
- limit: Optional[int] = None
464
- ) -> List[UserProfile]:
465
- """Get users by primary usertype (requires custom implementation)"""
466
- # This would require a more advanced query system
467
- # For now, we'll raise NotImplementedError to indicate this needs custom implementation
468
- raise NotImplementedError("get_users_by_usertype requires custom query implementation")
469
-
470
- async def get_users_by_organization(
447
+ profile_data: Optional[Dict[str, Any]] = None
448
+ ) -> tuple[bool, list[str]]:
449
+ """Validate UserProfile data"""
450
+ return await self.userprofile_ops.validate_userprofile_data(profile_data=profile_data)
451
+
452
+ ######################################################################
453
+ ######################### UserStatus Operations ######################
454
+ ######################################################################
455
+
456
+ async def create_userstatus(self, userstatus: UserStatus, creator_uid: Optional[str] = None) -> UserStatus:
457
+ """Create a UserStatus document"""
458
+ return await self.userstatus_ops.create_userstatus(userstatus=userstatus, creator_uid=creator_uid)
459
+
460
+ async def get_userstatus(self, user_uid: str) -> Optional[UserStatus]:
461
+ """Get a UserStatus by user UID"""
462
+ return await self.userstatus_ops.get_userstatus(user_uid=user_uid)
463
+
464
+ async def update_userstatus(self, user_uid: str, updates: Dict[str, Any], updater_uid: str) -> UserStatus:
465
+ """Update a UserStatus"""
466
+ return await self.userstatus_ops.update_userstatus(user_uid=user_uid, status_data=updates, updater_uid=updater_uid)
467
+
468
+ async def delete_userstatus(self, user_uid: str, updater_uid: str, archive: bool = True) -> bool:
469
+ """Delete a UserStatus"""
470
+ return await self.userstatus_ops.delete_userstatus(user_uid=user_uid, updater_uid=updater_uid, archive=archive)
471
+
472
+ async def userstatus_exists(self, user_uid: str) -> bool:
473
+ """Check if a UserStatus exists"""
474
+ return await self.userstatus_ops.userstatus_exists(user_uid=user_uid)
475
+
476
+ async def validate_userstatus_data(
471
477
  self,
472
- organization_uid: str,
473
- limit: Optional[int] = None
474
- ) -> List[UserProfile]:
475
- """Get users by organization (requires custom implementation)"""
476
- # This would require a more advanced query system
477
- raise NotImplementedError("get_users_by_organization requires custom query implementation")
478
-
479
- async def bulk_update_user_permissions(
478
+ status_data: Optional[Dict[str, Any]] = None
479
+ ) -> tuple[bool, list[str]]:
480
+ """Validate UserStatus data"""
481
+ return await self.userstatus_ops.validate_userstatus_data(status_data=status_data)
482
+
483
+ async def validate_and_cleanup_user_permissions(
480
484
  self,
481
- user_uids: List[str],
482
- domain: str,
483
- permissions_to_add: List[str],
484
- permissions_to_remove: List[str],
485
+ user_uid: str,
485
486
  updater_uid: str
486
- ) -> Dict[str, bool]:
487
- """Bulk update permissions for multiple users"""
488
- results = {}
489
-
490
- for user_uid in user_uids:
491
- try:
492
- # Add permissions
493
- for permission in permissions_to_add:
494
- await self.iam_ops.add_user_permission(
495
- user_uid=user_uid,
496
- domain=domain,
497
- permission_name=permission,
498
- iam_unit_type=IAMUnitType.GROUPS, # Default to groups
499
- source=f"bulk_update_by_{updater_uid}",
500
- updater_uid=updater_uid
501
- )
502
-
503
- # Remove permissions
504
- for permission in permissions_to_remove:
505
- await self.iam_ops.remove_user_permission(
506
- user_uid=user_uid,
507
- domain=domain,
508
- permission_name=permission,
509
- iam_unit_type=IAMUnitType.GROUPS, # Default to groups
510
- updater_uid=updater_uid
511
- )
512
-
513
- results[user_uid] = True
514
-
515
- except Exception as e:
516
- self.logger.error(f"Failed to update permissions for user {user_uid}: {e}")
517
- results[user_uid] = False
518
-
519
- return results
520
-
521
- # Statistics and Analytics Methods
522
-
523
- async def get_user_statistics(self) -> Dict[str, int]:
524
- """Get basic user statistics (requires custom implementation)"""
525
- # This would require aggregation queries
526
- raise NotImplementedError("get_user_statistics requires custom aggregation implementation")
527
-
528
- async def get_subscription_statistics(self) -> Dict[str, int]:
529
- """Get subscription statistics (requires custom implementation)"""
530
- # This would require aggregation queries
531
- raise NotImplementedError("get_subscription_statistics requires custom aggregation implementation")
532
-
533
- # User 360 Operations (Complete User Lifecycle)
534
-
535
- async def create_complete_user_with_auth(
536
- self,
537
- email: str,
538
- primary_usertype: str,
539
- password: Optional[str] = None,
540
- organizations_uids: Optional[Set[str]] = None,
541
- secondary_usertypes: Optional[List[str]] = None,
542
- first_name: Optional[str] = None,
543
- last_name: Optional[str] = None,
544
- username: Optional[str] = None,
545
- mobile: Optional[str] = None,
546
- provider_id: str = "password",
547
- email_verified: bool = False,
548
- disabled: bool = False,
549
- initial_subscription_plan_id: Optional[str] = None,
550
- iam_domain_permissions: Optional[Dict[str, Dict[str, Dict[str, IAMUnitRefAssignment]]]] = None,
551
- sbscrptn_based_insight_credits: int = 0,
552
- extra_insight_credits: int = 0,
553
- voting_credits: int = 0,
554
- metadata: Optional[Dict[str, Any]] = None,
555
- created_by: Optional[str] = None,
556
- set_custom_claims: bool = True
557
- ) -> Tuple[str, UserProfile, UserStatus]:
558
- """
559
- Create complete user with Firebase Auth, UserProfile, and UserStatus
487
+ ) -> int:
488
+ """Validate and cleanup expired permissions for a user"""
489
+ return await self.userstatus_ops.validate_and_cleanup_user_permissions(user_uid=user_uid, updater_uid=updater_uid)
560
490
 
561
- This method directly delegates to UserHolisticOperations.create_user
562
- """
563
- return await self.user_holistic_ops.create_user(
564
- email=email,
565
- primary_usertype=primary_usertype,
566
- password=password,
567
- organizations_uids=organizations_uids,
568
- secondary_usertypes=secondary_usertypes,
569
- first_name=first_name,
570
- last_name=last_name,
571
- username=username,
572
- mobile=mobile,
573
- provider_id=provider_id,
574
- email_verified=email_verified,
575
- disabled=disabled,
576
- initial_subscription_plan_id=initial_subscription_plan_id,
577
- iam_domain_permissions=iam_domain_permissions,
578
- sbscrptn_based_insight_credits=sbscrptn_based_insight_credits,
579
- extra_insight_credits=extra_insight_credits,
580
- voting_credits=voting_credits,
581
- metadata=metadata,
582
- created_by=created_by,
583
- set_custom_claims=set_custom_claims
491
+ ######################################################################
492
+ ######################### Subscription Operations ###################
493
+ ######################################################################
494
+
495
+ async def fetch_subscriptionplan_and_apply_subscription_to_user(
496
+ self,
497
+ user_uid: str,
498
+ plan_id: str,
499
+ updater_uid: str,
500
+ source: str = "user_core_service",
501
+ granted_at: Optional[datetime] = None,
502
+ auto_renewal_end: Optional[datetime] = None
503
+ ) -> UserSubscription:
504
+ """Fetch a subscription plan from catalog and apply to user"""
505
+ return await self.subscription_ops.fetch_subscriptionplan_and_apply_subscription_to_user(
506
+ user_uid=user_uid,
507
+ plan_id=plan_id,
508
+ updater_uid=updater_uid,
509
+ source=source,
510
+ granted_at=granted_at,
511
+ auto_renewal_end=auto_renewal_end
584
512
  )
585
513
 
586
- async def delete_complete_user(
514
+ async def apply_subscriptionplan_to_user(
587
515
  self,
588
516
  user_uid: str,
589
- delete_auth_user: bool = True,
590
- delete_profile: bool = True,
591
- delete_status: bool = True,
592
- deleted_by: str = "system_complete_deletion"
593
- ) -> Dict[str, Any]:
594
- """Delete complete user including Firebase Auth, UserProfile, and UserStatus"""
595
- return await self.user_holistic_ops.delete_user(
517
+ subscriptionplan: SubscriptionPlan,
518
+ updater_uid: str,
519
+ source: str = "user_core_service",
520
+ granted_at: Optional[datetime] = None,
521
+ auto_renewal_end: Optional[datetime] = None
522
+ ) -> UserSubscription:
523
+ """Apply a subscription plan directly to user (plan already fetched)"""
524
+ return await self.subscription_ops.apply_subscriptionplan(
596
525
  user_uid=user_uid,
597
- delete_auth_user=delete_auth_user,
598
- delete_profile=delete_profile,
599
- delete_status=delete_status,
600
- deleted_by=deleted_by
526
+ subscriptionplan=subscriptionplan,
527
+ updater_uid=updater_uid,
528
+ source=source,
529
+ granted_at=granted_at,
530
+ auto_renewal_end=auto_renewal_end
601
531
  )
602
532
 
603
- async def user_exists_complete(self, user_uid: str) -> Dict[str, bool]:
604
- """Check if complete user exists (Auth, Profile, Status)"""
605
- return await self.user_holistic_ops.user_exists_fully(user_uid)
533
+ async def cancel_user_subscription(self, user_uid: str, updater_uid: str) -> bool:
534
+ """Cancel a user's active subscription"""
535
+ return await self.subscription_ops.cancel_user_subscription(user_uid=user_uid, updater_uid=updater_uid)
606
536
 
607
- async def validate_complete_user(self, user_uid: str) -> Dict[str, Any]:
608
- """Validate complete user integrity"""
609
- return await self.user_holistic_ops.validate_user_full_existance(user_uid)
537
+ async def get_user_active_subscription(self, user_uid: str) -> Optional[UserSubscription]:
538
+ """Get a user's active subscription"""
539
+ return await self.subscription_ops.get_user_active_subscription(user_uid=user_uid)
610
540
 
611
- # User Auth Operations
541
+ async def update_user_subscription(
542
+ self,
543
+ user_uid: str,
544
+ subscription_data: Dict[str, Any],
545
+ updater_uid: str
546
+ ) -> Optional[UserSubscription]:
547
+ """Update a user's subscription"""
548
+ return await self.subscription_ops.update_user_subscription(user_uid=user_uid, subscription_updates=subscription_data, updater_uid=updater_uid)
612
549
 
613
- async def create_userauth(
550
+ async def downgrade_user_subscription_to_fallback_subscriptionplan(
614
551
  self,
615
- email: str,
616
- password: Optional[str] = None,
617
- email_verified: bool = False,
618
- disabled: bool = False,
619
- display_name: Optional[str] = None,
620
- phone_number: Optional[str] = None,
621
- custom_claims: Optional[Dict[str, Any]] = None
622
- ) -> str:
623
- """Create Firebase Auth user"""
624
- user_auth = UserAuth(
625
- email=email,
626
- password=password,
627
- email_verified=email_verified,
628
- disabled=disabled,
629
- phone_number=phone_number,
630
- custom_claims=custom_claims
552
+ user_uid: str,
553
+ reason: str = "subscription_expired"
554
+ ) -> Optional[UserSubscription]:
555
+ """Downgrade user subscription to fallback plan"""
556
+ return await self.subscription_ops.downgrade_user_subscription_to_fallback_subscriptionplan(
557
+ user_uid=user_uid, reason=reason
631
558
  )
632
- return await self.user_auth_ops.create_userauth(user_auth)
633
559
 
634
- async def delete_userauth(self, user_uid: str) -> bool:
635
- """Delete Firebase Auth user"""
636
- return await self.user_auth_ops.delete_userauth(user_uid)
560
+ ######################################################################
561
+ ######################### IAM/Permissions Operations ################
562
+ ######################################################################
637
563
 
638
- async def userauth_exists(self, user_uid: str) -> bool:
639
- """Check if Firebase Auth user exists"""
640
- return await self.user_auth_ops.userauth_exists(user_uid)
564
+ async def add_permission_to_user(
565
+ self,
566
+ user_uid: str,
567
+ permission: UserPermission,
568
+ updater_uid: str
569
+ ) -> bool:
570
+ """Add a permission to a user (returns success boolean)"""
571
+ return await self.iam_ops.add_permission_to_user(user_uid=user_uid, permission=permission, updater_uid=updater_uid)
572
+
573
+ async def get_permissions_of_user(self, user_uid: str) -> List[UserPermission]:
574
+ """Get a user's permissions"""
575
+ return await self.iam_ops.get_permissions_of_user(user_uid=user_uid)
641
576
 
642
- # Custom Claims Operations (delegated to user_auth_ops)
577
+ async def remove_all_permissions_from_user(self, user_uid: str, updater_uid: str, source: Optional[str] = None) -> int:
578
+ """Remove all permissions from a user"""
579
+ return await self.iam_ops.remove_all_permissions_from_user(user_uid=user_uid, source=source, updater_uid=updater_uid)
643
580
 
644
- async def set_userauth_custom_claims(
581
+ async def remove_permission_from_user(
645
582
  self,
646
583
  user_uid: str,
647
- custom_claims: Dict[str, Any],
648
- merge_with_existing: bool = False
584
+ domain: Optional[str] = None,
585
+ permission_type: Optional[IAMUnit] = None,
586
+ permission_name: Optional[str] = None,
587
+ source: Optional[str] = None,
588
+ updater_uid: Optional[str] = None
649
589
  ) -> bool:
650
- """Set Firebase Auth custom claims for a user with optional merging"""
651
- return await self.user_auth_ops.set_userauth_custom_claims(user_uid, custom_claims, merge_with_existing)
590
+ """Remove specific permission(s) from a user based on filter criteria"""
591
+ return await self.iam_ops.remove_permission_from_user(
592
+ user_uid=user_uid,
593
+ domain=domain,
594
+ permission_type=permission_type,
595
+ permission_name=permission_name,
596
+ source=source,
597
+ updater_uid=updater_uid
598
+ )
599
+
600
+ async def cleanup_expired_permissions_of_user(
601
+ self,
602
+ user_uid: str,
603
+ updater_uid: str,
604
+ iam_unit_type: Optional[IAMUnit] = None
605
+ ) -> int:
606
+ """Remove expired permissions from a user"""
607
+ return await self.iam_ops.cleanup_expired_permissions_of_user(user_uid=user_uid, iam_unit_type=iam_unit_type, updater_uid=updater_uid)
608
+
609
+ async def get_bulk_users_with_permission(
610
+ self,
611
+ domain: str,
612
+ iam_unit_type: IAMUnit,
613
+ permission_ref: str,
614
+ limit: int = 100,
615
+ valid_only: bool = True
616
+ ) -> List[str]:
617
+ """Get bulk users who have a specific permission"""
618
+ return await self.iam_ops.get_bulk_users_with_permission(
619
+ domain=domain,
620
+ iam_unit_type=iam_unit_type,
621
+ permission_ref=permission_ref,
622
+ limit=limit,
623
+ valid_only=valid_only
624
+ )
625
+
626
+ async def update_user_usertype(
627
+ self,
628
+ user_uid: str,
629
+ primary_usertype: Optional['IAMUserType'] = None,
630
+ secondary_usertypes: Optional[List['IAMUserType']] = None,
631
+ updater_uid: str = "system_usertype_update"
632
+ ) -> Tuple[UserProfile, Dict[str, Any]]:
633
+ """
634
+ Update user's primary and/or secondary usertypes efficiently across UserProfile and Firebase Auth.
635
+
636
+ Args:
637
+ user_uid: The UID of the user to update
638
+ primary_usertype: New primary usertype (optional, keeps existing if None)
639
+ secondary_usertypes: New secondary usertypes list (optional, keeps existing if None)
640
+ updater_uid: Who is performing this update
641
+
642
+ Returns:
643
+ Tuple of (updated_userprofile, updated_custom_claims)
644
+ """
645
+ return await self.usermultistep_ops.update_user_usertype(
646
+ user_uid=user_uid,
647
+ primary_usertype=primary_usertype,
648
+ secondary_usertypes=secondary_usertypes,
649
+ updater_uid=updater_uid
650
+ )
651
+
652
+ ######################################################################
653
+ ####################### User Subscription/Status Review #############
654
+ ######################################################################
655
+
656
+ async def review_and_clean_active_subscription_credits_and_permissions(
657
+ self,
658
+ user_uid: str,
659
+ updater_uid: str = "system_review",
660
+ review_auto_renewal: bool = True,
661
+ apply_fallback: bool = True,
662
+ clean_expired_permissions: bool = True,
663
+ review_credits: bool = True
664
+ ) -> Dict[str, Any]:
665
+ """
666
+ Orchestrate a comprehensive review and cleanup of a user's active subscription, credits, and permissions.
667
+ Delegates to UserstatusOperations for the actual logic.
668
+ """
669
+ return await self.userstatus_ops.review_and_clean_active_subscription_credits_and_permissions(
670
+ user_uid=user_uid,
671
+ updater_uid=updater_uid,
672
+ review_auto_renewal=review_auto_renewal,
673
+ apply_fallback=apply_fallback,
674
+ clean_expired_permissions=clean_expired_permissions,
675
+ review_credits=review_credits
676
+ )
677
+