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,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for public endpoints.
|
|
3
|
+
|
|
4
|
+
Public handler with no authentication required.
|
|
5
|
+
Useful for public APIs, health checks, and configuration endpoints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Any, Optional
|
|
9
|
+
from .base_handler import BaseLambdaHandler
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PublicLambdaHandler(BaseLambdaHandler):
|
|
13
|
+
"""
|
|
14
|
+
Lambda handler for public endpoints (no authentication).
|
|
15
|
+
|
|
16
|
+
Does not require any authentication or API keys.
|
|
17
|
+
Use for truly public endpoints like health checks or public configuration.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
handler = PublicLambdaHandler(
|
|
21
|
+
require_body=False,
|
|
22
|
+
convert_case=False
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def lambda_handler(event, context):
|
|
26
|
+
return handler.execute(event, context, get_config)
|
|
27
|
+
|
|
28
|
+
def get_config(event, service, user_context):
|
|
29
|
+
return {
|
|
30
|
+
"version": "1.0",
|
|
31
|
+
"environment": os.getenv("ENVIRONMENT")
|
|
32
|
+
}
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, **kwargs):
|
|
36
|
+
"""
|
|
37
|
+
Initialize public handler.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
**kwargs: Arguments passed to BaseLambdaHandler
|
|
41
|
+
"""
|
|
42
|
+
# Public handlers don't require JWT auth
|
|
43
|
+
kwargs.setdefault('require_auth', False)
|
|
44
|
+
super().__init__(**kwargs)
|
|
45
|
+
|
|
46
|
+
def _validate_security(self, event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
47
|
+
"""
|
|
48
|
+
Public endpoints have no security validation.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
None (always valid)
|
|
52
|
+
"""
|
|
53
|
+
return None
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Secure Lambda handler that relies on API Gateway authorization.
|
|
3
|
+
|
|
4
|
+
This handler assumes that API Gateway (or ALB) has already validated
|
|
5
|
+
the request using AWS Cognito, IAM, or a custom authorizer.
|
|
6
|
+
The handler trusts that the request has been authenticated.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict, Any, Optional
|
|
10
|
+
from aws_lambda_powertools import Logger
|
|
11
|
+
|
|
12
|
+
from .base_handler import BaseLambdaHandler
|
|
13
|
+
|
|
14
|
+
logger = Logger()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SecureLambdaHandler(BaseLambdaHandler):
|
|
18
|
+
"""
|
|
19
|
+
Secure handler that relies on API Gateway/ALB authorization.
|
|
20
|
+
|
|
21
|
+
Use this when:
|
|
22
|
+
- API Gateway has a Cognito authorizer configured
|
|
23
|
+
- API Gateway has a Lambda authorizer configured
|
|
24
|
+
- ALB has authentication configured
|
|
25
|
+
- Request is authenticated before reaching Lambda
|
|
26
|
+
|
|
27
|
+
The handler does NOT perform its own authentication.
|
|
28
|
+
It trusts the upstream service (API Gateway/ALB).
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
handler = SecureLambdaHandler(
|
|
32
|
+
service_class=VoteService,
|
|
33
|
+
require_body=True
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def lambda_handler(event, context):
|
|
37
|
+
return handler.execute(event, context, process_vote)
|
|
38
|
+
|
|
39
|
+
def process_vote(event, service, user_context):
|
|
40
|
+
# user_context contains claims from API Gateway authorizer
|
|
41
|
+
payload = event["parsed_body"]
|
|
42
|
+
return service.create_vote(...)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
require_authorizer_claims: bool = True,
|
|
48
|
+
**kwargs
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Initialize the secure handler.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
require_authorizer_claims: Whether to require authorizer claims in event
|
|
55
|
+
**kwargs: Arguments passed to BaseLambdaHandler
|
|
56
|
+
"""
|
|
57
|
+
super().__init__(**kwargs)
|
|
58
|
+
self.require_authorizer_claims = require_authorizer_claims
|
|
59
|
+
|
|
60
|
+
def _validate_security(self, event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
61
|
+
"""
|
|
62
|
+
Validate that request came through authorized API Gateway.
|
|
63
|
+
|
|
64
|
+
Checks for requestContext.authorizer which is populated by
|
|
65
|
+
API Gateway when using Cognito or Lambda authorizers.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Error response if validation fails, None if valid
|
|
69
|
+
"""
|
|
70
|
+
if not self.require_authorizer_claims:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
# Check for API Gateway request context
|
|
74
|
+
request_context = event.get("requestContext", {})
|
|
75
|
+
authorizer = request_context.get("authorizer", {})
|
|
76
|
+
|
|
77
|
+
# If no authorizer context, this might be a misconfiguration
|
|
78
|
+
if not authorizer:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"SecureLambdaHandler: No authorizer context found. "
|
|
81
|
+
"Ensure API Gateway has an authorizer configured."
|
|
82
|
+
)
|
|
83
|
+
# You can choose to allow or deny here
|
|
84
|
+
# For now, we'll log a warning but allow (assuming local dev/testing)
|
|
85
|
+
# In production, you might want to return an error
|
|
86
|
+
|
|
87
|
+
# The handler trusts API Gateway did the auth
|
|
88
|
+
# User claims are extracted in extract_user_context()
|
|
89
|
+
return None
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service pooling manager for Lambda warm starts.
|
|
3
|
+
|
|
4
|
+
Manages service initialization and caching to improve Lambda performance
|
|
5
|
+
by reusing connections across invocations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Type, TypeVar, Generic, Optional
|
|
9
|
+
|
|
10
|
+
T = TypeVar('T')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ServicePool(Generic[T]):
|
|
14
|
+
"""
|
|
15
|
+
Manages service instances for Lambda warm starts.
|
|
16
|
+
|
|
17
|
+
Lambda containers reuse the global scope between invocations, allowing
|
|
18
|
+
us to cache service instances (and their DB connections) to reduce
|
|
19
|
+
cold start latency by 80-90%.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
# Module level
|
|
23
|
+
vote_service_pool = ServicePool(VoteService)
|
|
24
|
+
|
|
25
|
+
# In handler
|
|
26
|
+
service = vote_service_pool.get()
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, service_class: Type[T]):
|
|
30
|
+
"""
|
|
31
|
+
Initialize the service pool.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
service_class: The service class to instantiate
|
|
35
|
+
"""
|
|
36
|
+
self.service_class = service_class
|
|
37
|
+
self._instance: Optional[T] = None
|
|
38
|
+
|
|
39
|
+
def get(self) -> T:
|
|
40
|
+
"""
|
|
41
|
+
Get or create the service instance.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Service instance (cached on warm starts)
|
|
45
|
+
"""
|
|
46
|
+
if self._instance is None:
|
|
47
|
+
self._instance = self.service_class()
|
|
48
|
+
return self._instance
|
|
49
|
+
|
|
50
|
+
def reset(self):
|
|
51
|
+
"""Reset the pool (useful for testing)."""
|
|
52
|
+
self._instance = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class MultiServicePool:
|
|
56
|
+
"""
|
|
57
|
+
Manages multiple service instances by class name.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
pool = MultiServicePool()
|
|
61
|
+
vote_service = pool.get(VoteService)
|
|
62
|
+
analytics_service = pool.get(WebsiteAnalyticsService)
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self):
|
|
66
|
+
self._pools: Dict[Type, ServicePool] = {}
|
|
67
|
+
|
|
68
|
+
def get(self, service_class: Type[T]) -> T:
|
|
69
|
+
"""
|
|
70
|
+
Get or create a service instance.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
service_class: The service class to instantiate
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Service instance (cached on warm starts)
|
|
77
|
+
"""
|
|
78
|
+
if service_class not in self._pools:
|
|
79
|
+
self._pools[service_class] = ServicePool(service_class)
|
|
80
|
+
return self._pools[service_class].get()
|
|
81
|
+
|
|
82
|
+
def reset(self, service_class: Optional[Type] = None):
|
|
83
|
+
"""
|
|
84
|
+
Reset one or all service pools.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
service_class: Specific class to reset, or None for all
|
|
88
|
+
"""
|
|
89
|
+
if service_class:
|
|
90
|
+
if service_class in self._pools:
|
|
91
|
+
self._pools[service_class].reset()
|
|
92
|
+
else:
|
|
93
|
+
for pool in self._pools.values():
|
|
94
|
+
pool.reset()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for creating directories.
|
|
3
|
+
|
|
4
|
+
Geek Cafe, LLC
|
|
5
|
+
MIT License. See Project Root for the license information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
from boto3_assist.dynamodb.dynamodb import DynamoDB
|
|
13
|
+
from geek_cafe_saas_sdk.domains.files.services.directory_service import DirectoryService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
17
|
+
"""Create directory handler."""
|
|
18
|
+
try:
|
|
19
|
+
if isinstance(event.get('body'), str):
|
|
20
|
+
body = json.loads(event['body'])
|
|
21
|
+
else:
|
|
22
|
+
body = event.get('body', {})
|
|
23
|
+
|
|
24
|
+
tenant_id = body.get('tenant_id')
|
|
25
|
+
user_id = body.get('user_id')
|
|
26
|
+
directory_name = body.get('directory_name')
|
|
27
|
+
parent_directory_id = body.get('parent_directory_id')
|
|
28
|
+
description = body.get('description')
|
|
29
|
+
|
|
30
|
+
if not all([tenant_id, user_id, directory_name]):
|
|
31
|
+
return {
|
|
32
|
+
'statusCode': 400,
|
|
33
|
+
'body': json.dumps({
|
|
34
|
+
'success': False,
|
|
35
|
+
'message': 'Missing required fields'
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
table_name = os.environ.get('DYNAMODB_TABLE_NAME', 'files-table')
|
|
40
|
+
db = DynamoDB()
|
|
41
|
+
|
|
42
|
+
dir_service = DirectoryService(
|
|
43
|
+
dynamodb=db,
|
|
44
|
+
table_name=table_name
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
result = dir_service.create(
|
|
48
|
+
tenant_id=tenant_id,
|
|
49
|
+
user_id=user_id,
|
|
50
|
+
directory_name=directory_name,
|
|
51
|
+
parent_directory_id=parent_directory_id,
|
|
52
|
+
description=description
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if result.success:
|
|
56
|
+
return {
|
|
57
|
+
'statusCode': 201,
|
|
58
|
+
'body': json.dumps({
|
|
59
|
+
'success': True,
|
|
60
|
+
'data': result.data.to_dictionary()
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
else:
|
|
64
|
+
return {
|
|
65
|
+
'statusCode': 400,
|
|
66
|
+
'body': json.dumps({
|
|
67
|
+
'success': False,
|
|
68
|
+
'message': result.message
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return {
|
|
74
|
+
'statusCode': 500,
|
|
75
|
+
'body': json.dumps({
|
|
76
|
+
'success': False,
|
|
77
|
+
'message': str(e)
|
|
78
|
+
})
|
|
79
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for deleting directories.
|
|
3
|
+
|
|
4
|
+
Geek Cafe, LLC
|
|
5
|
+
MIT License. See Project Root for the license information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
from boto3_assist.dynamodb.dynamodb import DynamoDB
|
|
13
|
+
from geek_cafe_saas_sdk.domains.files.services.directory_service import DirectoryService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
17
|
+
"""Delete directory handler."""
|
|
18
|
+
try:
|
|
19
|
+
path_params = event.get('pathParameters', {})
|
|
20
|
+
query_params = event.get('queryStringParameters', {})
|
|
21
|
+
|
|
22
|
+
directory_id = path_params.get('directory_id')
|
|
23
|
+
tenant_id = query_params.get('tenant_id')
|
|
24
|
+
user_id = query_params.get('user_id')
|
|
25
|
+
hard_delete = query_params.get('hard_delete', 'false').lower() == 'true'
|
|
26
|
+
|
|
27
|
+
if not all([directory_id, tenant_id, user_id]):
|
|
28
|
+
return {
|
|
29
|
+
'statusCode': 400,
|
|
30
|
+
'body': json.dumps({
|
|
31
|
+
'success': False,
|
|
32
|
+
'message': 'Missing required parameters'
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
table_name = os.environ.get('DYNAMODB_TABLE_NAME', 'files-table')
|
|
37
|
+
db = DynamoDB()
|
|
38
|
+
|
|
39
|
+
dir_service = DirectoryService(
|
|
40
|
+
dynamodb=db,
|
|
41
|
+
table_name=table_name
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
result = dir_service.delete(
|
|
45
|
+
resource_id=directory_id,
|
|
46
|
+
tenant_id=tenant_id,
|
|
47
|
+
user_id=user_id,
|
|
48
|
+
hard_delete=hard_delete
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if result.success:
|
|
52
|
+
return {
|
|
53
|
+
'statusCode': 200,
|
|
54
|
+
'body': json.dumps({
|
|
55
|
+
'success': True,
|
|
56
|
+
'message': 'Directory deleted successfully'
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
else:
|
|
60
|
+
status_code = 404 if result.error_code == 'NOT_FOUND' else 403
|
|
61
|
+
return {
|
|
62
|
+
'statusCode': status_code,
|
|
63
|
+
'body': json.dumps({
|
|
64
|
+
'success': False,
|
|
65
|
+
'message': result.message
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
return {
|
|
71
|
+
'statusCode': 500,
|
|
72
|
+
'body': json.dumps({
|
|
73
|
+
'success': False,
|
|
74
|
+
'message': str(e)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for getting directory metadata.
|
|
3
|
+
|
|
4
|
+
Geek Cafe, LLC
|
|
5
|
+
MIT License. See Project Root for the license information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
from boto3_assist.dynamodb.dynamodb import DynamoDB
|
|
13
|
+
from geek_cafe_saas_sdk.domains.files.services.directory_service import DirectoryService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
17
|
+
"""Get directory handler."""
|
|
18
|
+
try:
|
|
19
|
+
path_params = event.get('pathParameters', {})
|
|
20
|
+
query_params = event.get('queryStringParameters', {})
|
|
21
|
+
|
|
22
|
+
directory_id = path_params.get('directory_id')
|
|
23
|
+
tenant_id = query_params.get('tenant_id')
|
|
24
|
+
user_id = query_params.get('user_id')
|
|
25
|
+
|
|
26
|
+
if not all([directory_id, tenant_id, user_id]):
|
|
27
|
+
return {
|
|
28
|
+
'statusCode': 400,
|
|
29
|
+
'body': json.dumps({
|
|
30
|
+
'success': False,
|
|
31
|
+
'message': 'Missing required parameters'
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
table_name = os.environ.get('DYNAMODB_TABLE_NAME', 'files-table')
|
|
36
|
+
db = DynamoDB()
|
|
37
|
+
|
|
38
|
+
dir_service = DirectoryService(
|
|
39
|
+
dynamodb=db,
|
|
40
|
+
table_name=table_name
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
result = dir_service.get_by_id(
|
|
44
|
+
resource_id=directory_id,
|
|
45
|
+
tenant_id=tenant_id,
|
|
46
|
+
user_id=user_id
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if result.success:
|
|
50
|
+
return {
|
|
51
|
+
'statusCode': 200,
|
|
52
|
+
'body': json.dumps({
|
|
53
|
+
'success': True,
|
|
54
|
+
'data': result.data.to_dictionary()
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
else:
|
|
58
|
+
status_code = 404 if result.error_code == 'NOT_FOUND' else 403
|
|
59
|
+
return {
|
|
60
|
+
'statusCode': status_code,
|
|
61
|
+
'body': json.dumps({
|
|
62
|
+
'success': False,
|
|
63
|
+
'message': result.message
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
return {
|
|
69
|
+
'statusCode': 500,
|
|
70
|
+
'body': json.dumps({
|
|
71
|
+
'success': False,
|
|
72
|
+
'message': str(e)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for listing directories.
|
|
3
|
+
|
|
4
|
+
Geek Cafe, LLC
|
|
5
|
+
MIT License. See Project Root for the license information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
from boto3_assist.dynamodb.dynamodb import DynamoDB
|
|
13
|
+
from geek_cafe_saas_sdk.domains.files.services.directory_service import DirectoryService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
17
|
+
"""List directories handler."""
|
|
18
|
+
try:
|
|
19
|
+
query_params = event.get('queryStringParameters', {})
|
|
20
|
+
|
|
21
|
+
tenant_id = query_params.get('tenant_id')
|
|
22
|
+
user_id = query_params.get('user_id')
|
|
23
|
+
parent_directory_id = query_params.get('parent_directory_id')
|
|
24
|
+
limit = int(query_params.get('limit', '100'))
|
|
25
|
+
|
|
26
|
+
if not all([tenant_id, user_id]):
|
|
27
|
+
return {
|
|
28
|
+
'statusCode': 400,
|
|
29
|
+
'body': json.dumps({
|
|
30
|
+
'success': False,
|
|
31
|
+
'message': 'Missing required parameters'
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
table_name = os.environ.get('DYNAMODB_TABLE_NAME', 'files-table')
|
|
36
|
+
db = DynamoDB()
|
|
37
|
+
|
|
38
|
+
dir_service = DirectoryService(
|
|
39
|
+
dynamodb=db,
|
|
40
|
+
table_name=table_name
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
result = dir_service.list_by_parent(
|
|
44
|
+
tenant_id=tenant_id,
|
|
45
|
+
parent_directory_id=parent_directory_id,
|
|
46
|
+
user_id=user_id,
|
|
47
|
+
limit=limit
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if result.success:
|
|
51
|
+
return {
|
|
52
|
+
'statusCode': 200,
|
|
53
|
+
'body': json.dumps({
|
|
54
|
+
'success': True,
|
|
55
|
+
'data': [d.to_dictionary() for d in result.data],
|
|
56
|
+
'count': len(result.data)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
else:
|
|
60
|
+
return {
|
|
61
|
+
'statusCode': 400,
|
|
62
|
+
'body': json.dumps({
|
|
63
|
+
'success': False,
|
|
64
|
+
'message': result.message
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
return {
|
|
70
|
+
'statusCode': 500,
|
|
71
|
+
'body': json.dumps({
|
|
72
|
+
'success': False,
|
|
73
|
+
'message': str(e)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for moving directories.
|
|
3
|
+
|
|
4
|
+
Geek Cafe, LLC
|
|
5
|
+
MIT License. See Project Root for the license information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
from boto3_assist.dynamodb.dynamodb import DynamoDB
|
|
13
|
+
from geek_cafe_saas_sdk.domains.files.services.directory_service import DirectoryService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
17
|
+
"""Move directory handler."""
|
|
18
|
+
try:
|
|
19
|
+
path_params = event.get('pathParameters', {})
|
|
20
|
+
|
|
21
|
+
if isinstance(event.get('body'), str):
|
|
22
|
+
body = json.loads(event['body'])
|
|
23
|
+
else:
|
|
24
|
+
body = event.get('body', {})
|
|
25
|
+
|
|
26
|
+
directory_id = path_params.get('directory_id')
|
|
27
|
+
tenant_id = body.get('tenant_id')
|
|
28
|
+
user_id = body.get('user_id')
|
|
29
|
+
new_parent_id = body.get('new_parent_id')
|
|
30
|
+
|
|
31
|
+
if not all([directory_id, tenant_id, user_id, new_parent_id]):
|
|
32
|
+
return {
|
|
33
|
+
'statusCode': 400,
|
|
34
|
+
'body': json.dumps({
|
|
35
|
+
'success': False,
|
|
36
|
+
'message': 'Missing required parameters'
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
table_name = os.environ.get('DYNAMODB_TABLE_NAME', 'files-table')
|
|
41
|
+
db = DynamoDB()
|
|
42
|
+
|
|
43
|
+
dir_service = DirectoryService(
|
|
44
|
+
dynamodb=db,
|
|
45
|
+
table_name=table_name
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
result = dir_service.move(
|
|
49
|
+
directory_id=directory_id,
|
|
50
|
+
tenant_id=tenant_id,
|
|
51
|
+
user_id=user_id,
|
|
52
|
+
new_parent_id=new_parent_id
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if result.success:
|
|
56
|
+
return {
|
|
57
|
+
'statusCode': 200,
|
|
58
|
+
'body': json.dumps({
|
|
59
|
+
'success': True,
|
|
60
|
+
'data': result.data.to_dictionary()
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
else:
|
|
64
|
+
return {
|
|
65
|
+
'statusCode': 400,
|
|
66
|
+
'body': json.dumps({
|
|
67
|
+
'success': False,
|
|
68
|
+
'message': result.message
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return {
|
|
74
|
+
'statusCode': 500,
|
|
75
|
+
'body': json.dumps({
|
|
76
|
+
'success': False,
|
|
77
|
+
'message': str(e)
|
|
78
|
+
})
|
|
79
|
+
}
|