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,404 @@
|
|
|
1
|
+
# Lambda Handler Reference Implementations
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This directory contains **reference Lambda handler implementations** for the file system services. These handlers are **templates** meant to be copied and customized by projects consuming this SDK.
|
|
6
|
+
|
|
7
|
+
## Important: These Are Not Deployed
|
|
8
|
+
|
|
9
|
+
**This SDK does not deploy Lambda functions to AWS.** These handlers serve as:
|
|
10
|
+
|
|
11
|
+
- ✅ **Reference implementations** showing how to use the SDK services
|
|
12
|
+
- ✅ **Starting templates** for your own Lambda functions
|
|
13
|
+
- ✅ **Best practice examples** for structuring handlers
|
|
14
|
+
- ✅ **Integration patterns** with API Gateway
|
|
15
|
+
|
|
16
|
+
## How to Use
|
|
17
|
+
|
|
18
|
+
### 1. Install the SDK in Your Project
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install geek-cafe-saas-sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Copy Handlers You Need
|
|
25
|
+
|
|
26
|
+
Copy the handler files into your project:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Copy specific handler
|
|
30
|
+
cp lambda_handlers/files/upload/app.py my-project/handlers/file_upload.py
|
|
31
|
+
|
|
32
|
+
# Or copy entire categories
|
|
33
|
+
cp -r lambda_handlers/files my-project/handlers/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Customize for Your Needs
|
|
37
|
+
|
|
38
|
+
All handlers follow this pattern:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from geek_cafe_saas_sdk.domains.files.services.file_system_service import FileSystemService
|
|
42
|
+
|
|
43
|
+
def lambda_handler(event, context):
|
|
44
|
+
# 1. Extract parameters from event
|
|
45
|
+
# 2. Initialize services from SDK
|
|
46
|
+
# 3. Call service methods
|
|
47
|
+
# 4. Return formatted response
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Customize:
|
|
51
|
+
- **Authentication**: Add Cognito, JWT, or custom auth
|
|
52
|
+
- **Validation**: Add business-specific rules
|
|
53
|
+
- **Error handling**: Match your error format
|
|
54
|
+
- **Logging**: Integrate with your logging system
|
|
55
|
+
- **Metrics**: Add custom CloudWatch metrics
|
|
56
|
+
|
|
57
|
+
### 4. Deploy with Your Infrastructure
|
|
58
|
+
|
|
59
|
+
Deploy using your preferred tool:
|
|
60
|
+
|
|
61
|
+
**AWS SAM:**
|
|
62
|
+
```yaml
|
|
63
|
+
FileUploadFunction:
|
|
64
|
+
Type: AWS::Serverless::Function
|
|
65
|
+
Properties:
|
|
66
|
+
CodeUri: handlers/
|
|
67
|
+
Handler: file_upload.lambda_handler
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Serverless Framework:**
|
|
71
|
+
```yaml
|
|
72
|
+
functions:
|
|
73
|
+
fileUpload:
|
|
74
|
+
handler: handlers/file_upload.lambda_handler
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**AWS CDK:**
|
|
78
|
+
```python
|
|
79
|
+
lambda_.Function(
|
|
80
|
+
self, "FileUpload",
|
|
81
|
+
handler="file_upload.lambda_handler",
|
|
82
|
+
code=lambda_.Code.from_asset("handlers")
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Handler Structure
|
|
89
|
+
|
|
90
|
+
### File Operations
|
|
91
|
+
|
|
92
|
+
| Handler | Path | Purpose |
|
|
93
|
+
|---------|------|---------|
|
|
94
|
+
| `files/upload/app.py` | `POST /files` | Upload new file |
|
|
95
|
+
| `files/download/app.py` | `GET /files/{id}/download` | Download file |
|
|
96
|
+
| `files/get/app.py` | `GET /files/{id}` | Get metadata |
|
|
97
|
+
| `files/update/app.py` | `PUT /files/{id}` | Update metadata |
|
|
98
|
+
| `files/delete/app.py` | `DELETE /files/{id}` | Delete file |
|
|
99
|
+
| `files/list/app.py` | `GET /files` | List files |
|
|
100
|
+
|
|
101
|
+
### Directory Operations
|
|
102
|
+
|
|
103
|
+
| Handler | Path | Purpose |
|
|
104
|
+
|---------|------|---------|
|
|
105
|
+
| `directories/create/app.py` | `POST /directories` | Create directory |
|
|
106
|
+
| `directories/get/app.py` | `GET /directories/{id}` | Get directory |
|
|
107
|
+
| `directories/list/app.py` | `GET /directories` | List directories |
|
|
108
|
+
| `directories/move/app.py` | `PUT /directories/{id}/move` | Move directory |
|
|
109
|
+
| `directories/delete/app.py` | `DELETE /directories/{id}` | Delete directory |
|
|
110
|
+
|
|
111
|
+
### Sharing Operations
|
|
112
|
+
|
|
113
|
+
| Handler | Path | Purpose |
|
|
114
|
+
|---------|------|---------|
|
|
115
|
+
| `files/share/app.py` | `POST /files/share` | Share file |
|
|
116
|
+
| `files/shares/list/app.py` | `GET /files/shares` | List shares |
|
|
117
|
+
| `files/shares/revoke/app.py` | `DELETE /files/shares/{id}` | Revoke share |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Common Customizations
|
|
122
|
+
|
|
123
|
+
### Add Authentication
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
def lambda_handler(event, context):
|
|
127
|
+
# Validate JWT token
|
|
128
|
+
token = event['headers'].get('Authorization')
|
|
129
|
+
user = validate_token(token)
|
|
130
|
+
|
|
131
|
+
# Use validated user_id
|
|
132
|
+
result = file_service.create(
|
|
133
|
+
tenant_id=user.tenant_id,
|
|
134
|
+
user_id=user.user_id,
|
|
135
|
+
...
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Add Request Validation
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
def lambda_handler(event, context):
|
|
143
|
+
body = json.loads(event['body'])
|
|
144
|
+
|
|
145
|
+
# Validate with your schema
|
|
146
|
+
errors = validate_upload_request(body)
|
|
147
|
+
if errors:
|
|
148
|
+
return {
|
|
149
|
+
'statusCode': 400,
|
|
150
|
+
'body': json.dumps({'errors': errors})
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Add Custom Error Handling
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
def lambda_handler(event, context):
|
|
158
|
+
try:
|
|
159
|
+
result = file_service.create(...)
|
|
160
|
+
|
|
161
|
+
if not result.success:
|
|
162
|
+
# Map SDK errors to your error codes
|
|
163
|
+
error_code = map_error_code(result.error_code)
|
|
164
|
+
return custom_error_response(error_code, result.message)
|
|
165
|
+
|
|
166
|
+
except CustomException as e:
|
|
167
|
+
# Handle business exceptions
|
|
168
|
+
return business_error_response(e)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Add Logging and Metrics
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import logging
|
|
175
|
+
from aws_lambda_powertools import Logger, Metrics
|
|
176
|
+
|
|
177
|
+
logger = Logger()
|
|
178
|
+
metrics = Metrics()
|
|
179
|
+
|
|
180
|
+
@logger.inject_lambda_context
|
|
181
|
+
@metrics.log_metrics
|
|
182
|
+
def lambda_handler(event, context):
|
|
183
|
+
logger.info("File upload requested", extra={
|
|
184
|
+
"tenant_id": tenant_id,
|
|
185
|
+
"file_name": file_name
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
metrics.add_metric(name="FileUploads", unit="Count", value=1)
|
|
189
|
+
|
|
190
|
+
result = file_service.create(...)
|
|
191
|
+
|
|
192
|
+
if result.success:
|
|
193
|
+
metrics.add_metric(name="FileUploadSuccess", unit="Count", value=1)
|
|
194
|
+
else:
|
|
195
|
+
metrics.add_metric(name="FileUploadFailure", unit="Count", value=1)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Environment Variables
|
|
201
|
+
|
|
202
|
+
All handlers expect these environment variables:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
DYNAMODB_TABLE_NAME=your-table-name
|
|
206
|
+
S3_BUCKET_NAME=your-bucket-name
|
|
207
|
+
AWS_REGION=us-east-1
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Set these in your infrastructure code:
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
# SAM
|
|
214
|
+
Environment:
|
|
215
|
+
Variables:
|
|
216
|
+
DYNAMODB_TABLE_NAME: !Ref FilesTable
|
|
217
|
+
S3_BUCKET_NAME: !Ref FilesBucket
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Required Dependencies
|
|
223
|
+
|
|
224
|
+
When deploying, include:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
geek-cafe-saas-sdk
|
|
228
|
+
boto3
|
|
229
|
+
boto3-assist
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Use Lambda Layers for dependencies:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Create layer
|
|
236
|
+
mkdir -p layer/python
|
|
237
|
+
pip install -t layer/python geek-cafe-saas-sdk boto3-assist
|
|
238
|
+
cd layer && zip -r ../dependencies.zip .
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Testing Handlers Locally
|
|
244
|
+
|
|
245
|
+
### With SAM CLI
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Create test event
|
|
249
|
+
cat > test-event.json <<EOF
|
|
250
|
+
{
|
|
251
|
+
"body": {
|
|
252
|
+
"tenant_id": "test",
|
|
253
|
+
"user_id": "user1",
|
|
254
|
+
"file_name": "test.txt",
|
|
255
|
+
"file_data": "SGVsbG8="
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
EOF
|
|
259
|
+
|
|
260
|
+
# Invoke locally
|
|
261
|
+
sam local invoke FileUploadFunction -e test-event.json
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### With Python
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from your_handlers.file_upload import lambda_handler
|
|
268
|
+
|
|
269
|
+
event = {
|
|
270
|
+
"body": {
|
|
271
|
+
"tenant_id": "test",
|
|
272
|
+
"user_id": "user1",
|
|
273
|
+
"file_name": "test.txt",
|
|
274
|
+
"file_data": "SGVsbG8="
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
response = lambda_handler(event, None)
|
|
279
|
+
print(response)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Example: Complete Custom Handler
|
|
285
|
+
|
|
286
|
+
Here's how to create a custom handler based on the SDK:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
"""
|
|
290
|
+
Custom file upload handler for MyApp.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
import json
|
|
294
|
+
import base64
|
|
295
|
+
import os
|
|
296
|
+
from typing import Dict, Any
|
|
297
|
+
|
|
298
|
+
from geek_cafe_saas_sdk.domains.files.services.file_system_service import FileSystemService
|
|
299
|
+
from geek_cafe_saas_sdk.domains.files.services.s3_file_service import S3FileService
|
|
300
|
+
from boto3_assist.dynamodb.dynamodb import DynamoDB
|
|
301
|
+
from boto3_assist.s3.s3_connection import S3Connection
|
|
302
|
+
from boto3_assist.s3.s3_object import S3Object
|
|
303
|
+
from boto3_assist.s3.s3_bucket import S3Bucket
|
|
304
|
+
|
|
305
|
+
from myapp.auth import validate_jwt
|
|
306
|
+
from myapp.validation import validate_file_upload
|
|
307
|
+
from myapp.logging import get_logger
|
|
308
|
+
|
|
309
|
+
logger = get_logger(__name__)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
313
|
+
"""Custom file upload handler with MyApp-specific logic."""
|
|
314
|
+
|
|
315
|
+
# 1. Authenticate user
|
|
316
|
+
try:
|
|
317
|
+
user = validate_jwt(event['headers']['Authorization'])
|
|
318
|
+
except Exception as e:
|
|
319
|
+
return {
|
|
320
|
+
'statusCode': 401,
|
|
321
|
+
'body': json.dumps({'error': 'Unauthorized'})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
# 2. Parse and validate request
|
|
325
|
+
body = json.loads(event['body'])
|
|
326
|
+
errors = validate_file_upload(body, user)
|
|
327
|
+
if errors:
|
|
328
|
+
return {
|
|
329
|
+
'statusCode': 400,
|
|
330
|
+
'body': json.dumps({'errors': errors})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# 3. Initialize SDK services
|
|
334
|
+
db = DynamoDB()
|
|
335
|
+
connection = S3Connection()
|
|
336
|
+
|
|
337
|
+
s3_service = S3FileService(
|
|
338
|
+
s3_object=S3Object(connection=connection),
|
|
339
|
+
s3_bucket=S3Bucket(connection=connection),
|
|
340
|
+
default_bucket=os.environ['S3_BUCKET_NAME']
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
file_service = FileSystemService(
|
|
344
|
+
dynamodb=db,
|
|
345
|
+
table_name=os.environ['DYNAMODB_TABLE_NAME'],
|
|
346
|
+
s3_service=s3_service,
|
|
347
|
+
default_bucket=os.environ['S3_BUCKET_NAME']
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# 4. Upload file using SDK
|
|
351
|
+
try:
|
|
352
|
+
result = file_service.create(
|
|
353
|
+
tenant_id=user.tenant_id,
|
|
354
|
+
user_id=user.user_id,
|
|
355
|
+
file_name=body['file_name'],
|
|
356
|
+
file_data=base64.b64decode(body['file_data']),
|
|
357
|
+
mime_type=body.get('mime_type', 'application/octet-stream'),
|
|
358
|
+
directory_id=body.get('directory_id'),
|
|
359
|
+
tags=body.get('tags', [])
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
if result.success:
|
|
363
|
+
logger.info(f"File uploaded: {result.data.file_id}")
|
|
364
|
+
|
|
365
|
+
# 5. Return MyApp-formatted response
|
|
366
|
+
return {
|
|
367
|
+
'statusCode': 201,
|
|
368
|
+
'body': json.dumps({
|
|
369
|
+
'id': result.data.file_id,
|
|
370
|
+
'name': result.data.file_name,
|
|
371
|
+
'size': result.data.file_size,
|
|
372
|
+
'created_at': result.data.created_utc_ts
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
else:
|
|
376
|
+
logger.error(f"Upload failed: {result.message}")
|
|
377
|
+
return {
|
|
378
|
+
'statusCode': 400,
|
|
379
|
+
'body': json.dumps({
|
|
380
|
+
'error': result.message,
|
|
381
|
+
'code': result.error_code
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
except Exception as e:
|
|
386
|
+
logger.exception("Unexpected error during upload")
|
|
387
|
+
return {
|
|
388
|
+
'statusCode': 500,
|
|
389
|
+
'body': json.dumps({'error': 'Internal server error'})
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Support
|
|
396
|
+
|
|
397
|
+
- **Service Documentation**: See main SDK docs for service usage
|
|
398
|
+
- **Usage Examples**: See `docs/FILE_SYSTEM_USAGE.md`
|
|
399
|
+
- **API Reference**: See `docs/FILE_SYSTEM_API.md`
|
|
400
|
+
- **Integration Tests**: See `tests/test_files_integration.py` for complete workflows
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
**Remember**: These handlers are starting points. Customize them to fit your application's needs!
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler wrappers for reducing boilerplate in AWS Lambda functions.
|
|
3
|
+
|
|
4
|
+
This module provides a flexible, composable system for creating Lambda handlers
|
|
5
|
+
with built-in support for:
|
|
6
|
+
- API key validation
|
|
7
|
+
- API Gateway authorization (Cognito/Lambda authorizers)
|
|
8
|
+
- Public endpoints (no auth)
|
|
9
|
+
- Request/response transformation
|
|
10
|
+
- Service initialization and pooling
|
|
11
|
+
- Error handling and CORS
|
|
12
|
+
- User context extraction
|
|
13
|
+
|
|
14
|
+
Example Usage with Factory (Recommended):
|
|
15
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
16
|
+
from geek_cafe_saas_sdk.services.vote_service import VoteService
|
|
17
|
+
|
|
18
|
+
# Factory automatically selects handler based on AUTH_TYPE env var
|
|
19
|
+
# Defaults to secure (API Gateway authorizer) if not set
|
|
20
|
+
handler = create_handler(
|
|
21
|
+
service_class=VoteService,
|
|
22
|
+
require_body=True,
|
|
23
|
+
convert_case=True
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def lambda_handler(event, context):
|
|
27
|
+
return handler.execute(event, context, business_logic)
|
|
28
|
+
|
|
29
|
+
def business_logic(event, service, user_context):
|
|
30
|
+
payload = event["parsed_body"]
|
|
31
|
+
return service.create_vote(...)
|
|
32
|
+
|
|
33
|
+
Environment Configuration:
|
|
34
|
+
AUTH_TYPE=secure # API Gateway authorizer (default)
|
|
35
|
+
AUTH_TYPE=api_key # x-api-key header validation
|
|
36
|
+
AUTH_TYPE=public # No authentication
|
|
37
|
+
|
|
38
|
+
AUTH_STRICT=true # Strict validation (default)
|
|
39
|
+
AUTH_STRICT=false # Permissive for local dev
|
|
40
|
+
|
|
41
|
+
Direct Usage (if needed):
|
|
42
|
+
from geek_cafe_saas_sdk.lambda_handlers import SecureLambdaHandler
|
|
43
|
+
|
|
44
|
+
handler = SecureLambdaHandler(
|
|
45
|
+
service_class=VoteService,
|
|
46
|
+
require_body=True
|
|
47
|
+
)
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from ._base.base_handler import BaseLambdaHandler
|
|
51
|
+
from ._base.api_key_handler import ApiKeyLambdaHandler
|
|
52
|
+
from ._base.public_handler import PublicLambdaHandler
|
|
53
|
+
from ._base.secure_handler import SecureLambdaHandler
|
|
54
|
+
from ._base.authorized_secure_handler import AuthorizedSecureLambdaHandler
|
|
55
|
+
from ._base.service_pool import ServicePool
|
|
56
|
+
from ._base.handler_factory import HandlerFactory, create_handler
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"BaseLambdaHandler",
|
|
60
|
+
"ApiKeyLambdaHandler",
|
|
61
|
+
"PublicLambdaHandler",
|
|
62
|
+
"SecureLambdaHandler",
|
|
63
|
+
"AuthorizedSecureLambdaHandler",
|
|
64
|
+
"ServicePool",
|
|
65
|
+
"HandlerFactory",
|
|
66
|
+
"create_handler",
|
|
67
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Lambda handler implementations.
|
|
3
|
+
|
|
4
|
+
This module contains the core handler infrastructure that all Lambda handlers
|
|
5
|
+
can build upon. These are internal/shared components.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .base_handler import BaseLambdaHandler
|
|
9
|
+
from .api_key_handler import ApiKeyLambdaHandler
|
|
10
|
+
from .public_handler import PublicLambdaHandler
|
|
11
|
+
from .secure_handler import SecureLambdaHandler
|
|
12
|
+
from .authorized_secure_handler import AuthorizedSecureLambdaHandler
|
|
13
|
+
from .service_pool import ServicePool
|
|
14
|
+
from .handler_factory import HandlerFactory, create_handler
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'BaseLambdaHandler',
|
|
18
|
+
'ApiKeyLambdaHandler',
|
|
19
|
+
'PublicLambdaHandler',
|
|
20
|
+
'SecureLambdaHandler',
|
|
21
|
+
'AuthorizedSecureLambdaHandler',
|
|
22
|
+
'ServicePool',
|
|
23
|
+
'HandlerFactory',
|
|
24
|
+
'create_handler',
|
|
25
|
+
]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler with API key validation.
|
|
3
|
+
|
|
4
|
+
Implements the API key validation pattern used across the application.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Dict, Any, Optional, Callable, TypeVar
|
|
9
|
+
from aws_lambda_powertools import Logger
|
|
10
|
+
|
|
11
|
+
from geek_cafe_saas_sdk.utilities.response import error_response
|
|
12
|
+
from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
|
|
13
|
+
from .base_handler import BaseLambdaHandler
|
|
14
|
+
|
|
15
|
+
logger = Logger()
|
|
16
|
+
T = TypeVar('T')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ApiKeyLambdaHandler(BaseLambdaHandler):
|
|
20
|
+
"""
|
|
21
|
+
Lambda handler with API key validation.
|
|
22
|
+
|
|
23
|
+
Validates that requests include a valid API key in the x-api-key header.
|
|
24
|
+
The expected API key is read from the API_KEY environment variable.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
handler = ApiKeyLambdaHandler(
|
|
28
|
+
service_class=VoteService,
|
|
29
|
+
require_body=True
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def lambda_handler(event, context):
|
|
33
|
+
return handler.execute(event, context, process_vote)
|
|
34
|
+
|
|
35
|
+
def process_vote(event, service, user_context):
|
|
36
|
+
payload = event["parsed_body"]
|
|
37
|
+
return service.create_vote(...)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
api_key_env_var: str = "API_KEY",
|
|
43
|
+
api_key_header: str = "x-api-key",
|
|
44
|
+
**kwargs
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Initialize the API key handler.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
api_key_env_var: Environment variable name for API key
|
|
51
|
+
api_key_header: Header name to check for API key
|
|
52
|
+
**kwargs: Arguments passed to BaseLambdaHandler
|
|
53
|
+
"""
|
|
54
|
+
# API key handlers don't require JWT auth - API key IS the auth
|
|
55
|
+
kwargs.setdefault('require_auth', False)
|
|
56
|
+
super().__init__(**kwargs)
|
|
57
|
+
self.api_key_env_var = api_key_env_var
|
|
58
|
+
self.api_key_header = api_key_header
|
|
59
|
+
|
|
60
|
+
def _validate_security(self, event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
61
|
+
"""
|
|
62
|
+
Validate API key is present and correct at request time.
|
|
63
|
+
|
|
64
|
+
This method reads the API_KEY environment variable within the request
|
|
65
|
+
cycle, making it testable.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Error response if validation fails, None if valid.
|
|
69
|
+
"""
|
|
70
|
+
# 1. Get expected key from environment (at request time)
|
|
71
|
+
expected_api_key = os.getenv(self.api_key_env_var)
|
|
72
|
+
|
|
73
|
+
# 2. Check if the key is configured in the environment
|
|
74
|
+
if not expected_api_key:
|
|
75
|
+
logger.error(
|
|
76
|
+
f"API key not configured. Set '{self.api_key_env_var}' environment variable."
|
|
77
|
+
)
|
|
78
|
+
# This is a server-side configuration error
|
|
79
|
+
return error_response(
|
|
80
|
+
"This endpoint is not configured for API key access.",
|
|
81
|
+
"CONFIGURATION_ERROR",
|
|
82
|
+
500,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# 3. Get provided key from request headers
|
|
86
|
+
provided_api_key = LambdaEventUtility.get_value_from_header(
|
|
87
|
+
event, self.api_key_header
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# 4. Check if a key was provided
|
|
91
|
+
if not provided_api_key:
|
|
92
|
+
logger.warning(f"No API key provided in '{self.api_key_header}' header.")
|
|
93
|
+
return error_response("API key is required.", "UNAUTHORIZED", 401)
|
|
94
|
+
|
|
95
|
+
# 5. Validate the key
|
|
96
|
+
if provided_api_key != expected_api_key:
|
|
97
|
+
logger.warning("Invalid API key provided.")
|
|
98
|
+
return error_response("Invalid API key.", "UNAUTHORIZED", 401)
|
|
99
|
+
|
|
100
|
+
# 6. Validation passed
|
|
101
|
+
logger.info("API key validation successful.")
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def execute(
|
|
105
|
+
self,
|
|
106
|
+
event: Dict[str, Any],
|
|
107
|
+
context: Any,
|
|
108
|
+
business_logic: Callable[[Dict[str, Any], Any, Dict[str, Any]], Any],
|
|
109
|
+
injected_service: Optional[T] = None
|
|
110
|
+
) -> Dict[str, Any]:
|
|
111
|
+
"""
|
|
112
|
+
Execute the Lambda handler with API key validation.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
event: Lambda event dictionary
|
|
116
|
+
context: Lambda context object
|
|
117
|
+
business_logic: Callable that implements the business logic
|
|
118
|
+
injected_service: Optional service instance for testing
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Lambda response dictionary
|
|
122
|
+
"""
|
|
123
|
+
# Validate API key first
|
|
124
|
+
validation_error = self._validate_security(event)
|
|
125
|
+
if validation_error:
|
|
126
|
+
return validation_error
|
|
127
|
+
|
|
128
|
+
# If validation passed, execute parent's execute method
|
|
129
|
+
return super().execute(event, context, business_logic, injected_service)
|