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.
- ipulse_shared_core_ftredge/cache/shared_cache.py +1 -2
- 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 +35 -4
- 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 +274 -0
- ipulse_shared_core_ftredge/models/catalog/usertype.py +177 -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/user/user_subscription.py +348 -0
- 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 +479 -0
- ipulse_shared_core_ftredge/monitoring/__init__.py +0 -2
- ipulse_shared_core_ftredge/monitoring/tracemon.py +6 -6
- 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 +77 -16
- 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 +277 -0
- ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py +376 -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/user_core_service.py +536 -510
- ipulse_shared_core_ftredge/services/user/user_multistep_operations.py +796 -0
- ipulse_shared_core_ftredge/services/user/user_permissions_operations.py +392 -0
- ipulse_shared_core_ftredge/services/user/user_subscription_operations.py +488 -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 +476 -0
- ipulse_shared_core_ftredge/services/{charging_service.py → user_charging_service.py} +9 -9
- {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/METADATA +3 -4
- ipulse_shared_core_ftredge-23.1.1.dist-info/RECORD +50 -0
- ipulse_shared_core_ftredge/models/subscription.py +0 -190
- ipulse_shared_core_ftredge/models/user_status.py +0 -495
- ipulse_shared_core_ftredge/monitoring/microservmon.py +0 -526
- 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-20.0.1.dist-info/RECORD +0 -42
- {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/WHEEL +0 -0
- {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/licenses/LICENCE +0 -0
- {ipulse_shared_core_ftredge-20.0.1.dist-info → ipulse_shared_core_ftredge-23.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
usertype Catalog Service
|
|
3
|
+
|
|
4
|
+
This service manages usertype templates stored in Firestore.
|
|
5
|
+
These templates are used to configure default settings for user profiles and statuses.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, List, Optional, Any
|
|
10
|
+
from google.cloud.firestore import Client , Query
|
|
11
|
+
from ipulse_shared_base_ftredge import IAMUserType
|
|
12
|
+
from ipulse_shared_base_ftredge.enums.enums_status import ObjectOverallStatus
|
|
13
|
+
from ipulse_shared_core_ftredge.models.catalog.usertype import UserType
|
|
14
|
+
from ipulse_shared_core_ftredge.services.base.base_firestore_service import BaseFirestoreService
|
|
15
|
+
|
|
16
|
+
class CatalogUserTypeService(BaseFirestoreService[UserType]):
|
|
17
|
+
"""
|
|
18
|
+
Service for managing usertype catalog configurations.
|
|
19
|
+
|
|
20
|
+
This service provides CRUD operations for usertype templates that define
|
|
21
|
+
the default settings and permissions for different usertypes.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
firestore_client: Client,
|
|
27
|
+
logger: Optional[logging.Logger] = None
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Initialize the usertype Catalog Service.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
firestore_client: Firestore client instance
|
|
34
|
+
logger: Logger instance (optional)
|
|
35
|
+
"""
|
|
36
|
+
super().__init__(
|
|
37
|
+
db=firestore_client,
|
|
38
|
+
collection_name="papp_core_catalog_usertypes",
|
|
39
|
+
resource_type="usertype",
|
|
40
|
+
model_class=UserType,
|
|
41
|
+
logger=logger or logging.getLogger(__name__)
|
|
42
|
+
)
|
|
43
|
+
self.archive_collection_name = "~archive_papp_core_catalog_usertypes"
|
|
44
|
+
|
|
45
|
+
async def create_usertype(
|
|
46
|
+
self,
|
|
47
|
+
usertype_id: str,
|
|
48
|
+
user_type: UserType,
|
|
49
|
+
creator_uid: str
|
|
50
|
+
) -> UserType:
|
|
51
|
+
"""
|
|
52
|
+
Create a new usertype.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
usertype_id: Unique identifier for the usertype
|
|
56
|
+
user_type: Usertype data
|
|
57
|
+
creator_uid: UID of the user creating the usertype
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Created usertype
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ServiceError: If creation fails
|
|
64
|
+
ValidationError: If usertype data is invalid
|
|
65
|
+
"""
|
|
66
|
+
self.logger.info(f"Creating usertype: {usertype_id}")
|
|
67
|
+
|
|
68
|
+
# Create the document
|
|
69
|
+
created_doc = await self.create_document(
|
|
70
|
+
doc_id=usertype_id,
|
|
71
|
+
data=user_type,
|
|
72
|
+
creator_uid=creator_uid
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Convert back to model
|
|
76
|
+
result = UserType.model_validate(created_doc)
|
|
77
|
+
self.logger.info(f"Successfully created usertype: {usertype_id}")
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
async def get_usertype(self, usertype_id: str) -> Optional[UserType]:
|
|
81
|
+
"""
|
|
82
|
+
Retrieve a usertype by ID.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
usertype_id: Unique identifier for the usertype
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
usertype if found, None otherwise
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
ServiceError: If retrieval fails
|
|
92
|
+
"""
|
|
93
|
+
self.logger.debug(f"Retrieving usertype: {usertype_id}")
|
|
94
|
+
doc_data = await self.get_document(usertype_id)
|
|
95
|
+
if doc_data is None:
|
|
96
|
+
return None
|
|
97
|
+
return UserType.model_validate(doc_data) if isinstance(doc_data, dict) else doc_data
|
|
98
|
+
|
|
99
|
+
async def update_usertype(
|
|
100
|
+
self,
|
|
101
|
+
usertype_id: str,
|
|
102
|
+
updates: Dict[str, Any],
|
|
103
|
+
updater_uid: str
|
|
104
|
+
) -> UserType:
|
|
105
|
+
"""
|
|
106
|
+
Update a usertype.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
usertype_id: Unique identifier for the usertype
|
|
110
|
+
updates: Fields to update
|
|
111
|
+
updater_uid: UID of the user updating the usertype
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Updated usertype
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
ServiceError: If update fails
|
|
118
|
+
ResourceNotFoundError: If usertype not found
|
|
119
|
+
ValidationError: If update data is invalid
|
|
120
|
+
"""
|
|
121
|
+
self.logger.info(f"Updating usertype: {usertype_id}")
|
|
122
|
+
|
|
123
|
+
updated_doc = await self.update_document(
|
|
124
|
+
doc_id=usertype_id,
|
|
125
|
+
update_data=updates,
|
|
126
|
+
updater_uid=updater_uid
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
result = UserType.model_validate(updated_doc)
|
|
130
|
+
self.logger.info(f"Successfully updated usertype: {usertype_id}")
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
async def delete_usertype(
|
|
134
|
+
self,
|
|
135
|
+
usertype_id: str,
|
|
136
|
+
archive: bool = True
|
|
137
|
+
) -> bool:
|
|
138
|
+
"""
|
|
139
|
+
Delete a usertype.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
usertype_id: Unique identifier for the usertype
|
|
143
|
+
archive: Whether to archive the usertype before deletion
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
True if deletion was successful
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ServiceError: If deletion fails
|
|
150
|
+
ResourceNotFoundError: If usertype not found
|
|
151
|
+
"""
|
|
152
|
+
self.logger.info(f"Deleting usertype: {usertype_id}")
|
|
153
|
+
|
|
154
|
+
if archive:
|
|
155
|
+
# Get the usertype data before deletion for archiving
|
|
156
|
+
template = await self.get_usertype(usertype_id)
|
|
157
|
+
if template:
|
|
158
|
+
await self.archive_document(
|
|
159
|
+
document_data=template.model_dump(),
|
|
160
|
+
doc_id=usertype_id,
|
|
161
|
+
archive_collection=self.archive_collection_name,
|
|
162
|
+
archived_by="system"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
result = await self.delete_document(usertype_id)
|
|
166
|
+
self.logger.info(f"Successfully deleted usertype: {usertype_id}")
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
async def list_usertypes(
|
|
170
|
+
self,
|
|
171
|
+
primary_usertype: Optional[IAMUserType] = None,
|
|
172
|
+
pulse_status: Optional[ObjectOverallStatus] = None,
|
|
173
|
+
latest_version_only: bool = False,
|
|
174
|
+
limit: Optional[int] = None,
|
|
175
|
+
version_ordering: str = "DESCENDING"
|
|
176
|
+
) -> List[UserType]:
|
|
177
|
+
"""
|
|
178
|
+
List usertypes with optional filtering.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
primary_usertype: Filter by specific primary usertype
|
|
182
|
+
pulse_status: Filter by specific pulse status
|
|
183
|
+
latest_version_only: Only return the latest version per usertype
|
|
184
|
+
limit: Maximum number of usertypes to return
|
|
185
|
+
version_ordering: Order direction for version ('ASCENDING' or 'DESCENDING')
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of usertypes
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
ServiceError: If listing fails
|
|
192
|
+
"""
|
|
193
|
+
self.logger.debug(f"Listing usertypes - primary_usertype: {primary_usertype}, pulse_status: {pulse_status}, latest_version_only: {latest_version_only}, version_ordering: {version_ordering}")
|
|
194
|
+
|
|
195
|
+
# Build query filters
|
|
196
|
+
filters = []
|
|
197
|
+
if primary_usertype:
|
|
198
|
+
filters.append(("primary_usertype", "==", primary_usertype.value))
|
|
199
|
+
if pulse_status:
|
|
200
|
+
filters.append(("pulse_status", "==", pulse_status.value))
|
|
201
|
+
|
|
202
|
+
# Set ordering
|
|
203
|
+
order_by = "version"
|
|
204
|
+
order_direction = Query.DESCENDING if version_ordering == "DESCENDING" else Query.ASCENDING
|
|
205
|
+
|
|
206
|
+
# Optimize query if only the latest version of a specific usertype is needed
|
|
207
|
+
query_limit = limit
|
|
208
|
+
if latest_version_only and primary_usertype:
|
|
209
|
+
query_limit = 1
|
|
210
|
+
# Ensure descending order to get the latest
|
|
211
|
+
order_direction = Query.DESCENDING
|
|
212
|
+
|
|
213
|
+
docs = await self.list_documents(
|
|
214
|
+
filters=filters,
|
|
215
|
+
order_by=order_by,
|
|
216
|
+
order_direction=order_direction,
|
|
217
|
+
limit=query_limit
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Convert to UserType models
|
|
221
|
+
usertypes = [UserType.model_validate(doc) for doc in docs]
|
|
222
|
+
|
|
223
|
+
# If we need the latest of all usertypes, we fetch all sorted by version
|
|
224
|
+
# and then pick the first one for each primary_usertype in Python.
|
|
225
|
+
if latest_version_only and not primary_usertype:
|
|
226
|
+
# This assumes the list is sorted by version descending.
|
|
227
|
+
if order_direction != Query.DESCENDING:
|
|
228
|
+
self.logger.warning("latest_version_only is True but version_ordering is not DESCENDING. Results may not be the latest.")
|
|
229
|
+
|
|
230
|
+
usertype_groups = {}
|
|
231
|
+
for usertype in usertypes:
|
|
232
|
+
key = usertype.primary_usertype.value
|
|
233
|
+
if key not in usertype_groups:
|
|
234
|
+
usertype_groups[key] = usertype # First one is the latest due to sorting
|
|
235
|
+
|
|
236
|
+
return list(usertype_groups.values())
|
|
237
|
+
|
|
238
|
+
return usertypes
|
|
239
|
+
|
|
240
|
+
def _get_collection(self):
|
|
241
|
+
"""Get the Firestore collection reference."""
|
|
242
|
+
return self.db.collection(self.collection_name)
|
|
243
|
+
|
|
244
|
+
async def usertype_exists(self, usertype_id: str) -> bool:
|
|
245
|
+
"""
|
|
246
|
+
Check if a usertype exists.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
usertype_id: Unique identifier for the usertype
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
True if usertype exists, False otherwise
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
ServiceError: If check fails
|
|
256
|
+
"""
|
|
257
|
+
return await self.document_exists(usertype_id)
|
|
258
|
+
|
|
259
|
+
async def validate_usertype_data(self, usertype_data: Dict[str, Any]) -> tuple[bool, List[str]]:
|
|
260
|
+
"""
|
|
261
|
+
Validate usertype data.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
usertype_data: Usertype data to validate
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Tuple of (is_valid, list_of_errors)
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
UserType.model_validate(usertype_data)
|
|
271
|
+
return True, []
|
|
272
|
+
except Exception as e:
|
|
273
|
+
return False, [str(e)]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
async def get_default_credits_for_usertype(self, primary_usertype: IAMUserType) -> Dict[str, int]:
|
|
277
|
+
"""
|
|
278
|
+
Get default credit settings for a specific usertype.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
primary_usertype: The primary usertype
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Dictionary with default credit values
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
ServiceError: If retrieval fails
|
|
288
|
+
"""
|
|
289
|
+
self.logger.debug(f"Getting default credits for usertype: {primary_usertype}")
|
|
290
|
+
|
|
291
|
+
usertypes = await self.list_usertypes(
|
|
292
|
+
primary_usertype=primary_usertype,
|
|
293
|
+
pulse_status=ObjectOverallStatus.ACTIVE,
|
|
294
|
+
latest_version_only=True,
|
|
295
|
+
limit=1
|
|
296
|
+
)
|
|
297
|
+
usertype = usertypes[0] if usertypes else None
|
|
298
|
+
if not usertype:
|
|
299
|
+
self.logger.warning(f"No active usertype found for: {primary_usertype}")
|
|
300
|
+
return {
|
|
301
|
+
"subscription_based_insight_credits": 0,
|
|
302
|
+
"extra_insight_credits": 0,
|
|
303
|
+
"voting_credits": 0
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
"extra_insight_credits": usertype.default_extra_insight_credits,
|
|
308
|
+
"voting_credits": usertype.default_voting_credits
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async def fetch_catalog_usertype_based_on_email(
|
|
312
|
+
self,
|
|
313
|
+
email: str,
|
|
314
|
+
superadmins: Optional[List[str]] = None,
|
|
315
|
+
admins: Optional[List[str]] = None,
|
|
316
|
+
internal_domains: Optional[List[str]] = None
|
|
317
|
+
) -> UserType:
|
|
318
|
+
"""
|
|
319
|
+
Fetch the actual usertype from catalog based on email domain classification.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
email: User's email address
|
|
323
|
+
superadmins: List of superadmin emails
|
|
324
|
+
admins: List of admin emails
|
|
325
|
+
internal_domains: List of internal domains
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
UserType object from the catalog
|
|
329
|
+
|
|
330
|
+
Raises:
|
|
331
|
+
ServiceError: If no active usertype found for the determined primary_usertype
|
|
332
|
+
"""
|
|
333
|
+
if not email:
|
|
334
|
+
primary_usertype = IAMUserType.CUSTOMER
|
|
335
|
+
else:
|
|
336
|
+
superadmins = superadmins or []
|
|
337
|
+
admins = admins or []
|
|
338
|
+
internal_domains = internal_domains or []
|
|
339
|
+
|
|
340
|
+
email_lower = email.lower()
|
|
341
|
+
|
|
342
|
+
if email_lower in [admin.lower() for admin in superadmins]:
|
|
343
|
+
primary_usertype = IAMUserType.SUPERADMIN
|
|
344
|
+
elif email_lower in [admin.lower() for admin in admins]:
|
|
345
|
+
primary_usertype = IAMUserType.ADMIN
|
|
346
|
+
else:
|
|
347
|
+
domain = email_lower.split('@')[-1] if '@' in email_lower else ''
|
|
348
|
+
if domain in internal_domains:
|
|
349
|
+
primary_usertype = IAMUserType.INTERNAL
|
|
350
|
+
else:
|
|
351
|
+
primary_usertype = IAMUserType.CUSTOMER
|
|
352
|
+
|
|
353
|
+
# Get the actual usertype from the catalog
|
|
354
|
+
usertypes = await self.list_usertypes(
|
|
355
|
+
primary_usertype=primary_usertype,
|
|
356
|
+
pulse_status=ObjectOverallStatus.ACTIVE,
|
|
357
|
+
latest_version_only=True,
|
|
358
|
+
limit=1
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
if not usertypes:
|
|
362
|
+
from ipulse_shared_core_ftredge.exceptions import ServiceError
|
|
363
|
+
raise ServiceError(
|
|
364
|
+
error=f"No active usertype found in catalog for primary_usertype '{primary_usertype.value}'",
|
|
365
|
+
resource_type="usertype",
|
|
366
|
+
operation="fetch_catalog_usertype_based_on_email",
|
|
367
|
+
additional_info={
|
|
368
|
+
"email": email,
|
|
369
|
+
"primary_usertype": primary_usertype.value,
|
|
370
|
+
"domain": email.split('@')[-1] if '@' in email else ''
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
usertype = usertypes[0]
|
|
375
|
+
self.logger.info(f"Found usertype '{usertype.id}' for email '{email}' with primary_usertype '{primary_usertype.value}'")
|
|
376
|
+
return usertype
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Reusable credit checking and charging utilities for services."""
|
|
2
2
|
import os
|
|
3
3
|
from typing import Dict, Any, List, Optional, Callable, Awaitable, TypeVar
|
|
4
|
-
from ipulse_shared_core_ftredge.services.
|
|
4
|
+
from ipulse_shared_core_ftredge.services.user_charging_service import UserChargingService, ValidationError
|
|
5
5
|
import logging
|
|
6
6
|
|
|
7
7
|
T = TypeVar('T')
|
|
@@ -9,8 +9,8 @@ T = TypeVar('T')
|
|
|
9
9
|
class ChargingProcessor:
|
|
10
10
|
"""Handles credit checking and charging for both single item and batch access."""
|
|
11
11
|
|
|
12
|
-
def __init__(self,
|
|
13
|
-
self.
|
|
12
|
+
def __init__(self, user_charging_service: UserChargingService, logger: logging.Logger):
|
|
13
|
+
self.user_charging_service = user_charging_service
|
|
14
14
|
self.logger = logger
|
|
15
15
|
|
|
16
16
|
async def process_single_item_charging(
|
|
@@ -54,9 +54,9 @@ class ChargingProcessor:
|
|
|
54
54
|
# For free items, provide current credits if available
|
|
55
55
|
if pre_fetched_credits:
|
|
56
56
|
updated_user_credits = pre_fetched_credits
|
|
57
|
-
elif self.
|
|
57
|
+
elif self.user_charging_service: # Attempt to get current credits if not pre-fetched
|
|
58
58
|
try:
|
|
59
|
-
_, current_user_credits_from_verify = await self.
|
|
59
|
+
_, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, None)
|
|
60
60
|
updated_user_credits = current_user_credits_from_verify
|
|
61
61
|
except Exception: # pylint: disable=broad-except
|
|
62
62
|
self.logger.warning(f"Could not fetch current credits for user {user_uid} for free item.")
|
|
@@ -75,9 +75,9 @@ class ChargingProcessor:
|
|
|
75
75
|
# Similar to free items, provide current credits if available
|
|
76
76
|
if pre_fetched_credits:
|
|
77
77
|
updated_user_credits = pre_fetched_credits
|
|
78
|
-
elif self.
|
|
78
|
+
elif self.user_charging_service:
|
|
79
79
|
try:
|
|
80
|
-
_, current_user_credits_from_verify = await self.
|
|
80
|
+
_, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, None)
|
|
81
81
|
updated_user_credits = current_user_credits_from_verify
|
|
82
82
|
except Exception: # pylint: disable=broad-except
|
|
83
83
|
self.logger.warning(f"Could not fetch current credits for user {user_uid} during debug bypass.")
|
|
@@ -90,7 +90,7 @@ class ChargingProcessor:
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
# Verify credit service is available
|
|
93
|
-
if not self.
|
|
93
|
+
if not self.user_charging_service:
|
|
94
94
|
self.logger.error("ChargingService not initialized.")
|
|
95
95
|
return {
|
|
96
96
|
'access_granted': False,
|
|
@@ -101,7 +101,7 @@ class ChargingProcessor:
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
# Verify user has enough credits
|
|
104
|
-
has_credits, current_user_credits_from_verify = await self.
|
|
104
|
+
has_credits, current_user_credits_from_verify = await self.user_charging_service.verify_credits(
|
|
105
105
|
user_uid,
|
|
106
106
|
credit_cost,
|
|
107
107
|
pre_fetched_user_credits=pre_fetched_credits
|
|
@@ -121,7 +121,7 @@ class ChargingProcessor:
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
# Charge the user - this now returns (bool, Optional[Dict])
|
|
124
|
-
charged, calculated_updated_credits = await self.
|
|
124
|
+
charged, calculated_updated_credits = await self.user_charging_service.charge_credits_transaction(
|
|
125
125
|
user_uid,
|
|
126
126
|
credit_cost,
|
|
127
127
|
operation_description
|
|
@@ -143,9 +143,9 @@ class ChargingProcessor:
|
|
|
143
143
|
except ValidationError as ve:
|
|
144
144
|
self.logger.error(f"Validation error for item {item_id}, user {user_uid}: {str(ve)}")
|
|
145
145
|
# Try to get current credits to return
|
|
146
|
-
if self.
|
|
146
|
+
if self.user_charging_service:
|
|
147
147
|
try:
|
|
148
|
-
_, updated_user_credits = await self.
|
|
148
|
+
_, updated_user_credits = await self.user_charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
|
|
149
149
|
except Exception: # pylint: disable=broad-except
|
|
150
150
|
pass # Keep updated_user_credits as None
|
|
151
151
|
ve.additional_info = ve.additional_info or {}
|
|
@@ -155,9 +155,9 @@ class ChargingProcessor:
|
|
|
155
155
|
self.logger.error(f"Unexpected error during credit processing for item {item_id}, user {user_uid}: {str(e)}", exc_info=True)
|
|
156
156
|
# Try to get current credits to return
|
|
157
157
|
current_user_credits_on_error = None
|
|
158
|
-
if self.
|
|
158
|
+
if self.user_charging_service:
|
|
159
159
|
try:
|
|
160
|
-
_, current_user_credits_on_error = await self.
|
|
160
|
+
_, current_user_credits_on_error = await self.user_charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
|
|
161
161
|
except Exception: # pylint: disable=broad-except
|
|
162
162
|
pass
|
|
163
163
|
return {
|
|
@@ -228,9 +228,9 @@ class ChargingProcessor:
|
|
|
228
228
|
if not paid_items:
|
|
229
229
|
if pre_fetched_credits:
|
|
230
230
|
updated_user_credits = pre_fetched_credits
|
|
231
|
-
elif self.
|
|
231
|
+
elif self.user_charging_service:
|
|
232
232
|
try:
|
|
233
|
-
_, current_user_credits_from_verify = await self.
|
|
233
|
+
_, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, None)
|
|
234
234
|
updated_user_credits = current_user_credits_from_verify
|
|
235
235
|
|
|
236
236
|
except Exception: # pylint: disable=broad-except
|
|
@@ -250,9 +250,9 @@ class ChargingProcessor:
|
|
|
250
250
|
self.logger.info(f"Bypassing credit check for {len(paid_items)} paid items due to debug mode")
|
|
251
251
|
if pre_fetched_credits:
|
|
252
252
|
updated_user_credits = pre_fetched_credits
|
|
253
|
-
elif self.
|
|
253
|
+
elif self.user_charging_service:
|
|
254
254
|
try:
|
|
255
|
-
_, current_user_credits_from_verify = await self.
|
|
255
|
+
_, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, None)
|
|
256
256
|
updated_user_credits = current_user_credits_from_verify
|
|
257
257
|
except Exception: # pylint: disable=broad-except
|
|
258
258
|
self.logger.warning(f"Could not fetch current credits for user {user_uid} during debug bypass for batch.")
|
|
@@ -267,7 +267,7 @@ class ChargingProcessor:
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
# Verify credit service is available
|
|
270
|
-
if not self.
|
|
270
|
+
if not self.user_charging_service:
|
|
271
271
|
self.logger.error("ChargingService not initialized for batch processing.")
|
|
272
272
|
return {
|
|
273
273
|
'accessible_items': free_items,
|
|
@@ -280,7 +280,7 @@ class ChargingProcessor:
|
|
|
280
280
|
|
|
281
281
|
try:
|
|
282
282
|
# Verify user has enough credits for total cost
|
|
283
|
-
has_credits, current_user_credits_from_verify = await self.
|
|
283
|
+
has_credits, current_user_credits_from_verify = await self.user_charging_service.verify_credits(
|
|
284
284
|
user_uid,
|
|
285
285
|
total_cost,
|
|
286
286
|
pre_fetched_user_credits=pre_fetched_credits
|
|
@@ -300,7 +300,7 @@ class ChargingProcessor:
|
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
# Charge the user for all paid items
|
|
303
|
-
charged, calculated_updated_credits = await self.
|
|
303
|
+
charged, calculated_updated_credits = await self.user_charging_service.charge_credits_transaction(
|
|
304
304
|
user_uid,
|
|
305
305
|
total_cost,
|
|
306
306
|
f"{operation_description} ({len(paid_items)} items, total cost: {total_cost})"
|
|
@@ -322,9 +322,9 @@ class ChargingProcessor:
|
|
|
322
322
|
|
|
323
323
|
except ValidationError as ve:
|
|
324
324
|
self.logger.error(f"Validation error during batch credit check for user {user_uid}: {str(ve)}")
|
|
325
|
-
if self.
|
|
325
|
+
if self.user_charging_service:
|
|
326
326
|
try:
|
|
327
|
-
_, current_user_credits_from_verify = await self.
|
|
327
|
+
_, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
|
|
328
328
|
updated_user_credits = current_user_credits_from_verify
|
|
329
329
|
except Exception: # pylint: disable=broad-except
|
|
330
330
|
pass
|
|
@@ -334,9 +334,9 @@ class ChargingProcessor:
|
|
|
334
334
|
except Exception as e:
|
|
335
335
|
self.logger.error(f"Unexpected error during batch credit check for user {user_uid}: {str(e)}", exc_info=True)
|
|
336
336
|
current_credits_on_error = None
|
|
337
|
-
if self.
|
|
337
|
+
if self.user_charging_service:
|
|
338
338
|
try:
|
|
339
|
-
_, current_credits_on_error = await self.
|
|
339
|
+
_, current_credits_on_error = await self.user_charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
|
|
340
340
|
updated_user_credits = current_credits_on_error
|
|
341
341
|
except Exception: # pylint: disable=broad-except
|
|
342
342
|
pass
|
|
@@ -10,28 +10,8 @@ This module contains all user-related services organized into specialized operat
|
|
|
10
10
|
- User-specific exceptions: Specialized exception classes for user operations
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from .
|
|
14
|
-
from .
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from .iam_management_operations import IAMManagementOperations
|
|
19
|
-
from .user_auth_operations import UserAuthOperations
|
|
20
|
-
from .user_holistic_operations import UserHolisticOperations
|
|
21
|
-
from .user_core_service import UserCoreService, UserTypeDefaultsDocument
|
|
22
|
-
|
|
23
|
-
__all__ = [
|
|
24
|
-
# Operation classes
|
|
25
|
-
'UserAccountOperations',
|
|
26
|
-
'SubscriptionManagementOperations',
|
|
27
|
-
'IAMManagementOperations',
|
|
28
|
-
'UserAuthOperations',
|
|
29
|
-
'UserHolisticOperations',
|
|
30
|
-
|
|
31
|
-
# Main orchestrating service
|
|
32
|
-
'UserCoreService',
|
|
33
|
-
|
|
34
|
-
# Supporting models
|
|
35
|
-
'SubscriptionPlanDocument',
|
|
36
|
-
'UserTypeDefaultsDocument'
|
|
37
|
-
]
|
|
13
|
+
from .user_subscription_operations import UsersubscriptionOperations
|
|
14
|
+
from .user_permissions_operations import UserpermissionsOperations
|
|
15
|
+
from .userauth_operations import UserauthOperations
|
|
16
|
+
from .user_multistep_operations import UsermultistepOperations
|
|
17
|
+
from .user_core_service import UserCoreService
|