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,127 @@
1
+ """
2
+ Lambda handler for updating chat messages.
3
+
4
+ Supports multiple operations:
5
+ - Edit message content
6
+ - Add/remove reactions
7
+ """
8
+
9
+ from typing import Dict, Any
10
+ from geek_cafe_saas_sdk.lambda_handlers import create_handler
11
+ from geek_cafe_saas_sdk.domains.messaging.services import ChatMessageService
12
+
13
+ # Factory creates handler (defaults to secure auth)
14
+ handler_wrapper = create_handler(
15
+ service_class=ChatMessageService,
16
+ require_body=True,
17
+ convert_case=True
18
+ )
19
+
20
+
21
+ def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
22
+ """
23
+ Update a chat message.
24
+
25
+ Args:
26
+ event: Lambda event from API Gateway
27
+ context: Lambda context
28
+ injected_service: Optional ChatMessageService for testing
29
+
30
+ Path parameters:
31
+ id: Chat message ID
32
+
33
+ Expected body (camelCase from frontend):
34
+ {
35
+ "action": "update" | "add_reaction" | "remove_reaction",
36
+
37
+ // For action="update":
38
+ "content": "Updated message content",
39
+
40
+ // For action="add_reaction":
41
+ "emoji": "👍",
42
+
43
+ // For action="remove_reaction":
44
+ "emoji": "👍"
45
+ }
46
+
47
+ Returns 200 with updated chat message
48
+ """
49
+ return handler_wrapper.execute(event, context, update_chat_message, injected_service)
50
+
51
+
52
+ def update_chat_message(
53
+ event: Dict[str, Any],
54
+ service: ChatMessageService,
55
+ user_context: Dict[str, str]
56
+ ) -> Any:
57
+ """
58
+ Business logic for updating chat messages.
59
+
60
+ Routes to different service methods based on action parameter.
61
+ """
62
+ # Extract path parameter
63
+ path_params = event.get("pathParameters") or {}
64
+ message_id = path_params.get("id")
65
+
66
+ if not message_id:
67
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
68
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError
69
+ return ServiceResult.exception_result(
70
+ ValidationError("Message ID is required in path")
71
+ )
72
+
73
+ payload = event["parsed_body"]
74
+ action = payload.get("action", "update")
75
+
76
+ user_id = user_context.get("user_id")
77
+ tenant_id = user_context.get("tenant_id")
78
+
79
+ # Route to appropriate service method based on action
80
+
81
+ if action == "add_reaction":
82
+ # Add a reaction to the message
83
+ emoji = payload.get("emoji")
84
+ if not emoji:
85
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
86
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError
87
+ return ServiceResult.exception_result(
88
+ ValidationError("emoji is required for add_reaction action")
89
+ )
90
+
91
+ return service.add_reaction(
92
+ message_id=message_id,
93
+ tenant_id=tenant_id,
94
+ user_id=user_id,
95
+ emoji=emoji
96
+ )
97
+
98
+ elif action == "remove_reaction":
99
+ # Remove a reaction from the message
100
+ emoji = payload.get("emoji")
101
+ if not emoji:
102
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
103
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError
104
+ return ServiceResult.exception_result(
105
+ ValidationError("emoji is required for remove_reaction action")
106
+ )
107
+
108
+ return service.remove_reaction(
109
+ message_id=message_id,
110
+ tenant_id=tenant_id,
111
+ user_id=user_id,
112
+ emoji=emoji
113
+ )
114
+
115
+ else:
116
+ # Edit message content (only sender can edit)
117
+ updates = {}
118
+
119
+ if "content" in payload:
120
+ updates["content"] = payload["content"]
121
+
122
+ return service.update(
123
+ resource_id=message_id,
124
+ tenant_id=tenant_id,
125
+ user_id=user_id,
126
+ updates=updates
127
+ )
@@ -0,0 +1,94 @@
1
+ """
2
+ Lambda handler for creating contact threads.
3
+
4
+ Supports both guest contact forms (API key auth) and authenticated users.
5
+ Auth strategy is controlled by AUTH_TYPE environment variable:
6
+ - AUTH_TYPE=api_key - For public contact forms (validates x-api-key)
7
+ - AUTH_TYPE=secure - For authenticated app users (API Gateway authorizer)
8
+ """
9
+
10
+ from typing import Dict, Any
11
+ from geek_cafe_saas_sdk.lambda_handlers import create_handler
12
+ from geek_cafe_saas_sdk.domains.messaging.services import ContactThreadService
13
+
14
+ # Factory automatically selects handler based on AUTH_TYPE env var
15
+ handler_wrapper = create_handler(
16
+ service_class=ContactThreadService,
17
+ require_body=True,
18
+ convert_case=True
19
+ )
20
+
21
+
22
+ def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
23
+ """
24
+ Create a new contact thread.
25
+
26
+ Two deployment modes:
27
+
28
+ 1. Public Contact Form (AUTH_TYPE=api_key):
29
+ - Validates x-api-key header
30
+ - Creates contact from guest/anonymous user
31
+ - Used for website contact forms
32
+
33
+ 2. Authenticated App (AUTH_TYPE=secure):
34
+ - Trusts API Gateway Cognito authorizer
35
+ - Creates contact from logged-in user
36
+ - Used for in-app support tickets
37
+
38
+ Args:
39
+ event: Lambda event from API Gateway
40
+ context: Lambda context
41
+ injected_service: Optional ContactThreadService for testing
42
+
43
+ Expected body (camelCase from frontend):
44
+ {
45
+ "subject": "Contact inquiry",
46
+ "sender": {
47
+ "id": "guest-session-xyz",
48
+ "name": "John Doe",
49
+ "email": "john@example.com"
50
+ },
51
+ "initialMessage": "Message content",
52
+ "inboxId": "support" | "sales" | "billing",
53
+ "priority": "low" | "medium" | "high" | "urgent",
54
+ "source": "web" | "mobile" | "api"
55
+ }
56
+
57
+ Returns 201 with created contact thread
58
+ """
59
+ return handler_wrapper.execute(event, context, create_contact_thread, injected_service)
60
+
61
+
62
+ def create_contact_thread(
63
+ event: Dict[str, Any],
64
+ service: ContactThreadService,
65
+ user_context: Dict[str, str]
66
+ ) -> Any:
67
+ """
68
+ Business logic for creating contact threads.
69
+
70
+ Handles both guest users (API key) and authenticated users (Cognito).
71
+ """
72
+ payload = event["parsed_body"]
73
+
74
+ # Determine tenant and user ID
75
+ # For authenticated users, use token data
76
+ # For guest contact forms, use payload data
77
+ tenant_id = user_context.get("tenant_id") or payload.get("tenant_id", "default")
78
+
79
+ authenticated_user_id = user_context.get("user_id")
80
+ if authenticated_user_id:
81
+ user_id = authenticated_user_id
82
+ else:
83
+ # For API key requests, allow userId from sender
84
+ sender = payload.get("sender", {})
85
+ user_id = sender.get("id", "anonymous")
86
+
87
+ # Create the contact thread
88
+ result = service.create(
89
+ tenant_id=tenant_id,
90
+ user_id=user_id,
91
+ payload=payload
92
+ )
93
+
94
+ return result
@@ -0,0 +1,66 @@
1
+ """
2
+ Lambda handler for deleting (soft delete) contact threads.
3
+
4
+ Requires authentication and appropriate access permissions.
5
+ """
6
+
7
+ from typing import Dict, Any
8
+ from geek_cafe_saas_sdk.lambda_handlers import create_handler
9
+ from geek_cafe_saas_sdk.domains.messaging.services import ContactThreadService
10
+
11
+ # Factory creates handler (defaults to secure auth)
12
+ handler_wrapper = create_handler(
13
+ service_class=ContactThreadService,
14
+ require_body=False
15
+ )
16
+
17
+
18
+ def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
19
+ """
20
+ Delete (soft delete) a contact thread.
21
+
22
+ Args:
23
+ event: Lambda event from API Gateway
24
+ context: Lambda context
25
+ injected_service: Optional ContactThreadService for testing
26
+
27
+ Path parameters:
28
+ id: Contact thread ID
29
+
30
+ Returns 200 with success boolean
31
+ """
32
+ return handler_wrapper.execute(event, context, delete_contact_thread, injected_service)
33
+
34
+
35
+ def delete_contact_thread(
36
+ event: Dict[str, Any],
37
+ service: ContactThreadService,
38
+ user_context: Dict[str, str]
39
+ ) -> Any:
40
+ """
41
+ Business logic for deleting a contact thread.
42
+
43
+ Performs soft delete (sets deleted timestamp).
44
+ """
45
+ # Extract path parameter
46
+ path_params = event.get("pathParameters") or {}
47
+ thread_id = path_params.get("id")
48
+
49
+ if not thread_id:
50
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
51
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError
52
+ return ServiceResult.exception_result(
53
+ ValidationError("Thread ID is required in path")
54
+ )
55
+
56
+ user_id = user_context.get("user_id")
57
+ tenant_id = user_context.get("tenant_id")
58
+ user_inboxes = user_context.get("inboxes", [])
59
+
60
+ # Delete the contact thread
61
+ return service.delete(
62
+ resource_id=thread_id,
63
+ tenant_id=tenant_id,
64
+ user_id=user_id,
65
+ user_inboxes=user_inboxes
66
+ )
@@ -0,0 +1,67 @@
1
+ """
2
+ Lambda handler for getting a contact thread by ID.
3
+
4
+ Requires authentication (defaults to secure mode).
5
+ """
6
+
7
+ from typing import Dict, Any
8
+ from geek_cafe_saas_sdk.lambda_handlers import create_handler
9
+ from geek_cafe_saas_sdk.domains.messaging.services import ContactThreadService
10
+
11
+ # Factory creates handler (defaults to secure auth)
12
+ handler_wrapper = create_handler(
13
+ service_class=ContactThreadService,
14
+ require_body=False
15
+ )
16
+
17
+
18
+ def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
19
+ """
20
+ Get a contact thread by ID.
21
+
22
+ Args:
23
+ event: Lambda event from API Gateway
24
+ context: Lambda context
25
+ injected_service: Optional ContactThreadService for testing
26
+
27
+ Path parameters:
28
+ id: Contact thread ID
29
+
30
+ Returns 200 with contact thread details
31
+ """
32
+ return handler_wrapper.execute(event, context, get_contact_thread, injected_service)
33
+
34
+
35
+ def get_contact_thread(
36
+ event: Dict[str, Any],
37
+ service: ContactThreadService,
38
+ user_context: Dict[str, str]
39
+ ) -> Any:
40
+ """
41
+ Business logic for getting a contact thread.
42
+
43
+ Access control is enforced via inbox access or sender/assignee checks.
44
+ """
45
+ # Extract path parameter
46
+ path_params = event.get("pathParameters") or {}
47
+ thread_id = path_params.get("id")
48
+
49
+ if not thread_id:
50
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
51
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError
52
+ return ServiceResult.exception_result(
53
+ ValidationError("Thread ID is required in path")
54
+ )
55
+
56
+ # Get user's inbox access
57
+ user_id = user_context.get("user_id")
58
+ tenant_id = user_context.get("tenant_id")
59
+ user_inboxes = user_context.get("inboxes", [])
60
+
61
+ # Get the contact thread with access control
62
+ return service.get_by_id(
63
+ resource_id=thread_id,
64
+ tenant_id=tenant_id,
65
+ user_id=user_id,
66
+ user_inboxes=user_inboxes
67
+ )
@@ -0,0 +1,95 @@
1
+ """
2
+ Lambda handler for listing contact threads.
3
+
4
+ Supports multiple query patterns via query parameters:
5
+ - inbox + status (default): List threads in an inbox by status
6
+ - assigned: List threads assigned to current user
7
+ - sender_email: List all threads from a specific email
8
+ """
9
+
10
+ from typing import Dict, Any
11
+ from geek_cafe_saas_sdk.lambda_handlers import create_handler
12
+ from geek_cafe_saas_sdk.domains.messaging.services import ContactThreadService
13
+
14
+ # Factory creates handler (defaults to secure auth)
15
+ handler_wrapper = create_handler(
16
+ service_class=ContactThreadService,
17
+ require_body=False
18
+ )
19
+
20
+
21
+ def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
22
+ """
23
+ List contact threads based on query parameters.
24
+
25
+ Args:
26
+ event: Lambda event from API Gateway
27
+ context: Lambda context
28
+ injected_service: Optional ContactThreadService for testing
29
+
30
+ Query parameters:
31
+ inbox_id: Inbox ID (support, sales, billing)
32
+ status: Status filter (open, in_progress, resolved, closed)
33
+ assigned: "me" to get threads assigned to current user
34
+ sender_email: Email address to find all threads from sender
35
+ limit: Maximum number of results (default 50)
36
+
37
+ Examples:
38
+ GET /contact-threads?inbox_id=support&status=open
39
+ GET /contact-threads?assigned=me
40
+ GET /contact-threads?sender_email=guest@example.com
41
+
42
+ Returns 200 with list of contact threads
43
+ """
44
+ return handler_wrapper.execute(event, context, list_contact_threads, injected_service)
45
+
46
+
47
+ def list_contact_threads(
48
+ event: Dict[str, Any],
49
+ service: ContactThreadService,
50
+ user_context: Dict[str, str]
51
+ ) -> Any:
52
+ """
53
+ Business logic for listing contact threads.
54
+
55
+ Routes to different service methods based on query parameters.
56
+ """
57
+ query_params = event.get("queryStringParameters") or {}
58
+
59
+ user_id = user_context.get("user_id")
60
+ tenant_id = user_context.get("tenant_id")
61
+ limit = int(query_params.get("limit", "50"))
62
+
63
+ # Route to appropriate service method
64
+
65
+ # Pattern 1: List by assigned user
66
+ if query_params.get("assigned") == "me":
67
+ status = query_params.get("status")
68
+ return service.list_by_assigned_user(
69
+ assigned_to=user_id,
70
+ tenant_id=tenant_id,
71
+ status=status,
72
+ limit=limit
73
+ )
74
+
75
+ # Pattern 2: List by sender email
76
+ if "sender_email" in query_params:
77
+ sender_email = query_params.get("sender_email")
78
+ return service.list_by_sender_email(
79
+ sender_email=sender_email,
80
+ tenant_id=tenant_id,
81
+ limit=limit
82
+ )
83
+
84
+ # Pattern 3: List by inbox and status (default)
85
+ inbox_id = query_params.get("inbox_id", "support")
86
+ status = query_params.get("status", "open")
87
+ priority = query_params.get("priority")
88
+
89
+ return service.list_by_inbox_and_status(
90
+ inbox_id=inbox_id,
91
+ status=status,
92
+ tenant_id=tenant_id,
93
+ priority=priority,
94
+ limit=limit
95
+ )
@@ -0,0 +1,156 @@
1
+ """
2
+ Lambda handler for updating contact threads.
3
+
4
+ Supports multiple update operations:
5
+ - Update general fields (subject, priority, tags, etc.)
6
+ - Add messages to thread
7
+ - Assign to staff member
8
+ - Update status
9
+ """
10
+
11
+ from typing import Dict, Any
12
+ from geek_cafe_saas_sdk.lambda_handlers import create_handler
13
+ from geek_cafe_saas_sdk.domains.messaging.services import ContactThreadService
14
+
15
+ # Factory creates handler (defaults to secure auth)
16
+ handler_wrapper = create_handler(
17
+ service_class=ContactThreadService,
18
+ require_body=True,
19
+ convert_case=True
20
+ )
21
+
22
+
23
+ def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
24
+ """
25
+ Update a contact thread.
26
+
27
+ Args:
28
+ event: Lambda event from API Gateway
29
+ context: Lambda context
30
+ injected_service: Optional ContactThreadService for testing
31
+
32
+ Path parameters:
33
+ id: Contact thread ID
34
+
35
+ Expected body (camelCase from frontend):
36
+ {
37
+ "action": "update" | "add_message" | "assign" | "update_status",
38
+
39
+ // For action="update":
40
+ "subject": "Updated subject",
41
+ "priority": "urgent",
42
+ "tags": ["bug", "urgent"],
43
+
44
+ // For action="add_message":
45
+ "message": {
46
+ "content": "Reply message",
47
+ "senderName": "Support Staff",
48
+ "isStaffReply": true
49
+ },
50
+
51
+ // For action="assign":
52
+ "assignedTo": "staff_user_id",
53
+
54
+ // For action="update_status":
55
+ "status": "resolved"
56
+ }
57
+
58
+ Returns 200 with updated contact thread
59
+ """
60
+ return handler_wrapper.execute(event, context, update_contact_thread, injected_service)
61
+
62
+
63
+ def update_contact_thread(
64
+ event: Dict[str, Any],
65
+ service: ContactThreadService,
66
+ user_context: Dict[str, str]
67
+ ) -> Any:
68
+ """
69
+ Business logic for updating contact threads.
70
+
71
+ Routes to different service methods based on action parameter.
72
+ """
73
+ # Extract path parameter
74
+ path_params = event.get("pathParameters") or {}
75
+ thread_id = path_params.get("id")
76
+
77
+ if not thread_id:
78
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
79
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError
80
+ return ServiceResult.exception_result(
81
+ ValidationError("Thread ID is required in path")
82
+ )
83
+
84
+ payload = event["parsed_body"]
85
+ action = payload.get("action", "update")
86
+
87
+ user_id = user_context.get("user_id")
88
+ tenant_id = user_context.get("tenant_id")
89
+ user_inboxes = user_context.get("inboxes", [])
90
+
91
+ # Route to appropriate service method based on action
92
+
93
+ if action == "add_message":
94
+ # Add a message to the thread
95
+ message_data = payload.get("message", {})
96
+ return service.add_message(
97
+ thread_id=thread_id,
98
+ tenant_id=tenant_id,
99
+ user_id=user_id,
100
+ message_data=message_data,
101
+ user_inboxes=user_inboxes
102
+ )
103
+
104
+ elif action == "assign":
105
+ # Assign thread to a staff member
106
+ assigned_to = payload.get("assigned_to")
107
+ if not assigned_to:
108
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
109
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError
110
+ return ServiceResult.exception_result(
111
+ ValidationError("assigned_to is required for assign action")
112
+ )
113
+
114
+ return service.assign_thread(
115
+ thread_id=thread_id,
116
+ tenant_id=tenant_id,
117
+ user_id=user_id,
118
+ assigned_to=assigned_to,
119
+ user_inboxes=user_inboxes
120
+ )
121
+
122
+ elif action == "update_status":
123
+ # Update thread status
124
+ status = payload.get("status")
125
+ if not status:
126
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
127
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError
128
+ return ServiceResult.exception_result(
129
+ ValidationError("status is required for update_status action")
130
+ )
131
+
132
+ return service.update_status(
133
+ thread_id=thread_id,
134
+ tenant_id=tenant_id,
135
+ user_id=user_id,
136
+ status=status,
137
+ user_inboxes=user_inboxes
138
+ )
139
+
140
+ else:
141
+ # General update (update multiple fields)
142
+ # Extract allowed fields
143
+ updates = {}
144
+ allowed_fields = ['subject', 'status', 'priority', 'assigned_to', 'tags', 'inbox_id']
145
+
146
+ for field in allowed_fields:
147
+ if field in payload:
148
+ updates[field] = payload[field]
149
+
150
+ return service.update(
151
+ resource_id=thread_id,
152
+ tenant_id=tenant_id,
153
+ user_id=user_id,
154
+ updates=updates,
155
+ user_inboxes=user_inboxes
156
+ )
@@ -0,0 +1,13 @@
1
+ # Messaging Domain Models
2
+
3
+ from .chat_channel import ChatChannel
4
+ from .chat_channel_member import ChatChannelMember
5
+ from .chat_message import ChatMessage
6
+ from .contact_thread import ContactThread
7
+
8
+ __all__ = [
9
+ "ChatChannel",
10
+ "ChatChannelMember",
11
+ "ChatMessage",
12
+ "ContactThread",
13
+ ]