ipulse-shared-core-ftredge 22.1.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.
- ipulse_shared_core_ftredge/dependencies/auth_firebase_token_validation.py +60 -23
- ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +128 -157
- ipulse_shared_core_ftredge/exceptions/base_exceptions.py +12 -4
- ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py +4 -3
- ipulse_shared_core_ftredge/models/catalog/usertype.py +8 -1
- ipulse_shared_core_ftredge/models/user/user_subscription.py +142 -30
- ipulse_shared_core_ftredge/models/user/userstatus.py +63 -14
- ipulse_shared_core_ftredge/services/base/base_firestore_service.py +5 -3
- ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py +27 -23
- ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py +94 -25
- ipulse_shared_core_ftredge/services/user/user_core_service.py +141 -23
- ipulse_shared_core_ftredge/services/user/user_multistep_operations.py +144 -74
- ipulse_shared_core_ftredge/services/user/user_subscription_operations.py +24 -20
- ipulse_shared_core_ftredge/services/user/userstatus_operations.py +268 -4
- {ipulse_shared_core_ftredge-22.1.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/METADATA +1 -1
- {ipulse_shared_core_ftredge-22.1.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/RECORD +19 -20
- ipulse_shared_core_ftredge/services/user/firebase_auth_admin_helpers.py +0 -160
- {ipulse_shared_core_ftredge-22.1.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/WHEEL +0 -0
- {ipulse_shared_core_ftredge-22.1.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/licenses/LICENCE +0 -0
- {ipulse_shared_core_ftredge-22.1.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/top_level.txt +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Subscription Management Operations - Handle user subscriptions and related operations
|
|
3
3
|
"""
|
|
4
4
|
import logging
|
|
5
|
-
from typing import Optional
|
|
5
|
+
from typing import Optional, Dict, Any
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from google.cloud import firestore
|
|
8
8
|
from google.cloud.exceptions import GoogleCloudError
|
|
@@ -32,12 +32,12 @@ class UsersubscriptionOperations:
|
|
|
32
32
|
self.logger = logger or logging.getLogger(__name__)
|
|
33
33
|
self.timeout = timeout
|
|
34
34
|
|
|
35
|
-
def
|
|
35
|
+
def create_subscription_from_subscriptionplan(
|
|
36
36
|
self,
|
|
37
37
|
plan: SubscriptionPlan,
|
|
38
38
|
source: str,
|
|
39
39
|
granted_at: Optional[datetime] = None,
|
|
40
|
-
|
|
40
|
+
auto_renewal_end: Optional[datetime] = None
|
|
41
41
|
) -> UserSubscription:
|
|
42
42
|
"""
|
|
43
43
|
Common helper function to create a UserSubscription from a SubscriptionPlan.
|
|
@@ -68,10 +68,8 @@ class UsersubscriptionOperations:
|
|
|
68
68
|
plan.plan_validity_cycle_unit
|
|
69
69
|
)
|
|
70
70
|
|
|
71
|
-
# Use provided
|
|
72
|
-
|
|
73
|
-
plan.plan_default_auto_renewal if plan.plan_default_auto_renewal is not None else False
|
|
74
|
-
)
|
|
71
|
+
# Use provided auto_renewal_end or default from plan
|
|
72
|
+
effective_auto_renewal_end = auto_renewal_end if auto_renewal_end is not None else plan.plan_default_auto_renewal_end
|
|
75
73
|
|
|
76
74
|
try:
|
|
77
75
|
# Validate plan name
|
|
@@ -89,10 +87,10 @@ class UsersubscriptionOperations:
|
|
|
89
87
|
plan_version=plan.plan_version,
|
|
90
88
|
plan_id=plan.id or f"{plan.plan_name}_{plan.plan_version}",
|
|
91
89
|
cycle_start_date=start_date,
|
|
92
|
-
|
|
90
|
+
cycle_end_datetime=end_date,
|
|
93
91
|
validity_time_length=plan.plan_validity_cycle_length,
|
|
94
92
|
validity_time_unit=plan.plan_validity_cycle_unit,
|
|
95
|
-
|
|
93
|
+
auto_renew_end_datetime=effective_auto_renewal_end,
|
|
96
94
|
status=SubscriptionStatus.ACTIVE,
|
|
97
95
|
granted_iam_permissions=plan.granted_iam_permissions or [],
|
|
98
96
|
fallback_plan_id=plan.fallback_plan_id_if_current_plan_expired,
|
|
@@ -113,7 +111,7 @@ class UsersubscriptionOperations:
|
|
|
113
111
|
updater_uid: str,
|
|
114
112
|
source: str = "system_default_config",
|
|
115
113
|
granted_at: Optional[datetime] = None,
|
|
116
|
-
|
|
114
|
+
auto_renewal_end: Optional[datetime] = None
|
|
117
115
|
) -> UserSubscription:
|
|
118
116
|
"""
|
|
119
117
|
Fetch a subscription plan from catalog service and apply to user.
|
|
@@ -124,7 +122,7 @@ class UsersubscriptionOperations:
|
|
|
124
122
|
updater_uid: Who is applying the subscription
|
|
125
123
|
source: Source identifier
|
|
126
124
|
granted_at: Optional granted timestamp (overrides plan defaults)
|
|
127
|
-
|
|
125
|
+
auto_renewal_end: Optional auto-renewal end date (overrides plan defaults)
|
|
128
126
|
|
|
129
127
|
Returns:
|
|
130
128
|
Applied UserSubscription
|
|
@@ -158,7 +156,7 @@ class UsersubscriptionOperations:
|
|
|
158
156
|
updater_uid=updater_uid,
|
|
159
157
|
source=source,
|
|
160
158
|
granted_at=granted_at,
|
|
161
|
-
|
|
159
|
+
auto_renewal_end=auto_renewal_end,
|
|
162
160
|
add_associated_permissions=True
|
|
163
161
|
)
|
|
164
162
|
|
|
@@ -182,7 +180,7 @@ class UsersubscriptionOperations:
|
|
|
182
180
|
updater_uid: str,
|
|
183
181
|
source: str = "system_default_config",
|
|
184
182
|
granted_at: Optional[datetime] = None,
|
|
185
|
-
|
|
183
|
+
auto_renewal_end: Optional[datetime] = None,
|
|
186
184
|
add_associated_permissions: bool = True
|
|
187
185
|
) -> UserSubscription:
|
|
188
186
|
"""
|
|
@@ -213,18 +211,18 @@ class UsersubscriptionOperations:
|
|
|
213
211
|
|
|
214
212
|
try:
|
|
215
213
|
# Create subscription from plan using helper
|
|
216
|
-
subscription = self.
|
|
214
|
+
subscription = self.create_subscription_from_subscriptionplan(
|
|
217
215
|
plan=subscriptionplan,
|
|
218
216
|
source=f"{source}:{updater_uid}",
|
|
219
217
|
granted_at=granted_at,
|
|
220
|
-
|
|
218
|
+
auto_renewal_end=auto_renewal_end
|
|
221
219
|
)
|
|
222
220
|
|
|
223
221
|
# Apply subscription to user (this will handle removing existing permissions and adding new ones)
|
|
224
222
|
permissions_added = userstatus.apply_subscription(
|
|
225
223
|
subscription,
|
|
226
224
|
add_associated_permissions=add_associated_permissions,
|
|
227
|
-
|
|
225
|
+
remove_previous_subscription_permissions=True,
|
|
228
226
|
granted_by=f"UsersubscriptionOperations.apply:{source}:{updater_uid}"
|
|
229
227
|
)
|
|
230
228
|
|
|
@@ -442,18 +440,18 @@ class UsersubscriptionOperations:
|
|
|
442
440
|
current_plan_id = userstatus.active_subscription.plan_id
|
|
443
441
|
|
|
444
442
|
# Create new subscription from fallback plan with updated granted_at
|
|
445
|
-
new_subscription = self.
|
|
443
|
+
new_subscription = self.create_subscription_from_subscriptionplan(
|
|
446
444
|
plan=fallback_plan,
|
|
447
445
|
source=f"downgrade_from_{current_plan_id}:{reason}",
|
|
448
446
|
granted_at=datetime.now(timezone.utc), # Set to now for downgrade
|
|
449
|
-
|
|
447
|
+
auto_renewal_end=fallback_plan.plan_default_auto_renewal_end
|
|
450
448
|
)
|
|
451
449
|
|
|
452
450
|
# Apply the new subscription to user (this will handle revoking existing permissions and adding new ones)
|
|
453
451
|
permissions_added = userstatus.apply_subscription(
|
|
454
452
|
new_subscription,
|
|
455
453
|
add_associated_permissions=True,
|
|
456
|
-
|
|
454
|
+
remove_previous_subscription_permissions=True,
|
|
457
455
|
granted_by=f"UsersubscriptionOperations:downgrade:{reason}"
|
|
458
456
|
)
|
|
459
457
|
userstatus.updated_at = datetime.now(timezone.utc)
|
|
@@ -481,4 +479,10 @@ class UsersubscriptionOperations:
|
|
|
481
479
|
plan_id=fallback_plan_id,
|
|
482
480
|
operation="downgrade_to_fallback_plan",
|
|
483
481
|
original_error=e
|
|
484
|
-
) from e
|
|
482
|
+
) from e
|
|
483
|
+
|
|
484
|
+
######################################################################
|
|
485
|
+
######################### Subscription Lifecycle Support Methods #####
|
|
486
|
+
######################################################################
|
|
487
|
+
# Note: The main review method has been moved to UserStatusOperations
|
|
488
|
+
# as it manages overall user status, not just subscriptions
|
|
@@ -3,7 +3,7 @@ Userstatus Operations - CRUD operations for Userstatus
|
|
|
3
3
|
"""
|
|
4
4
|
import os
|
|
5
5
|
import logging
|
|
6
|
-
from typing import Dict, Any, Optional
|
|
6
|
+
from typing import Dict, Any, Optional, TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from google.cloud import firestore
|
|
9
9
|
from pydantic import ValidationError as PydanticValidationError
|
|
@@ -12,6 +12,11 @@ from ...models import UserStatus
|
|
|
12
12
|
from ...exceptions import ResourceNotFoundError, UserStatusError
|
|
13
13
|
from ..base import BaseFirestoreService
|
|
14
14
|
|
|
15
|
+
# Type checking imports to avoid circular dependencies
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .user_subscription_operations import UsersubscriptionOperations
|
|
18
|
+
from .user_permissions_operations import UserpermissionsOperations
|
|
19
|
+
|
|
15
20
|
|
|
16
21
|
class UserstatusOperations:
|
|
17
22
|
"""
|
|
@@ -23,12 +28,18 @@ class UserstatusOperations:
|
|
|
23
28
|
firestore_client: firestore.Client,
|
|
24
29
|
logger: Optional[logging.Logger] = None,
|
|
25
30
|
timeout: float = 10.0,
|
|
26
|
-
status_collection: Optional[str] = None
|
|
31
|
+
status_collection: Optional[str] = None,
|
|
32
|
+
subscription_ops: Optional["UsersubscriptionOperations"] = None,
|
|
33
|
+
permissions_ops: Optional["UserpermissionsOperations"] = None
|
|
27
34
|
):
|
|
28
35
|
self.db = firestore_client
|
|
29
36
|
self.logger = logger or logging.getLogger(__name__)
|
|
30
37
|
self.timeout = timeout
|
|
31
38
|
|
|
39
|
+
# Optional dependencies for comprehensive operations
|
|
40
|
+
self.subscription_ops = subscription_ops
|
|
41
|
+
self.permissions_ops = permissions_ops
|
|
42
|
+
|
|
32
43
|
self.status_collection_name = status_collection or UserStatus.get_collection_name()
|
|
33
44
|
|
|
34
45
|
# Archival configuration
|
|
@@ -110,8 +121,8 @@ class UserstatusOperations:
|
|
|
110
121
|
|
|
111
122
|
try:
|
|
112
123
|
updated_doc_dict = await self._status_db_service.update_document(
|
|
113
|
-
userstatus_id,
|
|
114
|
-
update_data,
|
|
124
|
+
doc_id=userstatus_id,
|
|
125
|
+
update_data=update_data,
|
|
115
126
|
updater_uid=updater_uid
|
|
116
127
|
)
|
|
117
128
|
self.logger.info("Userstatus for %s updated successfully by %s", user_uid, updater_uid)
|
|
@@ -210,3 +221,256 @@ class UserstatusOperations:
|
|
|
210
221
|
async def userstatus_exists(self, user_uid: str) -> bool:
|
|
211
222
|
"""Check if a user status exists."""
|
|
212
223
|
return await self._status_db_service.document_exists(f"{UserStatus.OBJ_REF}_{user_uid}")
|
|
224
|
+
|
|
225
|
+
######################################################################
|
|
226
|
+
######################### Comprehensive Review Methods #############
|
|
227
|
+
######################################################################
|
|
228
|
+
|
|
229
|
+
async def review_and_clean_active_subscription_credits_and_permissions(
|
|
230
|
+
self,
|
|
231
|
+
user_uid: str,
|
|
232
|
+
updater_uid: str = "system_review",
|
|
233
|
+
review_auto_renewal: bool = True,
|
|
234
|
+
apply_fallback: bool = True,
|
|
235
|
+
clean_expired_permissions: bool = True,
|
|
236
|
+
review_credits: bool = True
|
|
237
|
+
) -> Dict[str, Any]:
|
|
238
|
+
"""
|
|
239
|
+
Comprehensive review of user's active subscription, credits, and permissions.
|
|
240
|
+
This method handles:
|
|
241
|
+
1. Subscription lifecycle (auto-renewal, fallback, expiration)
|
|
242
|
+
2. Credit management based on subscription cycle timing
|
|
243
|
+
3. Permission cleanup and management
|
|
244
|
+
|
|
245
|
+
This is designed to be called on every authz request for comprehensive user state management.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
user_uid: User UID to review
|
|
249
|
+
updater_uid: User ID performing the review
|
|
250
|
+
account_for_auto_renewal: Whether to auto-renew expired cycles if auto_renew_end_datetime is valid
|
|
251
|
+
apply_fallback: Whether to apply fallback plans when subscriptions expire
|
|
252
|
+
clean_expired_permissions: Whether to clean expired permissions
|
|
253
|
+
review_credits: Whether to review and update subscription-based credits
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Dict containing detailed results of the review and actions taken
|
|
257
|
+
"""
|
|
258
|
+
from datetime import datetime, timezone
|
|
259
|
+
from ipulse_shared_base_ftredge.enums import SubscriptionStatus
|
|
260
|
+
from ...models import UserSubscription
|
|
261
|
+
|
|
262
|
+
self.logger.info("Starting comprehensive subscription, credits, and permissions review for user %s", user_uid)
|
|
263
|
+
|
|
264
|
+
result = {
|
|
265
|
+
'user_uid': user_uid,
|
|
266
|
+
'timestamp': datetime.now(timezone.utc),
|
|
267
|
+
'actions_taken': [],
|
|
268
|
+
'subscription_status': None,
|
|
269
|
+
'subscription_renewed': False,
|
|
270
|
+
'fallback_applied': False,
|
|
271
|
+
'subscription_revoked': False,
|
|
272
|
+
'permissions_cleaned': 0,
|
|
273
|
+
'permissions_added': 0,
|
|
274
|
+
'credits_updated': 0,
|
|
275
|
+
'error': None,
|
|
276
|
+
'original_subscription': None,
|
|
277
|
+
'final_subscription': None,
|
|
278
|
+
'updated_userstatus': None # Will be populated with the updated UserStatus
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
# Get current user status
|
|
283
|
+
userstatus = await self.get_userstatus(user_uid)
|
|
284
|
+
if not userstatus:
|
|
285
|
+
result['actions_taken'].append('no_userstatus_found')
|
|
286
|
+
return result
|
|
287
|
+
|
|
288
|
+
# Always add the current userstatus to result (will be updated if database changes occur)
|
|
289
|
+
result['updated_userstatus'] = userstatus
|
|
290
|
+
|
|
291
|
+
# Check if there's an active subscription
|
|
292
|
+
if not userstatus.active_subscription:
|
|
293
|
+
result['actions_taken'].append('no_active_subscription')
|
|
294
|
+
|
|
295
|
+
# Clean expired permissions if requested
|
|
296
|
+
if clean_expired_permissions:
|
|
297
|
+
expired_permissions = userstatus.cleanup_expired_permissions()
|
|
298
|
+
if expired_permissions > 0:
|
|
299
|
+
result['permissions_cleaned'] = expired_permissions
|
|
300
|
+
result['actions_taken'].append('cleaned_expired_permissions')
|
|
301
|
+
|
|
302
|
+
# Save changes and update the result with the updated userstatus
|
|
303
|
+
updated_userstatus = await self.update_userstatus(
|
|
304
|
+
user_uid=user_uid,
|
|
305
|
+
status_data=userstatus.model_dump(exclude_none=True),
|
|
306
|
+
updater_uid=f"review:{updater_uid}"
|
|
307
|
+
)
|
|
308
|
+
result['updated_userstatus'] = updated_userstatus
|
|
309
|
+
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
# Store original subscription for comparison
|
|
313
|
+
result['original_subscription'] = userstatus.active_subscription
|
|
314
|
+
now = datetime.now(timezone.utc)
|
|
315
|
+
|
|
316
|
+
# Use the subscription's is_active() method to check current status
|
|
317
|
+
if userstatus.active_subscription.is_active():
|
|
318
|
+
result['subscription_status'] = str(SubscriptionStatus.ACTIVE)
|
|
319
|
+
result['final_subscription'] = userstatus.active_subscription
|
|
320
|
+
result['actions_taken'].append('subscription_still_active')
|
|
321
|
+
|
|
322
|
+
# Update credits if subscription is active and review_credits is enabled
|
|
323
|
+
if review_credits:
|
|
324
|
+
credits_updated = userstatus.update_subscription_credits()
|
|
325
|
+
if credits_updated > 0:
|
|
326
|
+
result['credits_updated'] = credits_updated
|
|
327
|
+
result['actions_taken'].append('updated_subscription_credits')
|
|
328
|
+
|
|
329
|
+
# Clean expired permissions if requested
|
|
330
|
+
if clean_expired_permissions:
|
|
331
|
+
expired_permissions = userstatus.cleanup_expired_permissions()
|
|
332
|
+
if expired_permissions > 0:
|
|
333
|
+
result['permissions_cleaned'] = expired_permissions
|
|
334
|
+
result['actions_taken'].append('cleaned_expired_permissions_only')
|
|
335
|
+
|
|
336
|
+
# Save changes if any updates were made
|
|
337
|
+
if result['credits_updated'] > 0 or result['permissions_cleaned'] > 0:
|
|
338
|
+
updated_userstatus = await self.update_userstatus(
|
|
339
|
+
user_uid=user_uid,
|
|
340
|
+
status_data=userstatus.model_dump(exclude_none=True),
|
|
341
|
+
updater_uid=f"review:{updater_uid}"
|
|
342
|
+
)
|
|
343
|
+
result['updated_userstatus'] = updated_userstatus
|
|
344
|
+
|
|
345
|
+
return result
|
|
346
|
+
|
|
347
|
+
# Subscription is not active - determine why and what to do
|
|
348
|
+
subscription = userstatus.active_subscription
|
|
349
|
+
|
|
350
|
+
# Check if cycle is expired but auto-renewal is still valid
|
|
351
|
+
if (review_auto_renewal and
|
|
352
|
+
subscription.auto_renew_end_datetime and
|
|
353
|
+
now <= subscription.auto_renew_end_datetime and
|
|
354
|
+
now > subscription.cycle_end_datetime_safe):
|
|
355
|
+
|
|
356
|
+
# Attempt auto-renewal by extending the cycle
|
|
357
|
+
try:
|
|
358
|
+
# Calculate new cycle start date (where the last cycle ended)
|
|
359
|
+
new_cycle_start = subscription.cycle_end_datetime_safe
|
|
360
|
+
|
|
361
|
+
# Create new subscription with updated cycle start date
|
|
362
|
+
subscription_dict = subscription.model_dump()
|
|
363
|
+
subscription_dict.update({
|
|
364
|
+
'cycle_start_date': new_cycle_start,
|
|
365
|
+
'cycle_end_datetime': None, # Let the model auto-calculate this
|
|
366
|
+
'updated_at': now,
|
|
367
|
+
'updated_by': f"UserstatusOperations.auto_renew:{updater_uid}"
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
renewed_subscription = UserSubscription(**subscription_dict)
|
|
371
|
+
|
|
372
|
+
# Apply the renewed subscription
|
|
373
|
+
userstatus.apply_subscription(
|
|
374
|
+
renewed_subscription,
|
|
375
|
+
add_associated_permissions=True,
|
|
376
|
+
remove_previous_subscription_permissions=True,
|
|
377
|
+
granted_by=f"UserstatusOperations.review.auto_renew:{updater_uid}"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
result['subscription_renewed'] = True
|
|
381
|
+
result['subscription_status'] = str(userstatus.active_subscription.status)
|
|
382
|
+
result['final_subscription'] = renewed_subscription
|
|
383
|
+
result['actions_taken'].append('auto_renewed_cycle')
|
|
384
|
+
|
|
385
|
+
except (ValueError, UserStatusError) as renewal_error:
|
|
386
|
+
self.logger.error("Auto-renewal failed for user %s: %s", user_uid, renewal_error)
|
|
387
|
+
result['error'] = f"Auto-renewal failed: {str(renewal_error)}"
|
|
388
|
+
result['actions_taken'].append('auto_renewal_failed')
|
|
389
|
+
# Continue to fallback logic
|
|
390
|
+
|
|
391
|
+
# If auto-renewal didn't happen or failed, check for fallback
|
|
392
|
+
if not result['subscription_renewed']:
|
|
393
|
+
if apply_fallback and subscription.fallback_plan_id:
|
|
394
|
+
try:
|
|
395
|
+
# We need subscription_ops to handle the fallback plan logic
|
|
396
|
+
if self.subscription_ops:
|
|
397
|
+
# Import the catalog service to get fallback plan
|
|
398
|
+
from ...services.catalog.catalog_subscriptionplan_service import CatalogSubscriptionPlanService
|
|
399
|
+
|
|
400
|
+
catalog_service = CatalogSubscriptionPlanService(firestore_client=self.db, logger=self.logger)
|
|
401
|
+
fallback_plan = await catalog_service.get_subscriptionplan(subscription.fallback_plan_id)
|
|
402
|
+
|
|
403
|
+
if fallback_plan:
|
|
404
|
+
# Create new subscription from fallback plan
|
|
405
|
+
fallback_subscription = self.subscription_ops.create_subscription_from_subscriptionplan(
|
|
406
|
+
plan=fallback_plan,
|
|
407
|
+
source=f"fallback_from_{subscription.plan_id}:review:{updater_uid}",
|
|
408
|
+
granted_at=now,
|
|
409
|
+
auto_renewal_end=fallback_plan.plan_default_auto_renewal_end
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Apply fallback subscription
|
|
413
|
+
permissions_added = userstatus.apply_subscription(
|
|
414
|
+
fallback_subscription,
|
|
415
|
+
add_associated_permissions=True,
|
|
416
|
+
remove_previous_subscription_permissions=True,
|
|
417
|
+
granted_by=f"UserstatusOperations.review.fallback:{updater_uid}"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
result['fallback_applied'] = True
|
|
421
|
+
result['subscription_status'] = str(userstatus.active_subscription.status)
|
|
422
|
+
result['final_subscription'] = fallback_subscription
|
|
423
|
+
result['permissions_added'] = permissions_added
|
|
424
|
+
result['actions_taken'].append('applied_fallback_plan')
|
|
425
|
+
|
|
426
|
+
else:
|
|
427
|
+
self.logger.warning("Fallback plan %s not found for user %s", subscription.fallback_plan_id, user_uid)
|
|
428
|
+
result['actions_taken'].append('fallback_plan_not_found')
|
|
429
|
+
else:
|
|
430
|
+
self.logger.warning("Cannot apply fallback - subscription_ops not available")
|
|
431
|
+
result['actions_taken'].append('fallback_unavailable_no_subscription_ops')
|
|
432
|
+
|
|
433
|
+
except (ValueError, UserStatusError) as fallback_error:
|
|
434
|
+
self.logger.error("Fallback application failed for user %s: %s", user_uid, fallback_error)
|
|
435
|
+
result['error'] = f"Fallback failed: {str(fallback_error)}"
|
|
436
|
+
result['actions_taken'].append('fallback_failed')
|
|
437
|
+
# Continue to revocation logic
|
|
438
|
+
|
|
439
|
+
# If no renewal or fallback happened, revoke the subscription
|
|
440
|
+
if not result['subscription_renewed'] and not result['fallback_applied']:
|
|
441
|
+
permissions_cleaned = userstatus.revoke_subscription(remove_associated_permissions=True)
|
|
442
|
+
result['subscription_revoked'] = True
|
|
443
|
+
result['subscription_status'] = None
|
|
444
|
+
result['permissions_cleaned'] = permissions_cleaned
|
|
445
|
+
result['actions_taken'].append('subscription_revoked')
|
|
446
|
+
|
|
447
|
+
# Clean up all expired permissions (not just those associated with the subscription)
|
|
448
|
+
if clean_expired_permissions:
|
|
449
|
+
additional_expired = userstatus.cleanup_expired_permissions()
|
|
450
|
+
if additional_expired > 0:
|
|
451
|
+
result['permissions_cleaned'] += additional_expired
|
|
452
|
+
result['actions_taken'].append('cleaned_additional_expired_permissions')
|
|
453
|
+
|
|
454
|
+
# Save all changes to database
|
|
455
|
+
updated_userstatus = await self.update_userstatus(
|
|
456
|
+
user_uid=user_uid,
|
|
457
|
+
status_data=userstatus.model_dump(exclude_none=False), # Include None values for proper updates
|
|
458
|
+
updater_uid=f"review:{updater_uid}"
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# Add the updated UserStatus to the result
|
|
462
|
+
result['updated_userstatus'] = updated_userstatus
|
|
463
|
+
|
|
464
|
+
self.logger.info(
|
|
465
|
+
"Completed comprehensive review for user %s. Status: %s, Actions: %s",
|
|
466
|
+
user_uid, result['subscription_status'], result['actions_taken']
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
except Exception as e:
|
|
470
|
+
self.logger.error("Comprehensive review failed for user %s: %s", user_uid, e, exc_info=True)
|
|
471
|
+
result['error'] = str(e)
|
|
472
|
+
result['actions_taken'].append('review_failed')
|
|
473
|
+
# Re-raise for proper error handling
|
|
474
|
+
raise
|
|
475
|
+
|
|
476
|
+
return result
|
{ipulse_shared_core_ftredge-22.1.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipulse_shared_core_ftredge
|
|
3
|
-
Version:
|
|
3
|
+
Version: 23.1.1
|
|
4
4
|
Summary: Shared Core models and Logger util for the Pulse platform project. Using AI for financial advisory and investment management.
|
|
5
5
|
Home-page: https://github.com/TheFutureEdge/ipulse_shared_core
|
|
6
6
|
Author: Russlan Ramdowar
|
{ipulse_shared_core_ftredge-22.1.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/RECORD
RENAMED
|
@@ -2,50 +2,49 @@ ipulse_shared_core_ftredge/__init__.py,sha256=-KbdF_YW8pgf7pVv9qh_cA1xrNm_B9zigH
|
|
|
2
2
|
ipulse_shared_core_ftredge/cache/__init__.py,sha256=i2fPojmZiBwAoY5ovnnnME9USl4bi8MRPYkAgEfACfI,136
|
|
3
3
|
ipulse_shared_core_ftredge/cache/shared_cache.py,sha256=BDJtkTsdfmVjKaUkbBXOhJ2Oib7Li0UCsPjWX7FLIPU,12940
|
|
4
4
|
ipulse_shared_core_ftredge/dependencies/__init__.py,sha256=HGsR8HUguKTfjz_BorCILS4izX8CAjG-apE0kIPE0Yo,68
|
|
5
|
-
ipulse_shared_core_ftredge/dependencies/auth_firebase_token_validation.py,sha256=
|
|
5
|
+
ipulse_shared_core_ftredge/dependencies/auth_firebase_token_validation.py,sha256=iwNqOEFHEF0qK7vfRPRXpAexTwF5UOLsfdir1wEo9_E,3646
|
|
6
6
|
ipulse_shared_core_ftredge/dependencies/auth_protected_router.py,sha256=em5D5tE7OkgZmuCtYCKuUAnIZCgRJhCF8Ye5QmtGWlk,1807
|
|
7
|
-
ipulse_shared_core_ftredge/dependencies/authz_for_apis.py,sha256=
|
|
7
|
+
ipulse_shared_core_ftredge/dependencies/authz_for_apis.py,sha256=t2hz1wAVxCT0lCDVf6gmcYeLuUCR7mIXYsThKg0cIlM,14992
|
|
8
8
|
ipulse_shared_core_ftredge/dependencies/firestore_client.py,sha256=VbTb121nsc9EZPd1RDEsHBLW5pIiVw6Wdo2JFL4afMg,714
|
|
9
9
|
ipulse_shared_core_ftredge/exceptions/__init__.py,sha256=Cb_RsIie4DbT_NLwFVwjw4riDKsNNRQEuAvHvYa-Zco,1038
|
|
10
|
-
ipulse_shared_core_ftredge/exceptions/base_exceptions.py,sha256=
|
|
10
|
+
ipulse_shared_core_ftredge/exceptions/base_exceptions.py,sha256=117YsiCbYLLBu_D0IffYFVSX2yh-pisALMtoSMwj6xI,5338
|
|
11
11
|
ipulse_shared_core_ftredge/exceptions/user_exceptions.py,sha256=I-nm21MKrUYEoybpRODeYNzc184HfgHvRZQm_xux4VY,6824
|
|
12
12
|
ipulse_shared_core_ftredge/models/__init__.py,sha256=oaBL_BZWd7hIDu2K7yxtVKxtkOD9UF9r9_V2ZIPQ8Yk,350
|
|
13
13
|
ipulse_shared_core_ftredge/models/base_api_response.py,sha256=OwuWI2PsMSLDkFt643u35ZhW5AHFEMMAGnGprmUO0fA,2380
|
|
14
14
|
ipulse_shared_core_ftredge/models/base_data_model.py,sha256=GZ7KTT5FanHTgvmaHHTxawzAJtuixkbyb-SuL-mjWys,2193
|
|
15
15
|
ipulse_shared_core_ftredge/models/catalog/__init__.py,sha256=9oKJ74_mTtmj-0iDnRBiPI8m8QJ2J9wvx4ZWaZw3zRk,208
|
|
16
|
-
ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py,sha256=
|
|
17
|
-
ipulse_shared_core_ftredge/models/catalog/usertype.py,sha256=
|
|
16
|
+
ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py,sha256=WxKWzTmHJlvFQj6Kq69iWMoFkx_veiPhonFo8dUGzZw,9148
|
|
17
|
+
ipulse_shared_core_ftredge/models/catalog/usertype.py,sha256=E_qQCq7ytiFca6umaX_-_a6TuDh83YwSKtFKdeU4ErM,6584
|
|
18
18
|
ipulse_shared_core_ftredge/models/user/__init__.py,sha256=TheOLldY6v-OK9i-A5mQNIxjHhBFpuOJ43mi-swcN_o,196
|
|
19
19
|
ipulse_shared_core_ftredge/models/user/user_permissions.py,sha256=CUWDBPLPmKN3o43BTZAt0zJvm_ekjJA46iV6rVNp-oc,2411
|
|
20
|
-
ipulse_shared_core_ftredge/models/user/user_subscription.py,sha256=
|
|
20
|
+
ipulse_shared_core_ftredge/models/user/user_subscription.py,sha256=83ncQUHUYF6S19KdZM7-nLEVjD4VPIO88ZRVkXeNnE8,13514
|
|
21
21
|
ipulse_shared_core_ftredge/models/user/userauth.py,sha256=PbS-XSLxDl1feskI0iCziGvmMiLuF8o_ZTspAx1B0j0,3679
|
|
22
22
|
ipulse_shared_core_ftredge/models/user/userprofile.py,sha256=7VbE4qiKpDxZsNTk-IJKA32QxW0JOo8KWPkj8h9J2-Y,6945
|
|
23
|
-
ipulse_shared_core_ftredge/models/user/userstatus.py,sha256=
|
|
23
|
+
ipulse_shared_core_ftredge/models/user/userstatus.py,sha256=9BGaKiKL3JeSNQ8ZRRc0taLgSu0uxtBu5NRVsCbVXW4,18776
|
|
24
24
|
ipulse_shared_core_ftredge/monitoring/__init__.py,sha256=gUoJjT0wj-cQYnMWheWbh1mmRHmaeojmnBZTj7KPNus,61
|
|
25
25
|
ipulse_shared_core_ftredge/monitoring/tracemon.py,sha256=Trku0qrwWvEcvKsBWiYokd_G3fcH-5uP2wRVgcgIz_k,11596
|
|
26
26
|
ipulse_shared_core_ftredge/services/__init__.py,sha256=9AkMLCHNswhuNbQuJZaEVz4zt4F84PxfJLyU_bYk4Js,565
|
|
27
27
|
ipulse_shared_core_ftredge/services/charging_processors.py,sha256=9Re24dyXdjKYbqwx6uNLu3JBzIaw87TAV7Oe__M1QnA,16308
|
|
28
28
|
ipulse_shared_core_ftredge/services/user_charging_service.py,sha256=C3wMfgBXOz4RM1RLc7up2_pIPAnZv8ZYu-lLrkofTmc,14625
|
|
29
29
|
ipulse_shared_core_ftredge/services/base/__init__.py,sha256=zhyrHQMM0cLJr4spk2b6VsgJXuWBy7hUBzhrq_Seg9k,389
|
|
30
|
-
ipulse_shared_core_ftredge/services/base/base_firestore_service.py,sha256=
|
|
30
|
+
ipulse_shared_core_ftredge/services/base/base_firestore_service.py,sha256=leZFwxb1ruheypqudpKnuNtRQXtO4KNeoJk6ZACozHc,19512
|
|
31
31
|
ipulse_shared_core_ftredge/services/base/cache_aware_firestore_service.py,sha256=ya5Asff9BQodYnJVAw6M_Pm8WtVRPpEK7izFlZ2MyjA,10016
|
|
32
32
|
ipulse_shared_core_ftredge/services/catalog/__init__.py,sha256=ctc2nDGwsW_Ji4lk9pys3oyNwR_V-gHSbSHawym5fKQ,385
|
|
33
|
-
ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py,sha256=
|
|
34
|
-
ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py,sha256=
|
|
33
|
+
ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py,sha256=X5xAi9sOk_F1ky0ECwPVlwIPPsN2PrZC6bN_pASGDjQ,9702
|
|
34
|
+
ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py,sha256=C_VWxZ5iPcybjsSXdmZHyqS--rI3KY8pp7JDIy_L7S8,12833
|
|
35
35
|
ipulse_shared_core_ftredge/services/user/__init__.py,sha256=jmkD5XzAmaD8QV2UsgB5xynGcfsXliWtRtN2pt6kzbA,884
|
|
36
|
-
ipulse_shared_core_ftredge/services/user/
|
|
37
|
-
ipulse_shared_core_ftredge/services/user/
|
|
38
|
-
ipulse_shared_core_ftredge/services/user/user_multistep_operations.py,sha256=hQ1-BygC2Cxin6AUsEVgEWfZg035v5yn__sAoPzy0Us,35543
|
|
36
|
+
ipulse_shared_core_ftredge/services/user/user_core_service.py,sha256=o0trN4yTbyh-BJbXnin9XmS8hW5jOQW6RdegdQ2sRNo,28269
|
|
37
|
+
ipulse_shared_core_ftredge/services/user/user_multistep_operations.py,sha256=0MfMaKLGpVsgfD-Vgaa4s2dKk9nNoH6snWx_skkPy_o,39705
|
|
39
38
|
ipulse_shared_core_ftredge/services/user/user_permissions_operations.py,sha256=FByszIWo-qooLVXFTw0tGLWksIJEqHUPc_ZGwue0_pM,15753
|
|
40
|
-
ipulse_shared_core_ftredge/services/user/user_subscription_operations.py,sha256=
|
|
39
|
+
ipulse_shared_core_ftredge/services/user/user_subscription_operations.py,sha256=z98EO67wHlnDj1V7JK14yq6yaIoTVcX5X5v4-taZFHw,21651
|
|
41
40
|
ipulse_shared_core_ftredge/services/user/userauth_operations.py,sha256=9l2uBAcAxbUnilK8MZ7IlHzaGiaPuqx7nIC51mAyR9w,36120
|
|
42
41
|
ipulse_shared_core_ftredge/services/user/userprofile_operations.py,sha256=_qyIEAQYCTV-subgP-5naMs_26apCpauomE6qmCCVWs,7333
|
|
43
|
-
ipulse_shared_core_ftredge/services/user/userstatus_operations.py,sha256=
|
|
42
|
+
ipulse_shared_core_ftredge/services/user/userstatus_operations.py,sha256=sW4Q-aMG1mjKvqVKK5AA93-G57FPBCkxO7rPfCkhBd8,22726
|
|
44
43
|
ipulse_shared_core_ftredge/utils/__init__.py,sha256=JnxUb8I2MRjJC7rBPXSrpwBIQDEOku5O9JsiTi3oun8,56
|
|
45
44
|
ipulse_shared_core_ftredge/utils/custom_json_encoder.py,sha256=DblQLD0KOSNDyQ58wQRogBrShIXzPIZUw_oGOBATnJY,1366
|
|
46
45
|
ipulse_shared_core_ftredge/utils/json_encoder.py,sha256=QkcaFneVv3-q-s__Dz4OiUWYnM6jgHDJrDMdPv09RCA,2093
|
|
47
|
-
ipulse_shared_core_ftredge-
|
|
48
|
-
ipulse_shared_core_ftredge-
|
|
49
|
-
ipulse_shared_core_ftredge-
|
|
50
|
-
ipulse_shared_core_ftredge-
|
|
51
|
-
ipulse_shared_core_ftredge-
|
|
46
|
+
ipulse_shared_core_ftredge-23.1.1.dist-info/licenses/LICENCE,sha256=YBtYAXNqCCOo9Mr2hfkbSPAM9CeAr2j1VZBSwQTrNwE,1060
|
|
47
|
+
ipulse_shared_core_ftredge-23.1.1.dist-info/METADATA,sha256=keIkjScj_B7Wix1MqoCIY8aR5JRAVkbdJwFd3HWnslg,782
|
|
48
|
+
ipulse_shared_core_ftredge-23.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
49
|
+
ipulse_shared_core_ftredge-23.1.1.dist-info/top_level.txt,sha256=8sgYrptpexkA_6_HyGvho26cVFH9kmtGvaK8tHbsGHk,27
|
|
50
|
+
ipulse_shared_core_ftredge-23.1.1.dist-info/RECORD,,
|