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,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for listing file shares.
|
|
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.file_share_service import FileShareService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
17
|
+
"""List shares 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
|
+
file_id = query_params.get('file_id')
|
|
24
|
+
limit = int(query_params.get('limit', '50'))
|
|
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
|
+
share_service = FileShareService(
|
|
39
|
+
dynamodb=db,
|
|
40
|
+
table_name=table_name
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if file_id:
|
|
44
|
+
# List shares for a specific file
|
|
45
|
+
result = share_service.list_shares_by_file(
|
|
46
|
+
tenant_id=tenant_id,
|
|
47
|
+
file_id=file_id,
|
|
48
|
+
user_id=user_id,
|
|
49
|
+
limit=limit
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
# List shares with current user
|
|
53
|
+
result = share_service.list_shares_with_user(
|
|
54
|
+
tenant_id=tenant_id,
|
|
55
|
+
user_id=user_id,
|
|
56
|
+
limit=limit
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if result.success:
|
|
60
|
+
return {
|
|
61
|
+
'statusCode': 200,
|
|
62
|
+
'body': json.dumps({
|
|
63
|
+
'success': True,
|
|
64
|
+
'data': [s.to_dictionary() for s in result.data],
|
|
65
|
+
'count': len(result.data)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
else:
|
|
69
|
+
return {
|
|
70
|
+
'statusCode': 400,
|
|
71
|
+
'body': json.dumps({
|
|
72
|
+
'success': False,
|
|
73
|
+
'message': result.message
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return {
|
|
79
|
+
'statusCode': 500,
|
|
80
|
+
'body': json.dumps({
|
|
81
|
+
'success': False,
|
|
82
|
+
'message': str(e)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for revoking file shares.
|
|
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.file_share_service import FileShareService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
17
|
+
"""Revoke share handler."""
|
|
18
|
+
try:
|
|
19
|
+
path_params = event.get('pathParameters', {})
|
|
20
|
+
query_params = event.get('queryStringParameters', {})
|
|
21
|
+
|
|
22
|
+
share_id = path_params.get('share_id')
|
|
23
|
+
tenant_id = query_params.get('tenant_id')
|
|
24
|
+
user_id = query_params.get('user_id')
|
|
25
|
+
file_id = query_params.get('file_id')
|
|
26
|
+
|
|
27
|
+
if not all([share_id, tenant_id, user_id, file_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
|
+
share_service = FileShareService(
|
|
40
|
+
dynamodb=db,
|
|
41
|
+
table_name=table_name
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
result = share_service.delete(
|
|
45
|
+
resource_id=share_id,
|
|
46
|
+
tenant_id=tenant_id,
|
|
47
|
+
user_id=user_id,
|
|
48
|
+
file_id=file_id
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if result.success:
|
|
52
|
+
return {
|
|
53
|
+
'statusCode': 200,
|
|
54
|
+
'body': json.dumps({
|
|
55
|
+
'success': True,
|
|
56
|
+
'message': 'Share revoked 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,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for updating file 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 boto3_assist.s3.s3_connection import S3Connection
|
|
14
|
+
from boto3_assist.s3.s3_object import S3Object
|
|
15
|
+
from boto3_assist.s3.s3_bucket import S3Bucket
|
|
16
|
+
|
|
17
|
+
from geek_cafe_saas_sdk.domains.files.services.file_system_service import FileSystemService
|
|
18
|
+
from geek_cafe_saas_sdk.domains.files.services.s3_file_service import S3FileService
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
22
|
+
"""
|
|
23
|
+
Update file metadata handler.
|
|
24
|
+
|
|
25
|
+
Expected Event:
|
|
26
|
+
{
|
|
27
|
+
"pathParameters": {
|
|
28
|
+
"file_id": "file-123"
|
|
29
|
+
},
|
|
30
|
+
"body": {
|
|
31
|
+
"tenant_id": "tenant-456",
|
|
32
|
+
"user_id": "user-789",
|
|
33
|
+
"updates": {
|
|
34
|
+
"file_name": "Updated Name.pdf",
|
|
35
|
+
"description": "New description",
|
|
36
|
+
"tags": ["updated", "tags"],
|
|
37
|
+
"directory_id": "new-dir-id"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
{
|
|
44
|
+
"statusCode": 200,
|
|
45
|
+
"body": {
|
|
46
|
+
"success": true,
|
|
47
|
+
"data": {
|
|
48
|
+
"file_id": "file-123",
|
|
49
|
+
"file_name": "Updated Name.pdf",
|
|
50
|
+
...
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
# Parse request
|
|
57
|
+
path_params = event.get('pathParameters', {})
|
|
58
|
+
|
|
59
|
+
if isinstance(event.get('body'), str):
|
|
60
|
+
body = json.loads(event['body'])
|
|
61
|
+
else:
|
|
62
|
+
body = event.get('body', {})
|
|
63
|
+
|
|
64
|
+
file_id = path_params.get('file_id')
|
|
65
|
+
tenant_id = body.get('tenant_id')
|
|
66
|
+
user_id = body.get('user_id')
|
|
67
|
+
updates = body.get('updates', {})
|
|
68
|
+
|
|
69
|
+
# Validate required fields
|
|
70
|
+
if not all([file_id, tenant_id, user_id]):
|
|
71
|
+
return {
|
|
72
|
+
'statusCode': 400,
|
|
73
|
+
'body': json.dumps({
|
|
74
|
+
'success': False,
|
|
75
|
+
'message': 'Missing required parameters: file_id, tenant_id, user_id'
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if not updates:
|
|
80
|
+
return {
|
|
81
|
+
'statusCode': 400,
|
|
82
|
+
'body': json.dumps({
|
|
83
|
+
'success': False,
|
|
84
|
+
'message': 'No updates provided'
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Initialize services
|
|
89
|
+
table_name = os.environ.get('DYNAMODB_TABLE_NAME', 'files-table')
|
|
90
|
+
bucket_name = os.environ.get('S3_BUCKET_NAME', 'files-bucket')
|
|
91
|
+
|
|
92
|
+
db = DynamoDB()
|
|
93
|
+
connection = S3Connection()
|
|
94
|
+
|
|
95
|
+
s3_service = S3FileService(
|
|
96
|
+
s3_object=S3Object(connection=connection),
|
|
97
|
+
s3_bucket=S3Bucket(connection=connection),
|
|
98
|
+
default_bucket=bucket_name
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
file_service = FileSystemService(
|
|
102
|
+
dynamodb=db,
|
|
103
|
+
table_name=table_name,
|
|
104
|
+
s3_service=s3_service,
|
|
105
|
+
default_bucket=bucket_name
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Update file
|
|
109
|
+
result = file_service.update(
|
|
110
|
+
resource_id=file_id,
|
|
111
|
+
tenant_id=tenant_id,
|
|
112
|
+
user_id=user_id,
|
|
113
|
+
updates=updates
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if result.success:
|
|
117
|
+
file = result.data
|
|
118
|
+
return {
|
|
119
|
+
'statusCode': 200,
|
|
120
|
+
'body': json.dumps({
|
|
121
|
+
'success': True,
|
|
122
|
+
'data': file.to_dictionary()
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
else:
|
|
126
|
+
status_code = 404 if result.error_code == 'NOT_FOUND' else 403
|
|
127
|
+
return {
|
|
128
|
+
'statusCode': status_code,
|
|
129
|
+
'body': json.dumps({
|
|
130
|
+
'success': False,
|
|
131
|
+
'message': result.message,
|
|
132
|
+
'error_code': result.error_code
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
return {
|
|
138
|
+
'statusCode': 500,
|
|
139
|
+
'body': json.dumps({
|
|
140
|
+
'success': False,
|
|
141
|
+
'message': f'Internal server error: {str(e)}'
|
|
142
|
+
})
|
|
143
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for file upload.
|
|
3
|
+
|
|
4
|
+
Geek Cafe, LLC
|
|
5
|
+
MIT License. See Project Root for the license information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import base64
|
|
10
|
+
import os
|
|
11
|
+
from typing import Dict, Any
|
|
12
|
+
|
|
13
|
+
from boto3_assist.dynamodb.dynamodb import DynamoDB
|
|
14
|
+
from boto3_assist.s3.s3_connection import S3Connection
|
|
15
|
+
from boto3_assist.s3.s3_object import S3Object
|
|
16
|
+
from boto3_assist.s3.s3_bucket import S3Bucket
|
|
17
|
+
|
|
18
|
+
from geek_cafe_saas_sdk.domains.files.services.file_system_service import FileSystemService
|
|
19
|
+
from geek_cafe_saas_sdk.domains.files.services.s3_file_service import S3FileService
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
Upload file handler.
|
|
25
|
+
|
|
26
|
+
Expected Event:
|
|
27
|
+
{
|
|
28
|
+
"body": {
|
|
29
|
+
"tenant_id": "tenant-123",
|
|
30
|
+
"user_id": "user-456",
|
|
31
|
+
"file_name": "document.pdf",
|
|
32
|
+
"file_data": "base64_encoded_content",
|
|
33
|
+
"mime_type": "application/pdf",
|
|
34
|
+
"directory_id": "dir-789", # Optional
|
|
35
|
+
"versioning_strategy": "explicit", # Optional
|
|
36
|
+
"description": "Monthly report", # Optional
|
|
37
|
+
"tags": ["report", "monthly"] # Optional
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
{
|
|
43
|
+
"statusCode": 201,
|
|
44
|
+
"body": {
|
|
45
|
+
"success": true,
|
|
46
|
+
"data": {
|
|
47
|
+
"file_id": "file-123",
|
|
48
|
+
"file_name": "document.pdf",
|
|
49
|
+
"s3_key": "tenant-123/files/...",
|
|
50
|
+
"file_size": 12345
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
# Parse request body
|
|
57
|
+
if isinstance(event.get('body'), str):
|
|
58
|
+
body = json.loads(event['body'])
|
|
59
|
+
else:
|
|
60
|
+
body = event.get('body', {})
|
|
61
|
+
|
|
62
|
+
# Extract parameters
|
|
63
|
+
tenant_id = body.get('tenant_id')
|
|
64
|
+
user_id = body.get('user_id')
|
|
65
|
+
file_name = body.get('file_name')
|
|
66
|
+
file_data_b64 = body.get('file_data')
|
|
67
|
+
mime_type = body.get('mime_type', 'application/octet-stream')
|
|
68
|
+
directory_id = body.get('directory_id')
|
|
69
|
+
versioning_strategy = body.get('versioning_strategy', 's3_native')
|
|
70
|
+
description = body.get('description')
|
|
71
|
+
tags = body.get('tags', [])
|
|
72
|
+
|
|
73
|
+
# Validate required fields
|
|
74
|
+
if not all([tenant_id, user_id, file_name, file_data_b64]):
|
|
75
|
+
return {
|
|
76
|
+
'statusCode': 400,
|
|
77
|
+
'body': json.dumps({
|
|
78
|
+
'success': False,
|
|
79
|
+
'message': 'Missing required fields: tenant_id, user_id, file_name, file_data'
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Decode file data
|
|
84
|
+
file_data = base64.b64decode(file_data_b64)
|
|
85
|
+
|
|
86
|
+
# Initialize services
|
|
87
|
+
table_name = os.environ.get('DYNAMODB_TABLE_NAME', 'files-table')
|
|
88
|
+
bucket_name = os.environ.get('S3_BUCKET_NAME', 'files-bucket')
|
|
89
|
+
|
|
90
|
+
db = DynamoDB()
|
|
91
|
+
connection = S3Connection()
|
|
92
|
+
|
|
93
|
+
s3_service = S3FileService(
|
|
94
|
+
s3_object=S3Object(connection=connection),
|
|
95
|
+
s3_bucket=S3Bucket(connection=connection),
|
|
96
|
+
default_bucket=bucket_name
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
file_service = FileSystemService(
|
|
100
|
+
dynamodb=db,
|
|
101
|
+
table_name=table_name,
|
|
102
|
+
s3_service=s3_service,
|
|
103
|
+
default_bucket=bucket_name
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Upload file
|
|
107
|
+
result = file_service.create(
|
|
108
|
+
tenant_id=tenant_id,
|
|
109
|
+
user_id=user_id,
|
|
110
|
+
file_name=file_name,
|
|
111
|
+
file_data=file_data,
|
|
112
|
+
mime_type=mime_type,
|
|
113
|
+
directory_id=directory_id,
|
|
114
|
+
versioning_strategy=versioning_strategy,
|
|
115
|
+
description=description,
|
|
116
|
+
tags=tags
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if result.success:
|
|
120
|
+
file = result.data
|
|
121
|
+
return {
|
|
122
|
+
'statusCode': 201,
|
|
123
|
+
'body': json.dumps({
|
|
124
|
+
'success': True,
|
|
125
|
+
'data': {
|
|
126
|
+
'file_id': file.file_id,
|
|
127
|
+
'file_name': file.file_name,
|
|
128
|
+
's3_key': file.s3_key,
|
|
129
|
+
'file_size': file.file_size,
|
|
130
|
+
'created_utc_ts': file.created_utc_ts
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
else:
|
|
135
|
+
return {
|
|
136
|
+
'statusCode': 400,
|
|
137
|
+
'body': json.dumps({
|
|
138
|
+
'success': False,
|
|
139
|
+
'message': result.message,
|
|
140
|
+
'error_code': result.error_code
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
return {
|
|
146
|
+
'statusCode': 500,
|
|
147
|
+
'body': json.dumps({
|
|
148
|
+
'success': False,
|
|
149
|
+
'message': f'Internal server error: {str(e)}'
|
|
150
|
+
})
|
|
151
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Middleware decorators for Lambda functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .auth import require_auth, extract_user_context
|
|
6
|
+
from .cors import handle_cors
|
|
7
|
+
from .error_handling import handle_errors
|
|
8
|
+
from .validation import validate_request_body
|
|
9
|
+
from .authorization import (
|
|
10
|
+
require_authorization,
|
|
11
|
+
Permission,
|
|
12
|
+
Operation,
|
|
13
|
+
AuthContext,
|
|
14
|
+
ResourceContext,
|
|
15
|
+
AuthorizationResult,
|
|
16
|
+
AuthorizationMiddleware,
|
|
17
|
+
extract_auth_context,
|
|
18
|
+
extract_resource_context
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"require_auth",
|
|
23
|
+
"extract_user_context",
|
|
24
|
+
"handle_cors",
|
|
25
|
+
"handle_errors",
|
|
26
|
+
"validate_request_body",
|
|
27
|
+
"require_authorization",
|
|
28
|
+
"Permission",
|
|
29
|
+
"Operation",
|
|
30
|
+
"AuthContext",
|
|
31
|
+
"ResourceContext",
|
|
32
|
+
"AuthorizationResult",
|
|
33
|
+
"AuthorizationMiddleware",
|
|
34
|
+
"extract_auth_context",
|
|
35
|
+
"extract_resource_context"
|
|
36
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication middleware for Lambda handlers.
|
|
3
|
+
"""
|
|
4
|
+
import json
|
|
5
|
+
import functools
|
|
6
|
+
from typing import Dict, Any, Callable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def require_auth(handler: Callable) -> Callable:
|
|
10
|
+
"""
|
|
11
|
+
Decorator that ensures the request has valid authentication.
|
|
12
|
+
Expects API Gateway authorizer to populate requestContext.authorizer.
|
|
13
|
+
"""
|
|
14
|
+
@functools.wraps(handler)
|
|
15
|
+
def wrapper(event: Dict[str, Any], context: Any, *args, **kwargs) -> Dict[str, Any]:
|
|
16
|
+
# Check if authorizer context exists
|
|
17
|
+
request_context = event.get('requestContext', {})
|
|
18
|
+
authorizer = request_context.get('authorizer', {})
|
|
19
|
+
|
|
20
|
+
if not authorizer:
|
|
21
|
+
return {
|
|
22
|
+
'statusCode': 401,
|
|
23
|
+
'headers': {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'Access-Control-Allow-Origin': '*'
|
|
26
|
+
},
|
|
27
|
+
'body': json.dumps({
|
|
28
|
+
'error': 'Unauthorized',
|
|
29
|
+
'error_code': 'AUTH_REQUIRED',
|
|
30
|
+
'message': 'Missing authorizer context'
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Validate required auth fields
|
|
35
|
+
claims = authorizer.get('claims', {})
|
|
36
|
+
required_fields = ['custom:user_id', 'custom:tenant_id']
|
|
37
|
+
for field in required_fields:
|
|
38
|
+
if not claims.get(field):
|
|
39
|
+
return {
|
|
40
|
+
'statusCode': 401,
|
|
41
|
+
'headers': {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'Access-Control-Allow-Origin': '*'
|
|
44
|
+
},
|
|
45
|
+
'body': json.dumps({
|
|
46
|
+
'error': f'Missing required auth field: {field}',
|
|
47
|
+
'error_code': 'AUTH_INVALID',
|
|
48
|
+
'message': 'Missing required auth field'
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Call the original handler
|
|
53
|
+
return handler(event, context, *args, **kwargs)
|
|
54
|
+
|
|
55
|
+
return wrapper
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def extract_user_context(event: Dict[str, Any]) -> Dict[str, str]:
|
|
59
|
+
"""
|
|
60
|
+
Extract user context from API Gateway authorizer.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dict containing user_id, tenant_id, roles, permissions, inboxes, and other claims
|
|
64
|
+
"""
|
|
65
|
+
authorizer = event.get('requestContext', {}).get('authorizer', {})
|
|
66
|
+
claims = authorizer.get('claims', {})
|
|
67
|
+
|
|
68
|
+
# Parse comma-separated custom claims (strip whitespace and filter empty)
|
|
69
|
+
def parse_csv_claim(claim_value: str) -> list:
|
|
70
|
+
"""Parse comma-separated claim value into list."""
|
|
71
|
+
if not claim_value:
|
|
72
|
+
return []
|
|
73
|
+
return [item.strip() for item in claim_value.split(',') if item.strip()]
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
'user_id': claims.get('custom:user_id'),
|
|
77
|
+
'tenant_id': claims.get('custom:tenant_id'),
|
|
78
|
+
'roles': parse_csv_claim(claims.get('custom:roles', '')),
|
|
79
|
+
'permissions': parse_csv_claim(claims.get('custom:permissions', '')),
|
|
80
|
+
'inboxes': parse_csv_claim(claims.get('custom:inboxes', '')),
|
|
81
|
+
'shared_tenants': parse_csv_claim(claims.get('custom:shared_tenants', '')),
|
|
82
|
+
'email': claims.get('email'),
|
|
83
|
+
'name': claims.get('name'),
|
|
84
|
+
'sub': claims.get('sub') # Cognito user ID
|
|
85
|
+
}
|