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.

Files changed (194) hide show
  1. geek_cafe_saas_sdk/__init__.py +9 -0
  2. geek_cafe_saas_sdk/core/__init__.py +11 -0
  3. geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
  4. geek_cafe_saas_sdk/core/error_codes.py +132 -0
  5. geek_cafe_saas_sdk/core/service_errors.py +19 -0
  6. geek_cafe_saas_sdk/core/service_result.py +121 -0
  7. geek_cafe_saas_sdk/decorators/__init__.py +64 -0
  8. geek_cafe_saas_sdk/decorators/auth.py +373 -0
  9. geek_cafe_saas_sdk/decorators/core.py +358 -0
  10. geek_cafe_saas_sdk/domains/__init__.py +0 -0
  11. geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
  12. geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
  13. geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
  14. geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
  15. geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
  16. geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
  17. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
  18. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
  19. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
  20. geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
  21. geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
  22. geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
  23. geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
  24. geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
  25. geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
  26. geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
  27. geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
  28. geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
  29. geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
  30. geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
  31. geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
  32. geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
  33. geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
  34. geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
  35. geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
  36. geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
  37. geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
  38. geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
  39. geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
  40. geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
  41. geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
  42. geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
  43. geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
  44. geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
  45. geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
  46. geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
  47. geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
  48. geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
  49. geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
  50. geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
  51. geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
  52. geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
  53. geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
  54. geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
  55. geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
  56. geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
  57. geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
  58. geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
  59. geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
  60. geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
  61. geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
  62. geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
  63. geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
  64. geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
  65. geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
  66. geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
  67. geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
  68. geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
  69. geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
  70. geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
  71. geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
  72. geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
  73. geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
  74. geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
  75. geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
  76. geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
  77. geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
  78. geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
  79. geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
  80. geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
  81. geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
  82. geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
  83. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
  84. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
  85. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
  86. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
  87. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
  88. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
  89. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
  90. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
  91. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
  92. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
  93. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
  94. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
  95. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
  96. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
  97. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
  98. geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
  99. geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
  100. geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
  101. geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
  102. geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
  103. geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
  104. geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
  105. geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
  106. geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
  107. geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
  108. geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
  109. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
  110. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
  111. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
  112. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
  113. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
  114. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
  115. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
  116. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
  117. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
  118. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
  119. geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
  120. geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
  121. geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
  122. geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
  123. geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
  124. geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
  125. geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
  126. geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
  127. geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
  128. geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
  129. geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
  130. geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
  131. geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
  132. geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
  133. geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
  134. geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
  135. geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
  136. geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
  137. geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
  138. geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
  139. geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
  140. geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
  141. geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
  142. geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
  143. geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
  144. geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
  145. geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
  146. geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
  147. geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
  148. geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
  149. geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
  150. geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
  151. geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
  152. geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
  153. geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
  154. geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
  155. geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
  156. geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
  157. geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
  158. geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
  159. geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
  160. geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
  161. geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
  162. geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
  163. geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
  164. geek_cafe_saas_sdk/middleware/__init__.py +36 -0
  165. geek_cafe_saas_sdk/middleware/auth.py +85 -0
  166. geek_cafe_saas_sdk/middleware/authorization.py +523 -0
  167. geek_cafe_saas_sdk/middleware/cors.py +63 -0
  168. geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
  169. geek_cafe_saas_sdk/middleware/validation.py +80 -0
  170. geek_cafe_saas_sdk/models/__init__.py +20 -0
  171. geek_cafe_saas_sdk/models/base_model.py +233 -0
  172. geek_cafe_saas_sdk/services/__init__.py +18 -0
  173. geek_cafe_saas_sdk/services/database_service.py +441 -0
  174. geek_cafe_saas_sdk/utilities/__init__.py +88 -0
  175. geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
  176. geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
  177. geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
  178. geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
  179. geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
  180. geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
  181. geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
  182. geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
  183. geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
  184. geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
  185. geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
  186. geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
  187. geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
  188. geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
  189. geek_cafe_saas_sdk/utilities/response.py +209 -0
  190. geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
  191. geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
  192. geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
  193. geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
  194. 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
+ }