geek-cafe-saas-sdk 0.6.0__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 geek-cafe-saas-sdk might be problematic. Click here for more details.
- geek_cafe_saas_sdk/__init__.py +9 -0
- geek_cafe_saas_sdk/core/__init__.py +11 -0
- geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
- geek_cafe_saas_sdk/core/error_codes.py +132 -0
- geek_cafe_saas_sdk/core/service_errors.py +19 -0
- geek_cafe_saas_sdk/core/service_result.py +121 -0
- geek_cafe_saas_sdk/decorators/__init__.py +64 -0
- geek_cafe_saas_sdk/decorators/auth.py +373 -0
- geek_cafe_saas_sdk/decorators/core.py +358 -0
- geek_cafe_saas_sdk/domains/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
- geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
- geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
- geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
- geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
- geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
- geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
- geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
- geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
- geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
- geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
- geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
- geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
- geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
- geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
- geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
- geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
- geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
- geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
- geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
- geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
- geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
- geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
- geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
- geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
- geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
- geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
- geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
- geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
- geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
- geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
- geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
- geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
- geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
- geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
- geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
- geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
- geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
- geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
- geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
- geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
- geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
- geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
- geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
- geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
- geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
- geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
- geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
- geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
- geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
- geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
- geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
- geek_cafe_saas_sdk/middleware/__init__.py +36 -0
- geek_cafe_saas_sdk/middleware/auth.py +85 -0
- geek_cafe_saas_sdk/middleware/authorization.py +523 -0
- geek_cafe_saas_sdk/middleware/cors.py +63 -0
- geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
- geek_cafe_saas_sdk/middleware/validation.py +80 -0
- geek_cafe_saas_sdk/models/__init__.py +20 -0
- geek_cafe_saas_sdk/models/base_model.py +233 -0
- geek_cafe_saas_sdk/services/__init__.py +18 -0
- geek_cafe_saas_sdk/services/database_service.py +441 -0
- geek_cafe_saas_sdk/utilities/__init__.py +88 -0
- geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
- geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
- geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
- geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
- geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
- geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
- geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
- geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
- geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
- geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
- geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
- geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
- geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
- geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
- geek_cafe_saas_sdk/utilities/response.py +209 -0
- geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/licenses/LICENSE +47 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# src/geek_cafe_saas_sdk/lambda_handlers/users/delete/app.py
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
from geek_cafe_saas_sdk.services import UserService
|
|
6
|
+
from geek_cafe_saas_sdk.lambda_handlers import ServicePool
|
|
7
|
+
from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response, success_response
|
|
8
|
+
from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
|
|
9
|
+
|
|
10
|
+
user_service_pool = ServicePool(UserService)
|
|
11
|
+
|
|
12
|
+
def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
|
|
13
|
+
"""
|
|
14
|
+
Lambda handler for deleting a user by its ID.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
event: API Gateway event
|
|
18
|
+
context: Lambda context
|
|
19
|
+
injected_service: Optional UserService for testing
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
user_service = injected_service if injected_service else user_service_pool.get()
|
|
23
|
+
user_id = LambdaEventUtility.get_authenticated_user_id(event)
|
|
24
|
+
tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
|
|
25
|
+
resource_id = LambdaEventUtility.get_value_from_path_parameters(event, 'id')
|
|
26
|
+
|
|
27
|
+
if not resource_id:
|
|
28
|
+
return error_response("User ID is required in the path.", "VALIDATION_ERROR", 400)
|
|
29
|
+
|
|
30
|
+
result = user_service.delete(
|
|
31
|
+
resource_id=resource_id,
|
|
32
|
+
tenant_id=tenant_id,
|
|
33
|
+
user_id=user_id
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if result.success:
|
|
37
|
+
return success_response(message="User deleted successfully", status_code=204)
|
|
38
|
+
return service_result_to_response(result)
|
|
39
|
+
|
|
40
|
+
except Exception as e:
|
|
41
|
+
return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# src/geek_cafe_saas_sdk/lambda_handlers/users/get/app.py
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
from geek_cafe_saas_sdk.services import UserService
|
|
6
|
+
from geek_cafe_saas_sdk.lambda_handlers import ServicePool
|
|
7
|
+
from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response
|
|
8
|
+
from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
|
|
9
|
+
|
|
10
|
+
user_service_pool = ServicePool(UserService)
|
|
11
|
+
|
|
12
|
+
def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
|
|
13
|
+
"""
|
|
14
|
+
Lambda handler for retrieving a single user by its ID.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
event: API Gateway event
|
|
18
|
+
context: Lambda context
|
|
19
|
+
injected_service: Optional UserService for testing
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
user_service = injected_service if injected_service else user_service_pool.get()
|
|
23
|
+
user_id = LambdaEventUtility.get_authenticated_user_id(event)
|
|
24
|
+
tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
|
|
25
|
+
resource_id = LambdaEventUtility.get_value_from_path_parameters(event, 'id')
|
|
26
|
+
|
|
27
|
+
if not resource_id:
|
|
28
|
+
return error_response("User ID is required in the path.", "VALIDATION_ERROR", 400)
|
|
29
|
+
|
|
30
|
+
result = user_service.get_by_id(
|
|
31
|
+
resource_id=resource_id,
|
|
32
|
+
tenant_id=tenant_id,
|
|
33
|
+
user_id=user_id
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return service_result_to_response(result)
|
|
37
|
+
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# src/geek_cafe_saas_sdk/lambda_handlers/users/list/app.py
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
from geek_cafe_saas_sdk.services import UserService
|
|
6
|
+
from geek_cafe_saas_sdk.lambda_handlers import ServicePool
|
|
7
|
+
from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response
|
|
8
|
+
from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
|
|
9
|
+
|
|
10
|
+
user_service_pool = ServicePool(UserService)
|
|
11
|
+
|
|
12
|
+
def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
|
|
13
|
+
"""
|
|
14
|
+
Lambda handler for listing users with optional filters.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
event: API Gateway event
|
|
18
|
+
context: Lambda context
|
|
19
|
+
injected_service: Optional UserService for testing
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
user_service = injected_service if injected_service else user_service_pool.get()
|
|
23
|
+
user_id = LambdaEventUtility.get_authenticated_user_id(event)
|
|
24
|
+
tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
|
|
25
|
+
query_params = event.get('queryStringParameters', {}) or {}
|
|
26
|
+
|
|
27
|
+
# UserService has list_by_tenant method
|
|
28
|
+
result = user_service.list_by_tenant(
|
|
29
|
+
tenant_id=tenant_id,
|
|
30
|
+
user_id=user_id
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return service_result_to_response(result)
|
|
34
|
+
|
|
35
|
+
except Exception as e:
|
|
36
|
+
return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# src/geek_cafe_saas_sdk/lambda_handlers/users/update/app.py
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
|
|
6
|
+
from geek_cafe_saas_sdk.services import UserService
|
|
7
|
+
from geek_cafe_saas_sdk.lambda_handlers import ServicePool
|
|
8
|
+
from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response
|
|
9
|
+
from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
|
|
10
|
+
|
|
11
|
+
user_service_pool = ServicePool(UserService)
|
|
12
|
+
|
|
13
|
+
def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Lambda handler for updating an existing user.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
event: API Gateway event
|
|
19
|
+
context: Lambda context
|
|
20
|
+
injected_service: Optional UserService for testing
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
user_service = injected_service if injected_service else user_service_pool.get()
|
|
24
|
+
body = LambdaEventUtility.get_body_from_event(event)
|
|
25
|
+
user_id = LambdaEventUtility.get_authenticated_user_id(event)
|
|
26
|
+
tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
|
|
27
|
+
resource_id = LambdaEventUtility.get_value_from_path_parameters(event, 'id')
|
|
28
|
+
|
|
29
|
+
if not resource_id:
|
|
30
|
+
return error_response("User ID is required in the path.", "VALIDATION_ERROR", 400)
|
|
31
|
+
|
|
32
|
+
result = user_service.update(
|
|
33
|
+
resource_id=resource_id,
|
|
34
|
+
tenant_id=tenant_id,
|
|
35
|
+
user_id=user_id,
|
|
36
|
+
updates=body
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return service_result_to_response(result)
|
|
40
|
+
|
|
41
|
+
except json.JSONDecodeError:
|
|
42
|
+
return error_response("Invalid JSON in request body.", "VALIDATION_ERROR", 400)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024-2025 Geek Cafe, LLC
|
|
3
|
+
MIT License. See Project Root for the license information.
|
|
4
|
+
|
|
5
|
+
Permission model for fine-grained access control.
|
|
6
|
+
Supports extensible permission definitions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
|
|
11
|
+
from geek_cafe_saas_sdk.models.base_model import BaseModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Permission(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Permission definition model.
|
|
17
|
+
|
|
18
|
+
Defines individual permissions that can be granted to roles or users.
|
|
19
|
+
Extensible - applications can register custom permissions.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
- events:read, events:write, events:delete
|
|
23
|
+
- chat:send_message, chat:manage_channel
|
|
24
|
+
- analytics:view_dashboard
|
|
25
|
+
|
|
26
|
+
Access Patterns:
|
|
27
|
+
- Get permission by code (primary key)
|
|
28
|
+
- List all permissions (scan/query all)
|
|
29
|
+
- List permissions by category (GSI1)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__()
|
|
34
|
+
|
|
35
|
+
# Core fields
|
|
36
|
+
self._code: str | None = None # Unique code: "events:read"
|
|
37
|
+
self._name: str | None = None # Display name: "Read Events"
|
|
38
|
+
self._description: str | None = None
|
|
39
|
+
self._category: str | None = None # "events", "chat", "analytics", etc.
|
|
40
|
+
|
|
41
|
+
# Metadata
|
|
42
|
+
self._is_system: bool = True # System permissions can't be deleted
|
|
43
|
+
self._metadata: Dict[str, Any] = {}
|
|
44
|
+
|
|
45
|
+
self._setup_indexes()
|
|
46
|
+
|
|
47
|
+
def _setup_indexes(self):
|
|
48
|
+
"""Setup DynamoDB indexes for permission queries."""
|
|
49
|
+
|
|
50
|
+
# Primary index: permission by code
|
|
51
|
+
primary: DynamoDBIndex = DynamoDBIndex()
|
|
52
|
+
primary.name = "primary"
|
|
53
|
+
primary.partition_key.attribute_name = "pk"
|
|
54
|
+
primary.partition_key.value = lambda: DynamoDBKey.build_key(
|
|
55
|
+
("permission", self.code)
|
|
56
|
+
)
|
|
57
|
+
primary.sort_key.attribute_name = "sk"
|
|
58
|
+
primary.sort_key.value = lambda: DynamoDBKey.build_key(
|
|
59
|
+
("permission", self.code)
|
|
60
|
+
)
|
|
61
|
+
self.indexes.add_primary(primary)
|
|
62
|
+
|
|
63
|
+
# GSI1: Permissions by category
|
|
64
|
+
gsi: DynamoDBIndex = DynamoDBIndex()
|
|
65
|
+
gsi.name = "gsi1"
|
|
66
|
+
gsi.partition_key.attribute_name = f"{gsi.name}_pk"
|
|
67
|
+
gsi.partition_key.value = lambda: DynamoDBKey.build_key(
|
|
68
|
+
("permission_category", self.category or "uncategorized")
|
|
69
|
+
)
|
|
70
|
+
gsi.sort_key.attribute_name = f"{gsi.name}_sk"
|
|
71
|
+
gsi.sort_key.value = lambda: DynamoDBKey.build_key(
|
|
72
|
+
("code", self.code)
|
|
73
|
+
)
|
|
74
|
+
self.indexes.add_secondary(gsi)
|
|
75
|
+
|
|
76
|
+
# Code
|
|
77
|
+
@property
|
|
78
|
+
def code(self) -> str | None:
|
|
79
|
+
"""Permission code (e.g., 'events:read')."""
|
|
80
|
+
return self._code
|
|
81
|
+
|
|
82
|
+
@code.setter
|
|
83
|
+
def code(self, value: str | None):
|
|
84
|
+
self._code = value
|
|
85
|
+
|
|
86
|
+
# Name
|
|
87
|
+
@property
|
|
88
|
+
def name(self) -> str | None:
|
|
89
|
+
"""Display name."""
|
|
90
|
+
return self._name
|
|
91
|
+
|
|
92
|
+
@name.setter
|
|
93
|
+
def name(self, value: str | None):
|
|
94
|
+
self._name = value
|
|
95
|
+
|
|
96
|
+
# Description
|
|
97
|
+
@property
|
|
98
|
+
def description(self) -> str | None:
|
|
99
|
+
"""Permission description."""
|
|
100
|
+
return self._description
|
|
101
|
+
|
|
102
|
+
@description.setter
|
|
103
|
+
def description(self, value: str | None):
|
|
104
|
+
self._description = value
|
|
105
|
+
|
|
106
|
+
# Category
|
|
107
|
+
@property
|
|
108
|
+
def category(self) -> str | None:
|
|
109
|
+
"""Permission category (e.g., 'events', 'chat')."""
|
|
110
|
+
return self._category
|
|
111
|
+
|
|
112
|
+
@category.setter
|
|
113
|
+
def category(self, value: str | None):
|
|
114
|
+
self._category = value
|
|
115
|
+
|
|
116
|
+
# Is System
|
|
117
|
+
@property
|
|
118
|
+
def is_system(self) -> bool:
|
|
119
|
+
"""Whether this is a system permission (cannot be deleted)."""
|
|
120
|
+
return self._is_system
|
|
121
|
+
|
|
122
|
+
@is_system.setter
|
|
123
|
+
def is_system(self, value: bool):
|
|
124
|
+
self._is_system = value
|
|
125
|
+
|
|
126
|
+
# Metadata
|
|
127
|
+
@property
|
|
128
|
+
def metadata(self) -> Dict[str, Any]:
|
|
129
|
+
"""Additional metadata."""
|
|
130
|
+
return self._metadata
|
|
131
|
+
|
|
132
|
+
@metadata.setter
|
|
133
|
+
def metadata(self, value: Dict[str, Any]):
|
|
134
|
+
self._metadata = value if value else {}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024-2025 Geek Cafe, LLC
|
|
3
|
+
MIT License. See Project Root for the license information.
|
|
4
|
+
|
|
5
|
+
ResourcePermission model for ABAC (Attribute-Based Access Control).
|
|
6
|
+
Grants specific permissions to users on specific resources.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import List, Dict, Any
|
|
10
|
+
from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
|
|
11
|
+
from geek_cafe_saas_sdk.models.base_model import BaseModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ResourcePermission(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Resource-level permission grant.
|
|
17
|
+
|
|
18
|
+
Allows granting specific permissions to users on specific resources.
|
|
19
|
+
Supports resource sharing and delegation.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
- Grant "events:write" to user-456 on event-789
|
|
23
|
+
- Grant "chat:admin" to user-123 on channel-abc
|
|
24
|
+
- Grant "analytics:read" to user-999 on tenant-def
|
|
25
|
+
|
|
26
|
+
Access Patterns:
|
|
27
|
+
- Get grants for user on resource (GSI1: user + resource)
|
|
28
|
+
- List all user's grants (GSI2: user only)
|
|
29
|
+
- List all grants on resource (GSI3: resource only)
|
|
30
|
+
- Check specific grant (primary key)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
super().__init__()
|
|
35
|
+
|
|
36
|
+
# Who has access
|
|
37
|
+
self._user_id: str | None = None
|
|
38
|
+
self._tenant_id: str | None = None
|
|
39
|
+
|
|
40
|
+
# What they can access
|
|
41
|
+
self._resource_type: str | None = None # "event", "chat_channel", "group", etc.
|
|
42
|
+
self._resource_id: str | None = None
|
|
43
|
+
|
|
44
|
+
# What they can do
|
|
45
|
+
self._permissions: List[str] = [] # ["read", "write", "delete"]
|
|
46
|
+
|
|
47
|
+
# Context
|
|
48
|
+
self._granted_by: str | None = None # User ID who granted this
|
|
49
|
+
self._granted_at: int | None = None # UTC timestamp
|
|
50
|
+
self._expires_at: int | None = None # Optional expiration
|
|
51
|
+
self._reason: str | None = None # Why granted
|
|
52
|
+
|
|
53
|
+
# Metadata
|
|
54
|
+
self._metadata: Dict[str, Any] = {}
|
|
55
|
+
|
|
56
|
+
self._setup_indexes()
|
|
57
|
+
|
|
58
|
+
def _setup_indexes(self):
|
|
59
|
+
"""Setup DynamoDB indexes for resource permission queries."""
|
|
60
|
+
|
|
61
|
+
# Primary index: specific grant
|
|
62
|
+
primary: DynamoDBIndex = DynamoDBIndex()
|
|
63
|
+
primary.name = "primary"
|
|
64
|
+
primary.partition_key.attribute_name = "pk"
|
|
65
|
+
primary.partition_key.value = lambda: DynamoDBKey.build_key(
|
|
66
|
+
("user", self.user_id),
|
|
67
|
+
("resource", self.resource_type),
|
|
68
|
+
("resource_id", self.resource_id)
|
|
69
|
+
)
|
|
70
|
+
primary.sort_key.attribute_name = "sk"
|
|
71
|
+
primary.sort_key.value = lambda: DynamoDBKey.build_key(
|
|
72
|
+
("grant", self.id)
|
|
73
|
+
)
|
|
74
|
+
self.indexes.add_primary(primary)
|
|
75
|
+
|
|
76
|
+
# GSI1: User's grants on a specific resource
|
|
77
|
+
gsi: DynamoDBIndex = DynamoDBIndex()
|
|
78
|
+
gsi.name = "gsi1"
|
|
79
|
+
gsi.partition_key.attribute_name = f"{gsi.name}_pk"
|
|
80
|
+
gsi.partition_key.value = lambda: DynamoDBKey.build_key(
|
|
81
|
+
("user", self.user_id)
|
|
82
|
+
)
|
|
83
|
+
gsi.sort_key.attribute_name = f"{gsi.name}_sk"
|
|
84
|
+
gsi.sort_key.value = lambda: DynamoDBKey.build_key(
|
|
85
|
+
("resource", self.resource_type),
|
|
86
|
+
("resource_id", self.resource_id)
|
|
87
|
+
)
|
|
88
|
+
self.indexes.add_secondary(gsi)
|
|
89
|
+
|
|
90
|
+
# GSI2: All grants for a user (across all resources)
|
|
91
|
+
gsi: DynamoDBIndex = DynamoDBIndex()
|
|
92
|
+
gsi.name = "gsi2"
|
|
93
|
+
gsi.partition_key.attribute_name = f"{gsi.name}_pk"
|
|
94
|
+
gsi.partition_key.value = lambda: DynamoDBKey.build_key(
|
|
95
|
+
("user_grants", self.user_id)
|
|
96
|
+
)
|
|
97
|
+
gsi.sort_key.attribute_name = f"{gsi.name}_sk"
|
|
98
|
+
gsi.sort_key.value = lambda: DynamoDBKey.build_key(
|
|
99
|
+
("resource", self.resource_type),
|
|
100
|
+
("resource_id", self.resource_id),
|
|
101
|
+
("grant", self.id)
|
|
102
|
+
)
|
|
103
|
+
self.indexes.add_secondary(gsi)
|
|
104
|
+
|
|
105
|
+
# GSI3: All grants on a resource (who has access)
|
|
106
|
+
gsi: DynamoDBIndex = DynamoDBIndex()
|
|
107
|
+
gsi.name = "gsi3"
|
|
108
|
+
gsi.partition_key.attribute_name = f"{gsi.name}_pk"
|
|
109
|
+
gsi.partition_key.value = lambda: DynamoDBKey.build_key(
|
|
110
|
+
("resource", self.resource_type),
|
|
111
|
+
("resource_id", self.resource_id)
|
|
112
|
+
)
|
|
113
|
+
gsi.sort_key.attribute_name = f"{gsi.name}_sk"
|
|
114
|
+
gsi.sort_key.value = lambda: DynamoDBKey.build_key(
|
|
115
|
+
("user", self.user_id)
|
|
116
|
+
)
|
|
117
|
+
self.indexes.add_secondary(gsi)
|
|
118
|
+
|
|
119
|
+
# GSI4: Grants by tenant (for admin view)
|
|
120
|
+
gsi: DynamoDBIndex = DynamoDBIndex()
|
|
121
|
+
gsi.name = "gsi4"
|
|
122
|
+
gsi.partition_key.attribute_name = f"{gsi.name}_pk"
|
|
123
|
+
gsi.partition_key.value = lambda: DynamoDBKey.build_key(
|
|
124
|
+
("tenant", self.tenant_id)
|
|
125
|
+
)
|
|
126
|
+
gsi.sort_key.attribute_name = f"{gsi.name}_sk"
|
|
127
|
+
gsi.sort_key.value = lambda: DynamoDBKey.build_key(
|
|
128
|
+
("resource", self.resource_type),
|
|
129
|
+
("resource_id", self.resource_id),
|
|
130
|
+
("user", self.user_id)
|
|
131
|
+
)
|
|
132
|
+
self.indexes.add_secondary(gsi)
|
|
133
|
+
|
|
134
|
+
# User ID
|
|
135
|
+
@property
|
|
136
|
+
def user_id(self) -> str | None:
|
|
137
|
+
"""User being granted access."""
|
|
138
|
+
return self._user_id
|
|
139
|
+
|
|
140
|
+
@user_id.setter
|
|
141
|
+
def user_id(self, value: str | None):
|
|
142
|
+
self._user_id = value
|
|
143
|
+
|
|
144
|
+
# Tenant ID
|
|
145
|
+
@property
|
|
146
|
+
def tenant_id(self) -> str | None:
|
|
147
|
+
"""Tenant context for the grant."""
|
|
148
|
+
return self._tenant_id
|
|
149
|
+
|
|
150
|
+
@tenant_id.setter
|
|
151
|
+
def tenant_id(self, value: str | None):
|
|
152
|
+
self._tenant_id = value
|
|
153
|
+
|
|
154
|
+
# Resource Type
|
|
155
|
+
@property
|
|
156
|
+
def resource_type(self) -> str | None:
|
|
157
|
+
"""Type of resource (e.g., 'event', 'chat_channel')."""
|
|
158
|
+
return self._resource_type
|
|
159
|
+
|
|
160
|
+
@resource_type.setter
|
|
161
|
+
def resource_type(self, value: str | None):
|
|
162
|
+
self._resource_type = value
|
|
163
|
+
|
|
164
|
+
# Resource ID
|
|
165
|
+
@property
|
|
166
|
+
def resource_id(self) -> str | None:
|
|
167
|
+
"""ID of the specific resource."""
|
|
168
|
+
return self._resource_id
|
|
169
|
+
|
|
170
|
+
@resource_id.setter
|
|
171
|
+
def resource_id(self, value: str | None):
|
|
172
|
+
self._resource_id = value
|
|
173
|
+
|
|
174
|
+
# Permissions
|
|
175
|
+
@property
|
|
176
|
+
def permissions(self) -> List[str]:
|
|
177
|
+
"""Permissions granted (e.g., ['read', 'write'])."""
|
|
178
|
+
return self._permissions
|
|
179
|
+
|
|
180
|
+
@permissions.setter
|
|
181
|
+
def permissions(self, value: List[str]):
|
|
182
|
+
self._permissions = value if value else []
|
|
183
|
+
|
|
184
|
+
# Granted By
|
|
185
|
+
@property
|
|
186
|
+
def granted_by(self) -> str | None:
|
|
187
|
+
"""User ID who granted this permission."""
|
|
188
|
+
return self._granted_by
|
|
189
|
+
|
|
190
|
+
@granted_by.setter
|
|
191
|
+
def granted_by(self, value: str | None):
|
|
192
|
+
self._granted_by = value
|
|
193
|
+
|
|
194
|
+
# Granted At
|
|
195
|
+
@property
|
|
196
|
+
def granted_at(self) -> int | None:
|
|
197
|
+
"""UTC timestamp when granted."""
|
|
198
|
+
return self._granted_at
|
|
199
|
+
|
|
200
|
+
@granted_at.setter
|
|
201
|
+
def granted_at(self, value: int | None):
|
|
202
|
+
self._granted_at = value
|
|
203
|
+
|
|
204
|
+
# Expires At
|
|
205
|
+
@property
|
|
206
|
+
def expires_at(self) -> int | None:
|
|
207
|
+
"""UTC timestamp when grant expires (None = never)."""
|
|
208
|
+
return self._expires_at
|
|
209
|
+
|
|
210
|
+
@expires_at.setter
|
|
211
|
+
def expires_at(self, value: int | None):
|
|
212
|
+
self._expires_at = value
|
|
213
|
+
|
|
214
|
+
# Reason
|
|
215
|
+
@property
|
|
216
|
+
def reason(self) -> str | None:
|
|
217
|
+
"""Reason for granting permission."""
|
|
218
|
+
return self._reason
|
|
219
|
+
|
|
220
|
+
@reason.setter
|
|
221
|
+
def reason(self, value: str | None):
|
|
222
|
+
self._reason = value
|
|
223
|
+
|
|
224
|
+
# Metadata
|
|
225
|
+
@property
|
|
226
|
+
def metadata(self) -> Dict[str, Any]:
|
|
227
|
+
"""Additional metadata."""
|
|
228
|
+
return self._metadata
|
|
229
|
+
|
|
230
|
+
@metadata.setter
|
|
231
|
+
def metadata(self, value: Dict[str, Any]):
|
|
232
|
+
self._metadata = value if value else {}
|
|
233
|
+
|
|
234
|
+
# Helper Methods
|
|
235
|
+
|
|
236
|
+
def is_expired(self) -> bool:
|
|
237
|
+
"""Check if grant has expired."""
|
|
238
|
+
if self.expires_at is None:
|
|
239
|
+
return False
|
|
240
|
+
import time
|
|
241
|
+
return time.time() > self.expires_at
|
|
242
|
+
|
|
243
|
+
def has_permission(self, permission: str) -> bool:
|
|
244
|
+
"""Check if grant includes specific permission."""
|
|
245
|
+
return permission in self.permissions
|