ipulse-shared-core-ftredge 18.0.1__tar.gz → 19.0.1__tar.gz
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-18.0.1/src/ipulse_shared_core_ftredge.egg-info → ipulse_shared_core_ftredge-19.0.1}/PKG-INFO +1 -1
- ipulse_shared_core_ftredge-19.0.1/pyproject.toml +17 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/setup.py +1 -1
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/__init__.py +1 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/exceptions/__init__.py +47 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/exceptions/user_exceptions.py +219 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/models/__init__.py +0 -2
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/models/base_data_model.py +6 -6
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/models/user_auth.py +64 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/models/user_profile.py +41 -7
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/models/user_status.py +44 -138
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/monitoring/__init__.py +5 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/monitoring/microservmon.py +483 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/__init__.py +25 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/base/__init__.py +12 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/base/base_firestore_service.py +520 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/services/cache_aware_firestore_service.py +44 -8
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/services/charging_service.py +1 -1
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/user/__init__.py +37 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/user/iam_management_operations.py +326 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +384 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/user/user_account_operations.py +479 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/user/user_auth_operations.py +305 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/user/user_core_service.py +651 -0
- ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +436 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge.egg-info}/PKG-INFO +1 -1
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge.egg-info/SOURCES.txt +14 -6
- ipulse_shared_core_ftredge-19.0.1/tests/test_cache_aware_service.py +270 -0
- ipulse_shared_core_ftredge-18.0.1/pyproject.toml +0 -3
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/__init__.py +0 -12
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/models/organization_profile.py +0 -96
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/models/user_auth.py +0 -9
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/models/user_profile_update.py +0 -39
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/services/__init__.py +0 -18
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/services/base_firestore_service.py +0 -249
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/services/fastapiservicemon.py +0 -140
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/services/servicemon.py +0 -240
- ipulse_shared_core_ftredge-18.0.1/tests/test_cache_aware_service.py +0 -234
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/LICENCE +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/README.md +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/setup.cfg +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/cache/__init__.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/cache/shared_cache.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/dependencies/__init__.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/dependencies/auth_firebase_token_validation.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/dependencies/auth_protected_router.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/dependencies/firestore_client.py +0 -0
- ipulse_shared_core_ftredge-18.0.1/src/ipulse_shared_core_ftredge/services/base_service_exceptions.py → ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/exceptions/base_exceptions.py +1 -1
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/models/base_api_response.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/models/subscription.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/services/charging_processors.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/utils/__init__.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/utils/custom_json_encoder.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge/utils/json_encoder.py +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge.egg-info/dependency_links.txt +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge.egg-info/requires.txt +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/src/ipulse_shared_core_ftredge.egg-info/top_level.txt +0 -0
- {ipulse_shared_core_ftredge-18.0.1 → ipulse_shared_core_ftredge-19.0.1}/tests/test_shared_cache.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipulse_shared_core_ftredge
|
|
3
|
-
Version:
|
|
3
|
+
Version: 19.0.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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.pytest.ini_options]
|
|
6
|
+
asyncio_mode = "auto"
|
|
7
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
8
|
+
python_classes = ["Test*", "!TestModel"]
|
|
9
|
+
addopts = [
|
|
10
|
+
"-v",
|
|
11
|
+
"--tb=short",
|
|
12
|
+
"--strict-markers",
|
|
13
|
+
"--disable-warnings"
|
|
14
|
+
]
|
|
15
|
+
testpaths = ["tests"]
|
|
16
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
17
|
+
python_functions = ["test_*"]
|
|
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
|
|
|
3
3
|
|
|
4
4
|
setup(
|
|
5
5
|
name='ipulse_shared_core_ftredge',
|
|
6
|
-
version='
|
|
6
|
+
version='19.0.1',
|
|
7
7
|
package_dir={'': 'src'}, # Specify the source directory
|
|
8
8
|
packages=find_packages(where='src'), # Look for packages in 'src'
|
|
9
9
|
install_requires=[
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# pylint: disable=missing-module-docstring
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exception module for ipulse_shared_core_ftredge
|
|
3
|
+
|
|
4
|
+
This module centralizes all exceptions to prevent circular import dependencies.
|
|
5
|
+
All services import exceptions from here instead of from each other.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import all exceptions from submodules
|
|
9
|
+
from .base_exceptions import (
|
|
10
|
+
BaseServiceException,
|
|
11
|
+
ServiceError,
|
|
12
|
+
ValidationError,
|
|
13
|
+
ResourceNotFoundError,
|
|
14
|
+
AuthorizationError
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from .user_exceptions import (
|
|
18
|
+
UserCoreError,
|
|
19
|
+
UserCreationError,
|
|
20
|
+
UserDeletionError,
|
|
21
|
+
UserValidationError,
|
|
22
|
+
UserProfileError,
|
|
23
|
+
UserStatusError,
|
|
24
|
+
UserAuthError,
|
|
25
|
+
SubscriptionError,
|
|
26
|
+
IAMPermissionError
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
# Base exceptions
|
|
31
|
+
'BaseServiceException',
|
|
32
|
+
'ServiceError',
|
|
33
|
+
'ValidationError',
|
|
34
|
+
'ResourceNotFoundError',
|
|
35
|
+
'AuthorizationError',
|
|
36
|
+
|
|
37
|
+
# User-specific exceptions
|
|
38
|
+
'UserCoreError',
|
|
39
|
+
'UserCreationError',
|
|
40
|
+
'UserDeletionError',
|
|
41
|
+
'UserValidationError',
|
|
42
|
+
'UserProfileError',
|
|
43
|
+
'UserStatusError',
|
|
44
|
+
'UserAuthError',
|
|
45
|
+
'SubscriptionError',
|
|
46
|
+
'IAMPermissionError'
|
|
47
|
+
]
|
ipulse_shared_core_ftredge-19.0.1/src/ipulse_shared_core_ftredge/exceptions/user_exceptions.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Custom exceptions for UserCoreService operations"""
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
from .base_exceptions import BaseServiceException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UserCoreError(BaseServiceException):
|
|
7
|
+
"""Base exception for UserCore operations"""
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
detail: str,
|
|
11
|
+
user_uid: Optional[str] = None,
|
|
12
|
+
operation: Optional[str] = None,
|
|
13
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
14
|
+
original_error: Optional[Exception] = None
|
|
15
|
+
):
|
|
16
|
+
super().__init__(
|
|
17
|
+
status_code=500,
|
|
18
|
+
detail=detail,
|
|
19
|
+
resource_type="UserCore",
|
|
20
|
+
resource_id=user_uid,
|
|
21
|
+
additional_info=additional_info,
|
|
22
|
+
original_error=original_error
|
|
23
|
+
)
|
|
24
|
+
self.operation = operation
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UserProfileError(UserCoreError):
|
|
28
|
+
"""Exception for UserProfile operations"""
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
detail: str,
|
|
32
|
+
user_uid: Optional[str] = None,
|
|
33
|
+
operation: Optional[str] = None,
|
|
34
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
35
|
+
original_error: Optional[Exception] = None
|
|
36
|
+
):
|
|
37
|
+
super().__init__(
|
|
38
|
+
detail=detail,
|
|
39
|
+
user_uid=user_uid,
|
|
40
|
+
operation=operation,
|
|
41
|
+
additional_info=additional_info,
|
|
42
|
+
original_error=original_error
|
|
43
|
+
)
|
|
44
|
+
self.resource_type = "UserProfile"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class UserStatusError(UserCoreError):
|
|
48
|
+
"""Exception for UserStatus operations"""
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
detail: str,
|
|
52
|
+
user_uid: Optional[str] = None,
|
|
53
|
+
operation: Optional[str] = None,
|
|
54
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
55
|
+
original_error: Optional[Exception] = None
|
|
56
|
+
):
|
|
57
|
+
super().__init__(
|
|
58
|
+
detail=detail,
|
|
59
|
+
user_uid=user_uid,
|
|
60
|
+
operation=operation,
|
|
61
|
+
additional_info=additional_info,
|
|
62
|
+
original_error=original_error
|
|
63
|
+
)
|
|
64
|
+
self.resource_type = "UserStatus"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class UserAuthError(UserCoreError):
|
|
68
|
+
"""Exception for Firebase Auth operations"""
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
detail: str,
|
|
72
|
+
user_uid: Optional[str] = None,
|
|
73
|
+
operation: Optional[str] = None,
|
|
74
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
75
|
+
original_error: Optional[Exception] = None
|
|
76
|
+
):
|
|
77
|
+
super().__init__(
|
|
78
|
+
detail=detail,
|
|
79
|
+
user_uid=user_uid,
|
|
80
|
+
operation=operation,
|
|
81
|
+
additional_info=additional_info,
|
|
82
|
+
original_error=original_error
|
|
83
|
+
)
|
|
84
|
+
self.resource_type = "UserAuth"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class SubscriptionError(UserCoreError):
|
|
88
|
+
"""Exception for subscription operations"""
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
detail: str,
|
|
92
|
+
user_uid: Optional[str] = None,
|
|
93
|
+
plan_id: Optional[str] = None,
|
|
94
|
+
operation: Optional[str] = None,
|
|
95
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
96
|
+
original_error: Optional[Exception] = None
|
|
97
|
+
):
|
|
98
|
+
additional_info = additional_info or {}
|
|
99
|
+
if plan_id:
|
|
100
|
+
additional_info['plan_id'] = plan_id
|
|
101
|
+
|
|
102
|
+
super().__init__(
|
|
103
|
+
detail=detail,
|
|
104
|
+
user_uid=user_uid,
|
|
105
|
+
operation=operation,
|
|
106
|
+
additional_info=additional_info,
|
|
107
|
+
original_error=original_error
|
|
108
|
+
)
|
|
109
|
+
self.resource_type = "Subscription"
|
|
110
|
+
self.plan_id = plan_id
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class IAMPermissionError(UserCoreError):
|
|
114
|
+
"""Exception for IAM permission operations"""
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
detail: str,
|
|
118
|
+
user_uid: Optional[str] = None,
|
|
119
|
+
domain: Optional[str] = None,
|
|
120
|
+
permission: Optional[str] = None,
|
|
121
|
+
operation: Optional[str] = None,
|
|
122
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
123
|
+
original_error: Optional[Exception] = None
|
|
124
|
+
):
|
|
125
|
+
additional_info = additional_info or {}
|
|
126
|
+
if domain:
|
|
127
|
+
additional_info['domain'] = domain
|
|
128
|
+
if permission:
|
|
129
|
+
additional_info['permission'] = permission
|
|
130
|
+
|
|
131
|
+
super().__init__(
|
|
132
|
+
detail=detail,
|
|
133
|
+
user_uid=user_uid,
|
|
134
|
+
operation=operation,
|
|
135
|
+
additional_info=additional_info,
|
|
136
|
+
original_error=original_error
|
|
137
|
+
)
|
|
138
|
+
self.resource_type = "IAMPermission"
|
|
139
|
+
self.domain = domain
|
|
140
|
+
self.permission = permission
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class UserCreationError(UserCoreError):
|
|
144
|
+
"""Exception for user creation operations"""
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
detail: str,
|
|
148
|
+
email: Optional[str] = None,
|
|
149
|
+
user_uid: Optional[str] = None,
|
|
150
|
+
failed_component: Optional[str] = None,
|
|
151
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
152
|
+
original_error: Optional[Exception] = None
|
|
153
|
+
):
|
|
154
|
+
additional_info = additional_info or {}
|
|
155
|
+
if email:
|
|
156
|
+
additional_info['email'] = email
|
|
157
|
+
if failed_component:
|
|
158
|
+
additional_info['failed_component'] = failed_component
|
|
159
|
+
|
|
160
|
+
super().__init__(
|
|
161
|
+
detail=detail,
|
|
162
|
+
user_uid=user_uid,
|
|
163
|
+
operation="create_user",
|
|
164
|
+
additional_info=additional_info,
|
|
165
|
+
original_error=original_error
|
|
166
|
+
)
|
|
167
|
+
self.resource_type = "UserCreation"
|
|
168
|
+
self.email = email
|
|
169
|
+
self.failed_component = failed_component
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class UserDeletionError(UserCoreError):
|
|
173
|
+
"""Exception for user deletion operations"""
|
|
174
|
+
def __init__(
|
|
175
|
+
self,
|
|
176
|
+
detail: str,
|
|
177
|
+
user_uid: Optional[str] = None,
|
|
178
|
+
deletion_target: Optional[str] = None,
|
|
179
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
180
|
+
original_error: Optional[Exception] = None
|
|
181
|
+
):
|
|
182
|
+
additional_info = additional_info or {}
|
|
183
|
+
if deletion_target:
|
|
184
|
+
additional_info['deletion_target'] = deletion_target
|
|
185
|
+
|
|
186
|
+
super().__init__(
|
|
187
|
+
detail=detail,
|
|
188
|
+
user_uid=user_uid,
|
|
189
|
+
operation="delete_user",
|
|
190
|
+
additional_info=additional_info,
|
|
191
|
+
original_error=original_error
|
|
192
|
+
)
|
|
193
|
+
self.resource_type = "UserDeletion"
|
|
194
|
+
self.deletion_target = deletion_target
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class UserValidationError(UserCoreError):
|
|
198
|
+
"""Exception for user data validation"""
|
|
199
|
+
def __init__(
|
|
200
|
+
self,
|
|
201
|
+
detail: str,
|
|
202
|
+
user_uid: Optional[str] = None,
|
|
203
|
+
validation_field: Optional[str] = None,
|
|
204
|
+
additional_info: Optional[Dict[str, Any]] = None,
|
|
205
|
+
original_error: Optional[Exception] = None
|
|
206
|
+
):
|
|
207
|
+
additional_info = additional_info or {}
|
|
208
|
+
if validation_field:
|
|
209
|
+
additional_info['validation_field'] = validation_field
|
|
210
|
+
|
|
211
|
+
super().__init__(
|
|
212
|
+
detail=detail,
|
|
213
|
+
user_uid=user_uid,
|
|
214
|
+
operation="validate_user_core_data",
|
|
215
|
+
additional_info=additional_info,
|
|
216
|
+
original_error=original_error
|
|
217
|
+
)
|
|
218
|
+
self.resource_type = "UserValidation"
|
|
219
|
+
self.validation_field = validation_field
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from .user_profile import UserProfile
|
|
2
2
|
from .subscription import Subscription
|
|
3
3
|
from .user_status import UserStatus, IAMUnitRefAssignment
|
|
4
|
-
from .user_profile_update import UserProfileUpdate
|
|
5
4
|
from .user_auth import UserAuth
|
|
6
|
-
from .organization_profile import OrganizationProfile
|
|
7
5
|
from .base_api_response import BaseAPIResponse , CustomJSONResponse, CreditChargeableAPIResponse, UserCreditBalance, UpdatedUserCreditInfo
|
|
8
6
|
from .base_data_model import BaseDataModel
|
|
9
7
|
|
|
@@ -7,7 +7,7 @@ import dateutil.parser
|
|
|
7
7
|
|
|
8
8
|
class BaseDataModel(BaseModel):
|
|
9
9
|
"""Base model with common fields and configuration"""
|
|
10
|
-
model_config = ConfigDict(frozen=
|
|
10
|
+
model_config = ConfigDict(frozen=False, extra="forbid")
|
|
11
11
|
|
|
12
12
|
# Required class variables that must be defined in subclasses
|
|
13
13
|
VERSION: ClassVar[float]
|
|
@@ -18,13 +18,13 @@ class BaseDataModel(BaseModel):
|
|
|
18
18
|
schema_version: float = Field(
|
|
19
19
|
..., # Make this required
|
|
20
20
|
description="Version of this Class == version of DB Schema",
|
|
21
|
-
frozen=True
|
|
21
|
+
frozen=True # Keep schema version frozen for data integrity
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
# Audit fields
|
|
25
|
-
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)
|
|
26
|
-
created_by: str = Field(
|
|
27
|
-
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)
|
|
24
|
+
# Audit fields - now mutable for updates
|
|
25
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
26
|
+
created_by: str = Field(...)
|
|
27
|
+
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
28
28
|
updated_by: str = Field(...)
|
|
29
29
|
|
|
30
30
|
@classmethod
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any, List
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pydantic import BaseModel, Field, EmailStr, ConfigDict, field_validator
|
|
4
|
+
|
|
5
|
+
class UserAuth(BaseModel):
|
|
6
|
+
"""Comprehensive authentication model for user credentials and auth operations"""
|
|
7
|
+
model_config = ConfigDict(extra="forbid")
|
|
8
|
+
|
|
9
|
+
# Core authentication fields
|
|
10
|
+
email: EmailStr = Field(..., description="User's email address")
|
|
11
|
+
password: Optional[str] = Field(None, min_length=6, description="User's password (for creation/update only)")
|
|
12
|
+
|
|
13
|
+
# Firebase Auth specific fields
|
|
14
|
+
firebase_uid: Optional[str] = Field(None, description="Firebase Auth UID")
|
|
15
|
+
provider_id: str = Field(default="password", description="Authentication provider ID")
|
|
16
|
+
email_verified: bool = Field(default=False, description="Whether email is verified")
|
|
17
|
+
disabled: bool = Field(default=False, description="Whether user account is disabled")
|
|
18
|
+
|
|
19
|
+
# Multi-factor authentication
|
|
20
|
+
mfa_enabled: bool = Field(default=False, description="Whether MFA is enabled")
|
|
21
|
+
phone_number: Optional[str] = Field(None, description="Phone number for SMS MFA")
|
|
22
|
+
|
|
23
|
+
# Custom claims and metadata
|
|
24
|
+
custom_claims: Dict[str, Any] = Field(default_factory=dict, description="Firebase custom claims")
|
|
25
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional authentication metadata")
|
|
26
|
+
|
|
27
|
+
# Provider data
|
|
28
|
+
provider_data: List[Dict[str, Any]] = Field(default_factory=list, description="Provider-specific data")
|
|
29
|
+
|
|
30
|
+
# Account management
|
|
31
|
+
created_at: Optional[datetime] = Field(None, description="Account creation timestamp")
|
|
32
|
+
last_sign_in: Optional[datetime] = Field(None, description="Last sign-in timestamp")
|
|
33
|
+
last_refresh: Optional[datetime] = Field(None, description="Last token refresh timestamp")
|
|
34
|
+
|
|
35
|
+
# Password management
|
|
36
|
+
password_hash: Optional[str] = Field(None, description="Password hash (internal use only)")
|
|
37
|
+
password_salt: Optional[str] = Field(None, description="Password salt (internal use only)")
|
|
38
|
+
valid_since: Optional[datetime] = Field(None, description="Timestamp since when tokens are valid")
|
|
39
|
+
|
|
40
|
+
@field_validator('phone_number')
|
|
41
|
+
@classmethod
|
|
42
|
+
def validate_phone_number(cls, v: Optional[str]) -> Optional[str]:
|
|
43
|
+
"""Validate phone number format if provided"""
|
|
44
|
+
if v is None:
|
|
45
|
+
return v
|
|
46
|
+
# Basic E.164 format validation
|
|
47
|
+
if not v.startswith('+') or not v[1:].isdigit() or len(v) < 8 or len(v) > 16:
|
|
48
|
+
raise ValueError('Phone number must be in E.164 format (+1234567890)')
|
|
49
|
+
return v
|
|
50
|
+
|
|
51
|
+
@field_validator('custom_claims')
|
|
52
|
+
@classmethod
|
|
53
|
+
def validate_custom_claims(cls, v: Dict[str, Any]) -> Dict[str, Any]:
|
|
54
|
+
"""Validate custom claims don't contain reserved Firebase claims"""
|
|
55
|
+
reserved_claims = {
|
|
56
|
+
'iss', 'aud', 'auth_time', 'user_id', 'sub', 'iat', 'exp', 'email',
|
|
57
|
+
'email_verified', 'phone_number', 'name', 'picture', 'firebase'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for claim in v.keys():
|
|
61
|
+
if claim in reserved_claims:
|
|
62
|
+
raise ValueError(f'Custom claim "{claim}" is reserved by Firebase')
|
|
63
|
+
|
|
64
|
+
return v
|
|
@@ -4,6 +4,7 @@ from typing import Set, Optional, ClassVar, Dict, Any, List
|
|
|
4
4
|
from pydantic import EmailStr, Field, ConfigDict, field_validator, model_validator, computed_field
|
|
5
5
|
from ipulse_shared_base_ftredge import Layer, Module, list_as_lower_strings, Subject
|
|
6
6
|
from .base_data_model import BaseDataModel
|
|
7
|
+
import re # Add re import
|
|
7
8
|
|
|
8
9
|
# ORIGINAL AUTHOR ="Russlan Ramdowar;russlan@ftredge.com"
|
|
9
10
|
# CLASS_ORGIN_DATE=datetime(2024, 2, 12, 20, 5)
|
|
@@ -27,7 +28,7 @@ class UserProfile(BaseDataModel):
|
|
|
27
28
|
)
|
|
28
29
|
|
|
29
30
|
id: str = Field(
|
|
30
|
-
|
|
31
|
+
default="", # Will be auto-generated from user_uid if not provided
|
|
31
32
|
description=f"User Profile ID, format: {OBJ_REF}_user_uid"
|
|
32
33
|
)
|
|
33
34
|
|
|
@@ -42,7 +43,7 @@ class UserProfile(BaseDataModel):
|
|
|
42
43
|
description="Primary user type (e.g., customer, internal, admin, superadmin)"
|
|
43
44
|
)
|
|
44
45
|
|
|
45
|
-
# Renamed
|
|
46
|
+
# Renamed usertypes to secondary_usertypes
|
|
46
47
|
secondary_usertypes: List[str] = Field(
|
|
47
48
|
default_factory=list,
|
|
48
49
|
description="List of secondary user types"
|
|
@@ -71,11 +72,11 @@ class UserProfile(BaseDataModel):
|
|
|
71
72
|
)
|
|
72
73
|
|
|
73
74
|
# User-editable fields
|
|
74
|
-
username:
|
|
75
|
-
default=
|
|
76
|
-
max_length=
|
|
77
|
-
pattern="^[a-zA-Z0-9_
|
|
78
|
-
description="Username (public display name)"
|
|
75
|
+
username: str = Field(
|
|
76
|
+
default="", # Made optional with empty default - will be auto-generated
|
|
77
|
+
max_length=12, # Updated to 12 characters
|
|
78
|
+
pattern="^[a-zA-Z0-9_]+$", # Allow underscore
|
|
79
|
+
description="Username (public display name), max 12 chars, alphanumeric and underscore. Auto-generated from email if not provided."
|
|
79
80
|
)
|
|
80
81
|
dob: Optional[date] = Field(
|
|
81
82
|
default=None,
|
|
@@ -122,4 +123,37 @@ class UserProfile(BaseDataModel):
|
|
|
122
123
|
if 'user_uid' in data and data['user_uid']:
|
|
123
124
|
data['id'] = f"{cls.OBJ_REF}_{data['user_uid']}"
|
|
124
125
|
|
|
126
|
+
return data
|
|
127
|
+
|
|
128
|
+
@model_validator(mode='before')
|
|
129
|
+
@classmethod
|
|
130
|
+
def populate_username(cls, data: Any) -> Any:
|
|
131
|
+
"""
|
|
132
|
+
Generates or sanitizes the username.
|
|
133
|
+
If username is provided and non-empty, it's sanitized and truncated to 10 chars.
|
|
134
|
+
If not provided or empty, it's generated from the email (part before '@'),
|
|
135
|
+
sanitized, and truncated to 10 chars.
|
|
136
|
+
If no email is available, generates a default username.
|
|
137
|
+
"""
|
|
138
|
+
if not isinstance(data, dict):
|
|
139
|
+
# Not a dict, perhaps an instance already, skip
|
|
140
|
+
return data
|
|
141
|
+
|
|
142
|
+
email = data.get('email')
|
|
143
|
+
username = data.get('username')
|
|
144
|
+
|
|
145
|
+
# Check if username is provided and non-empty
|
|
146
|
+
if username and isinstance(username, str) and username.strip():
|
|
147
|
+
# Sanitize and truncate provided username
|
|
148
|
+
sanitized_username = re.sub(r'[^a-zA-Z0-9_]', '', username)
|
|
149
|
+
data['username'] = sanitized_username[:12] if sanitized_username else "user"
|
|
150
|
+
elif email and isinstance(email, str):
|
|
151
|
+
# Generate from email
|
|
152
|
+
email_prefix = email.split('@')[0]
|
|
153
|
+
sanitized_prefix = re.sub(r'[^a-zA-Z0-9_]', '', email_prefix)
|
|
154
|
+
data['username'] = sanitized_prefix[:12] if sanitized_prefix else "user"
|
|
155
|
+
else:
|
|
156
|
+
# Fallback if no email or username provided
|
|
157
|
+
data['username'] = "user"
|
|
158
|
+
|
|
125
159
|
return data
|