ipulse-shared-core-ftredge 19.0.1__py3-none-any.whl → 22.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ipulse-shared-core-ftredge might be problematic. Click here for more details.
- ipulse_shared_core_ftredge/cache/shared_cache.py +1 -2
- ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +4 -4
- ipulse_shared_core_ftredge/exceptions/base_exceptions.py +23 -0
- ipulse_shared_core_ftredge/models/__init__.py +3 -7
- ipulse_shared_core_ftredge/models/base_data_model.py +17 -19
- ipulse_shared_core_ftredge/models/catalog/__init__.py +10 -0
- ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py +273 -0
- ipulse_shared_core_ftredge/models/catalog/usertype.py +170 -0
- ipulse_shared_core_ftredge/models/user/__init__.py +5 -0
- ipulse_shared_core_ftredge/models/user/user_permissions.py +66 -0
- ipulse_shared_core_ftredge/models/{subscription.py → user/user_subscription.py} +66 -20
- ipulse_shared_core_ftredge/models/{user_auth.py → user/userauth.py} +19 -10
- ipulse_shared_core_ftredge/models/{user_profile.py → user/userprofile.py} +53 -21
- ipulse_shared_core_ftredge/models/user/userstatus.py +430 -0
- ipulse_shared_core_ftredge/monitoring/__init__.py +2 -2
- ipulse_shared_core_ftredge/monitoring/tracemon.py +320 -0
- ipulse_shared_core_ftredge/services/__init__.py +11 -13
- ipulse_shared_core_ftredge/services/base/__init__.py +3 -1
- ipulse_shared_core_ftredge/services/base/base_firestore_service.py +73 -14
- ipulse_shared_core_ftredge/services/{cache_aware_firestore_service.py → base/cache_aware_firestore_service.py} +46 -32
- ipulse_shared_core_ftredge/services/catalog/__init__.py +14 -0
- ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py +273 -0
- ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py +307 -0
- ipulse_shared_core_ftredge/services/charging_processors.py +25 -25
- ipulse_shared_core_ftredge/services/user/__init__.py +5 -25
- ipulse_shared_core_ftredge/services/user/firebase_auth_admin_helpers.py +160 -0
- ipulse_shared_core_ftredge/services/user/user_core_service.py +423 -515
- ipulse_shared_core_ftredge/services/user/user_multistep_operations.py +726 -0
- ipulse_shared_core_ftredge/services/user/user_permissions_operations.py +392 -0
- ipulse_shared_core_ftredge/services/user/user_subscription_operations.py +484 -0
- ipulse_shared_core_ftredge/services/user/userauth_operations.py +928 -0
- ipulse_shared_core_ftredge/services/user/userprofile_operations.py +166 -0
- ipulse_shared_core_ftredge/services/user/userstatus_operations.py +212 -0
- ipulse_shared_core_ftredge/services/{charging_service.py → user_charging_service.py} +9 -9
- {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/METADATA +3 -4
- ipulse_shared_core_ftredge-22.1.1.dist-info/RECORD +51 -0
- ipulse_shared_core_ftredge/models/user_status.py +0 -495
- ipulse_shared_core_ftredge/monitoring/microservmon.py +0 -483
- ipulse_shared_core_ftredge/services/user/iam_management_operations.py +0 -326
- ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +0 -384
- ipulse_shared_core_ftredge/services/user/user_account_operations.py +0 -479
- ipulse_shared_core_ftredge/services/user/user_auth_operations.py +0 -305
- ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +0 -436
- ipulse_shared_core_ftredge-19.0.1.dist-info/RECORD +0 -41
- {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/WHEEL +0 -0
- {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/licenses/LICENCE +0 -0
- {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/top_level.txt +0 -0
|
@@ -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
|