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