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,9 @@
1
+ """
2
+ Geek Cafe Services - Base Reusable Services for SaaS
3
+
4
+ Version 0.2.0 adds Lambda Handler Wrappers for reducing boilerplate code.
5
+ """
6
+ __version__ = "0.6.0"
7
+
8
+ # Import main modules for easier access
9
+ from . import services
@@ -0,0 +1,11 @@
1
+ # Core module for foundational classes and utilities
2
+
3
+ from .service_errors import ValidationError, AccessDeniedError, NotFoundError
4
+ from .service_result import ServiceResult
5
+
6
+ __all__ = [
7
+ 'ValidationError',
8
+ 'AccessDeniedError',
9
+ 'NotFoundError',
10
+ 'ServiceResult',
11
+ ]
@@ -0,0 +1,33 @@
1
+ from typing import Optional, Dict
2
+ from datetime import datetime
3
+ import logging
4
+
5
+
6
+ class AuditMixin:
7
+ """Mixin for services that need audit logging."""
8
+
9
+ def _log_activity(self, action: str, resource_type: str, resource_id: str,
10
+ tenant_id: str, user_id: str, metadata: Optional[Dict] = None) -> None:
11
+ """Log activity for audit purposes."""
12
+ logger = logging.getLogger(f"{__name__}.audit")
13
+
14
+ audit_data = {
15
+ 'action': action,
16
+ 'resource_type': resource_type,
17
+ 'resource_id': resource_id,
18
+ 'tenant_id': tenant_id,
19
+ 'user_id': user_id,
20
+ 'timestamp': datetime.now().isoformat(),
21
+ 'metadata': metadata or {}
22
+ }
23
+
24
+ logger.info(
25
+ f"AUDIT: {action} on {resource_type} {resource_id} by user {user_id}",
26
+ extra=audit_data
27
+ )
28
+
29
+ # Also print for console visibility
30
+ print(f"šŸ“‹ AUDIT: {action} on {resource_type} {resource_id} by user {user_id}")
31
+
32
+ # TODO: Implement activity logging to database such as dynamodb or put into a queue
33
+
@@ -0,0 +1,132 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ MIT License. See Project Root for the license information.
4
+
5
+ Standardized error codes for all services.
6
+ """
7
+
8
+ from enum import Enum
9
+
10
+
11
+ class ErrorCode(str, Enum):
12
+ """
13
+ Standardized error codes for all services.
14
+
15
+ Error codes are organized by category and map to HTTP status code equivalents.
16
+ Using an enum ensures type safety and consistency across all services.
17
+ """
18
+
19
+ # Input/Validation Errors (4xx equivalent)
20
+ VALIDATION_ERROR = "VALIDATION_ERROR" # Invalid input data
21
+ MISSING_REQUIRED_FIELD = "MISSING_REQUIRED_FIELD" # Specific validation failure
22
+ INVALID_FORMAT = "INVALID_FORMAT" # Data format issues
23
+ INVALID_PARAMETER = "INVALID_PARAMETER" # Parameter value issues
24
+
25
+ # Authorization Errors (403 equivalent)
26
+ ACCESS_DENIED = "ACCESS_DENIED" # User lacks permission
27
+ INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS" # Role-based denial
28
+ AUTHENTICATION_REQUIRED = "AUTHENTICATION_REQUIRED" # No auth token provided
29
+
30
+ # Resource Errors (404 equivalent)
31
+ NOT_FOUND = "NOT_FOUND" # Resource doesn't exist
32
+ RESOURCE_DELETED = "RESOURCE_DELETED" # Resource was soft-deleted
33
+
34
+ # Conflict Errors (409 equivalent)
35
+ ALREADY_EXISTS = "ALREADY_EXISTS" # Duplicate resource
36
+ CONCURRENT_MODIFICATION = "CONCURRENT_MODIFICATION" # Optimistic lock failure
37
+ RESOURCE_CONFLICT = "RESOURCE_CONFLICT" # General conflict
38
+
39
+ # Database Errors (500 equivalent)
40
+ DATABASE_ERROR = "DATABASE_ERROR" # Generic DB error
41
+ DATABASE_SAVE_FAILED = "DATABASE_SAVE_FAILED" # Save operation failed
42
+ DATABASE_DELETE_FAILED = "DATABASE_DELETE_FAILED" # Delete operation failed
43
+ DATABASE_QUERY_FAILED = "DATABASE_QUERY_FAILED" # Query operation failed
44
+ DATABASE_CONNECTION_ERROR = "DATABASE_CONNECTION_ERROR" # Connection issues
45
+
46
+ # Service/Business Logic Errors (500 equivalent)
47
+ INTERNAL_ERROR = "INTERNAL_ERROR" # Unexpected error
48
+ SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE" # Dependency failure
49
+ OPERATION_FAILED = "OPERATION_FAILED" # Business logic failure
50
+
51
+ # Batch/Bulk Operation Errors
52
+ PARTIAL_FAILURE = "PARTIAL_FAILURE" # Some items in batch failed
53
+ BATCH_OPERATION_FAILED = "BATCH_OPERATION_FAILED" # Entire batch failed
54
+
55
+ # Rate Limiting / Throttling (429 equivalent)
56
+ RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED" # Too many requests
57
+ QUOTA_EXCEEDED = "QUOTA_EXCEEDED" # Usage quota exceeded
58
+
59
+ def __str__(self) -> str:
60
+ """Return the error code value as string."""
61
+ return self.value
62
+
63
+ @classmethod
64
+ def is_client_error(cls, code: 'ErrorCode') -> bool:
65
+ """
66
+ Check if error code represents a client error (4xx equivalent).
67
+
68
+ Args:
69
+ code: Error code to check
70
+
71
+ Returns:
72
+ True if client error, False otherwise
73
+ """
74
+ client_errors = {
75
+ cls.VALIDATION_ERROR,
76
+ cls.MISSING_REQUIRED_FIELD,
77
+ cls.INVALID_FORMAT,
78
+ cls.INVALID_PARAMETER,
79
+ cls.ACCESS_DENIED,
80
+ cls.INSUFFICIENT_PERMISSIONS,
81
+ cls.AUTHENTICATION_REQUIRED,
82
+ cls.NOT_FOUND,
83
+ cls.RESOURCE_DELETED,
84
+ cls.ALREADY_EXISTS,
85
+ cls.CONCURRENT_MODIFICATION,
86
+ cls.RESOURCE_CONFLICT,
87
+ cls.RATE_LIMIT_EXCEEDED,
88
+ cls.QUOTA_EXCEEDED,
89
+ }
90
+ return code in client_errors
91
+
92
+ @classmethod
93
+ def is_server_error(cls, code: 'ErrorCode') -> bool:
94
+ """
95
+ Check if error code represents a server error (5xx equivalent).
96
+
97
+ Args:
98
+ code: Error code to check
99
+
100
+ Returns:
101
+ True if server error, False otherwise
102
+ """
103
+ return not cls.is_client_error(code)
104
+
105
+ @classmethod
106
+ def get_http_status(cls, code: 'ErrorCode') -> int:
107
+ """
108
+ Get suggested HTTP status code for error code.
109
+
110
+ Args:
111
+ code: Error code
112
+
113
+ Returns:
114
+ HTTP status code (e.g., 400, 404, 500)
115
+ """
116
+ status_map = {
117
+ cls.VALIDATION_ERROR: 400,
118
+ cls.MISSING_REQUIRED_FIELD: 400,
119
+ cls.INVALID_FORMAT: 400,
120
+ cls.INVALID_PARAMETER: 400,
121
+ cls.AUTHENTICATION_REQUIRED: 401,
122
+ cls.ACCESS_DENIED: 403,
123
+ cls.INSUFFICIENT_PERMISSIONS: 403,
124
+ cls.NOT_FOUND: 404,
125
+ cls.RESOURCE_DELETED: 410, # Gone
126
+ cls.ALREADY_EXISTS: 409,
127
+ cls.CONCURRENT_MODIFICATION: 409,
128
+ cls.RESOURCE_CONFLICT: 409,
129
+ cls.RATE_LIMIT_EXCEEDED: 429,
130
+ cls.QUOTA_EXCEEDED: 429,
131
+ }
132
+ return status_map.get(code, 500) # Default to 500 for server errors
@@ -0,0 +1,19 @@
1
+ # Service Errors
2
+ from typing import Optional, Union, List
3
+
4
+ class ValidationError(Exception):
5
+ """Validation error for service operations."""
6
+
7
+ def __init__(self, message: str, field: Optional[Union[str, List[str]]] = None):
8
+ super().__init__(message)
9
+ self.field = field
10
+
11
+
12
+ class AccessDeniedError(Exception):
13
+ """Access denied error for service operations."""
14
+ pass
15
+
16
+
17
+ class NotFoundError(Exception):
18
+ """Resource not found error."""
19
+ pass
@@ -0,0 +1,121 @@
1
+ """Base service classes for collaborative property operations."""
2
+
3
+
4
+ import traceback
5
+ from typing import Any, Dict, List, Optional, TypeVar, Generic
6
+ from datetime import datetime
7
+
8
+ from aws_lambda_powertools import Logger
9
+
10
+ logger = Logger()
11
+
12
+
13
+ T = TypeVar('T')
14
+
15
+
16
+ class ServiceResult(Generic[T]):
17
+ """Standard service operation result with enhanced error handling."""
18
+
19
+ def __init__(self, success: bool, data: Optional[T] = None,
20
+ message: Optional[str] = None, error_code: Optional[str] = None,
21
+ error_details: Optional[Dict[str, Any]] = None,
22
+ stack_trace: Optional[str] = None):
23
+ self.success = success
24
+ self.data = data
25
+ self.message = message
26
+ self.error_code = error_code
27
+ self.error_details = error_details or {}
28
+ self.stack_trace = stack_trace
29
+ self.timestamp = datetime.now()
30
+
31
+ @classmethod
32
+ def success_result(cls, data: T) -> 'ServiceResult[T]':
33
+ """Create a successful result."""
34
+ return cls(success=True, data=data)
35
+
36
+ @classmethod
37
+ def error_result(cls, message: str, error_code: Optional[str] = None,
38
+ error_details: Optional[Dict[str, Any]] = None) -> 'ServiceResult[T]':
39
+ """Create an error result with basic error information."""
40
+ return cls(success=False, message=message, error_code=error_code, error_details=error_details)
41
+
42
+ @classmethod
43
+ def exception_result(cls, exception: Exception, error_code: Optional[str] = None,
44
+ context: Optional[str] = None) -> 'ServiceResult[T]':
45
+ """Create an error result from an exception with full stack trace logging."""
46
+
47
+ # Get the full stack trace
48
+ stack_trace = traceback.format_exc()
49
+
50
+ # Create detailed error message
51
+ error_message = f"{type(exception).__name__}: {str(exception)}"
52
+ if context:
53
+ error_message = f"{context} - {error_message}"
54
+
55
+ # Prepare error details
56
+ error_details = {
57
+ 'exception_type': type(exception).__name__,
58
+ 'exception_message': str(exception),
59
+ 'context': context,
60
+ 'timestamp': datetime.now().isoformat()
61
+ }
62
+
63
+ # Log the full error with stack trace to CloudWatch
64
+ logger.error(
65
+ f"Service operation failed: {error_message}\n"
66
+ f"Context: {context or 'None'}\n"
67
+ f"Exception Type: {type(exception).__name__}\n"
68
+ f"Exception Message: {str(exception)}\n"
69
+ f"Stack Trace:\n{stack_trace}",
70
+ extra={
71
+ 'error_code': error_code,
72
+ 'exception_type': type(exception).__name__,
73
+ 'context': context,
74
+ 'stack_trace': stack_trace
75
+ }
76
+ )
77
+
78
+ # Also print to console for immediate visibility
79
+ print(f"\n🚨 SERVICE ERROR: {error_message}")
80
+ print(f"šŸ“ Context: {context or 'None'}")
81
+ print(f"šŸ” Exception Type: {type(exception).__name__}")
82
+ print(f"šŸ“ Stack Trace:")
83
+ print(stack_trace)
84
+ print("" + "="*80 + "")
85
+
86
+ return cls(
87
+ success=False,
88
+ message=error_message,
89
+ error_code=error_code or 'INTERNAL_ERROR',
90
+ error_details=error_details,
91
+ stack_trace=stack_trace
92
+ )
93
+
94
+ def to_dict(self) -> Dict[str, Any]:
95
+ """Convert result to dictionary for API responses."""
96
+ result = {
97
+ 'success': self.success,
98
+ 'timestamp': self.timestamp.isoformat()
99
+ }
100
+
101
+ if self.success:
102
+ result['data'] = self.data
103
+ else:
104
+ result['error'] = {
105
+ 'message': self.message,
106
+ 'code': self.error_code,
107
+ 'details': self.error_details
108
+ }
109
+ # Only include stack trace in development/debug mode
110
+ # You might want to add a flag to control this
111
+ if self.stack_trace:
112
+ result['error']['stack_trace'] = self.stack_trace
113
+
114
+ return result
115
+
116
+
117
+
118
+
119
+
120
+
121
+
@@ -0,0 +1,64 @@
1
+ """
2
+ Lambda handler decorators for cross-cutting concerns.
3
+
4
+ This module provides composable decorators for Lambda handlers following
5
+ industry best practices (AWS Lambda Powertools, Flask, FastAPI patterns).
6
+
7
+ Usage:
8
+ from geek_cafe_saas_sdk.decorators import (
9
+ handle_errors,
10
+ add_cors,
11
+ parse_request_body,
12
+ inject_service
13
+ )
14
+
15
+ @handle_errors
16
+ @add_cors
17
+ @parse_request_body(convert_case=True)
18
+ @inject_service(MessageService)
19
+ def handler(event, context, service):
20
+ return service.get_by_id(event['pathParameters']['id'])
21
+
22
+ Design Principles:
23
+ - Explicit > Implicit: See all behaviors in handler signature
24
+ - Composable: Stack decorators as needed
25
+ - Single Responsibility: Each decorator has one job
26
+ - Testable: Test handlers and decorators independently
27
+ - Industry Standard: Follows AWS Lambda Powertools patterns
28
+ """
29
+
30
+ from .core import (
31
+ handle_errors,
32
+ add_cors,
33
+ parse_request_body,
34
+ inject_service,
35
+ log_execution,
36
+ validate_path_params,
37
+ extract_user_context_decorator
38
+ )
39
+
40
+ from .auth import (
41
+ require_authorization,
42
+ require_admin,
43
+ require_tenant_admin,
44
+ require_platform_admin,
45
+ public
46
+ )
47
+
48
+ __all__ = [
49
+ # Core decorators
50
+ "handle_errors",
51
+ "add_cors",
52
+ "parse_request_body",
53
+ "inject_service",
54
+ "log_execution",
55
+ "validate_path_params",
56
+ "extract_user_context_decorator",
57
+
58
+ # Auth decorators
59
+ "require_authorization",
60
+ "require_admin",
61
+ "require_tenant_admin",
62
+ "require_platform_admin",
63
+ "public",
64
+ ]