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,41 @@
1
+ # src/geek_cafe_saas_sdk/lambda_handlers/communities/create/app.py
2
+
3
+ import json
4
+ from typing import Dict, Any
5
+
6
+ from geek_cafe_saas_sdk.services import CommunityService
7
+ from geek_cafe_saas_sdk.lambda_handlers import ServicePool
8
+ from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response
9
+ from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
10
+
11
+ community_service_pool = ServicePool(CommunityService)
12
+
13
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
14
+ """
15
+ Lambda handler for creating a new community.
16
+
17
+ Args:
18
+ event: API Gateway event
19
+ context: Lambda context
20
+ injected_service: Optional CommunityService for testing
21
+ """
22
+ try:
23
+ # Use injected service (testing) or pool (production)
24
+ community_service = injected_service if injected_service else community_service_pool.get()
25
+ body = LambdaEventUtility.get_body_from_event(event)
26
+ user_id = LambdaEventUtility.get_authenticated_user_id(event)
27
+ tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
28
+
29
+ # Pass all body parameters to the service
30
+ result = community_service.create(
31
+ tenant_id=tenant_id,
32
+ user_id=user_id,
33
+ **body
34
+ )
35
+
36
+ return service_result_to_response(result, success_status=201)
37
+
38
+ except json.JSONDecodeError:
39
+ return error_response("Invalid JSON in request body.", "VALIDATION_ERROR", 400)
40
+ except Exception as e:
41
+ return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
@@ -0,0 +1,41 @@
1
+ # src/geek_cafe_saas_sdk/lambda_handlers/communities/delete/app.py
2
+
3
+ from typing import Dict, Any
4
+
5
+ from geek_cafe_saas_sdk.services import CommunityService
6
+ from geek_cafe_saas_sdk.lambda_handlers import ServicePool
7
+ from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response, success_response
8
+ from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
9
+
10
+ community_service_pool = ServicePool(CommunityService)
11
+
12
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
13
+ """
14
+ Lambda handler for deleting a community.
15
+
16
+ Args:
17
+ event: API Gateway event
18
+ context: Lambda context
19
+ injected_service: Optional CommunityService for testing
20
+ """
21
+ try:
22
+ community_service = injected_service if injected_service else community_service_pool.get()
23
+ user_id = LambdaEventUtility.get_authenticated_user_id(event)
24
+ tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
25
+ resource_id = LambdaEventUtility.get_value_from_path_parameters(event, 'id')
26
+
27
+ if not resource_id:
28
+ return error_response("Community ID is required in the path.", "VALIDATION_ERROR", 400)
29
+
30
+ result = community_service.delete(
31
+ resource_id=resource_id,
32
+ tenant_id=tenant_id,
33
+ user_id=user_id
34
+ )
35
+
36
+ if result.success:
37
+ return success_response(message="Community deleted successfully", status_code=204)
38
+ return service_result_to_response(result)
39
+
40
+ except Exception as e:
41
+ return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
@@ -0,0 +1,39 @@
1
+ # src/geek_cafe_saas_sdk/lambda_handlers/communities/get/app.py
2
+
3
+ from typing import Dict, Any
4
+
5
+ from geek_cafe_saas_sdk.services import CommunityService
6
+ from geek_cafe_saas_sdk.lambda_handlers import ServicePool
7
+ from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response
8
+ from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
9
+
10
+ community_service_pool = ServicePool(CommunityService)
11
+
12
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
13
+ """
14
+ Lambda handler for getting a community by ID.
15
+
16
+ Args:
17
+ event: API Gateway event
18
+ context: Lambda context
19
+ injected_service: Optional CommunityService for testing
20
+ """
21
+ try:
22
+ community_service = injected_service if injected_service else community_service_pool.get()
23
+ user_id = LambdaEventUtility.get_authenticated_user_id(event)
24
+ tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
25
+ resource_id = LambdaEventUtility.get_value_from_path_parameters(event, 'id')
26
+
27
+ if not resource_id:
28
+ return error_response("Community ID is required in the path.", "VALIDATION_ERROR", 400)
29
+
30
+ result = community_service.get_by_id(
31
+ resource_id=resource_id,
32
+ tenant_id=tenant_id,
33
+ user_id=user_id
34
+ )
35
+
36
+ return service_result_to_response(result)
37
+
38
+ except Exception as e:
39
+ return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
@@ -0,0 +1,36 @@
1
+ # src/geek_cafe_saas_sdk/lambda_handlers/communities/list/app.py
2
+
3
+ from typing import Dict, Any
4
+
5
+ from geek_cafe_saas_sdk.services import CommunityService
6
+ from geek_cafe_saas_sdk.lambda_handlers import ServicePool
7
+ from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response
8
+ from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
9
+
10
+ community_service_pool = ServicePool(CommunityService)
11
+
12
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
13
+ """
14
+ Lambda handler for listing communities.
15
+
16
+ Args:
17
+ event: API Gateway event
18
+ context: Lambda context
19
+ injected_service: Optional CommunityService for testing
20
+ """
21
+ try:
22
+ community_service = injected_service if injected_service else community_service_pool.get()
23
+ user_id = LambdaEventUtility.get_authenticated_user_id(event)
24
+ tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
25
+ query_params = event.get('queryStringParameters', {}) or {}
26
+
27
+ # CommunityService has list_by_tenant method
28
+ result = community_service.list_by_tenant(
29
+ tenant_id=tenant_id,
30
+ user_id=user_id
31
+ )
32
+
33
+ return service_result_to_response(result)
34
+
35
+ except Exception as e:
36
+ return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
@@ -0,0 +1,44 @@
1
+ # src/geek_cafe_saas_sdk/lambda_handlers/communities/update/app.py
2
+
3
+ import json
4
+ from typing import Dict, Any
5
+
6
+ from geek_cafe_saas_sdk.services import CommunityService
7
+ from geek_cafe_saas_sdk.lambda_handlers import ServicePool
8
+ from geek_cafe_saas_sdk.utilities.response import service_result_to_response, error_response
9
+ from geek_cafe_saas_sdk.utilities.lambda_event_utility import LambdaEventUtility
10
+
11
+ community_service_pool = ServicePool(CommunityService)
12
+
13
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
14
+ """
15
+ Lambda handler for updating a community.
16
+
17
+ Args:
18
+ event: API Gateway event
19
+ context: Lambda context
20
+ injected_service: Optional CommunityService for testing
21
+ """
22
+ try:
23
+ community_service = injected_service if injected_service else community_service_pool.get()
24
+ body = LambdaEventUtility.get_body_from_event(event)
25
+ user_id = LambdaEventUtility.get_authenticated_user_id(event)
26
+ tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
27
+ resource_id = LambdaEventUtility.get_value_from_path_parameters(event, 'id')
28
+
29
+ if not resource_id:
30
+ return error_response("Community ID is required in the path.", "VALIDATION_ERROR", 400)
31
+
32
+ result = community_service.update(
33
+ resource_id=resource_id,
34
+ tenant_id=tenant_id,
35
+ user_id=user_id,
36
+ updates=body
37
+ )
38
+
39
+ return service_result_to_response(result)
40
+
41
+ except json.JSONDecodeError:
42
+ return error_response("Invalid JSON in request body.", "VALIDATION_ERROR", 400)
43
+ except Exception as e:
44
+ return error_response(f"An unexpected error occurred: {str(e)}", "INTERNAL_ERROR", 500)
@@ -0,0 +1,6 @@
1
+ # Communities Domain Models
2
+
3
+ from .community import Community
4
+ from .community_member import CommunityMember
5
+
6
+ __all__ = ["Community", "CommunityMember"]
@@ -0,0 +1,326 @@
1
+ from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
2
+ from boto3_assist.utilities.string_utility import StringUtility
3
+ import datetime as dt
4
+ from typing import List, Optional, Dict, Any
5
+ from geek_cafe_saas_sdk.models.base_model import BaseModel
6
+
7
+
8
+ class Community(BaseModel):
9
+ """
10
+ Community model for member-based organizing.
11
+
12
+ Similar to Meetup groups - supports membership management,
13
+ leadership structure, dues, and event organization.
14
+ Represents communities with membership, moderation, and privacy controls.
15
+ """
16
+
17
+ def __init__(self):
18
+ super().__init__()
19
+ self._name: str | None = None
20
+ self._description: str | None = None
21
+ self._category: str | None = None
22
+ self._privacy: str = "public" # public, private
23
+ self._tags: List[str] = []
24
+ self._join_approval: str = "open" # open, approval
25
+ self._requires_dues: bool = False
26
+ self._dues_monthly: float | None = None
27
+ self._dues_yearly: float | None = None
28
+
29
+ # Leadership (kept in-model for fast access)
30
+ self._owner_id: str | None = None
31
+ self._co_owners: List[str] = [] # ~5-10 max
32
+ self._moderators: List[str] = [] # ~10-20 max
33
+
34
+ # Membership stats (cached/denormalized)
35
+ self._member_count: int = 0 # Use CommunityMemberService for actual membership
36
+
37
+ self._setup_indexes()
38
+
39
+ def _setup_indexes(self):
40
+ """Setup DynamoDB indexes for community queries."""
41
+
42
+ # Primary index: communities by ID
43
+ primary: DynamoDBIndex = DynamoDBIndex()
44
+ primary.name = "primary"
45
+ primary.partition_key.attribute_name = "pk"
46
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(("community", self.id))
47
+ primary.sort_key.attribute_name = "sk"
48
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("community", self.id))
49
+ self.indexes.add_primary(primary)
50
+
51
+ ## GSI: 1 - Communities by owner
52
+ gsi: DynamoDBIndex = DynamoDBIndex()
53
+ gsi.name = "gsi1"
54
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
55
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("user", self.owner_id))
56
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
57
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(("model", "community"), ("ts", self.created_utc_ts))
58
+ self.indexes.add_secondary(gsi)
59
+
60
+ ## GSI: 2 - Communities by privacy
61
+ gsi: DynamoDBIndex = DynamoDBIndex()
62
+ gsi.name = "gsi2"
63
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
64
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("privacy", self.privacy))
65
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
66
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(("ts", self.created_utc_ts))
67
+ self.indexes.add_secondary(gsi)
68
+
69
+ ## GSI: 3 - Communities by category
70
+ gsi: DynamoDBIndex = DynamoDBIndex()
71
+ gsi.name = "gsi3"
72
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
73
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("category", self.category))
74
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
75
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(("ts", self.created_utc_ts))
76
+ self.indexes.add_secondary(gsi)
77
+
78
+ ## GSI: 4 - Communities by tenant
79
+ gsi: DynamoDBIndex = DynamoDBIndex()
80
+ gsi.name = "gsi4"
81
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
82
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("tenant", self.tenant_id))
83
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
84
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(("model", "community"), ("ts", self.created_utc_ts))
85
+ self.indexes.add_secondary(gsi)
86
+
87
+ ## GSI: 5 - All communities (for admin queries)
88
+ gsi: DynamoDBIndex = DynamoDBIndex()
89
+ gsi.name = "gsi5"
90
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
91
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("community", "all"))
92
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
93
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(("ts", self.created_utc_ts))
94
+ self.indexes.add_secondary(gsi)
95
+
96
+ @property
97
+ def name(self) -> str | None:
98
+ """Community name."""
99
+ return self._name
100
+
101
+ @name.setter
102
+ def name(self, value: str | None):
103
+ self._name = value
104
+
105
+ @property
106
+ def description(self) -> str | None:
107
+ """Community description."""
108
+ return self._description
109
+
110
+ @description.setter
111
+ def description(self, value: str | None):
112
+ self._description = value
113
+
114
+ @property
115
+ def category(self) -> str | None:
116
+ """Community category."""
117
+ return self._category
118
+
119
+ @category.setter
120
+ def category(self, value: str | None):
121
+ self._category = value
122
+
123
+ @property
124
+ def privacy(self) -> str:
125
+ """Community privacy: public, private."""
126
+ return self._privacy
127
+
128
+ @privacy.setter
129
+ def privacy(self, value: str | None):
130
+ """Set privacy with validation."""
131
+ if value in ["public", "private"]:
132
+ self._privacy = value
133
+ else:
134
+ self._privacy = "public" # default
135
+
136
+ @property
137
+ def tags(self) -> List[str]:
138
+ """Community tags."""
139
+ return self._tags
140
+
141
+ @tags.setter
142
+ def tags(self, value: List[str] | None):
143
+ """Set tags, ensuring it's always a list."""
144
+ if value is None:
145
+ self._tags = []
146
+ elif isinstance(value, list):
147
+ # Limit to 5 tags, max 20 chars each
148
+ self._tags = [tag[:20] for tag in value[:5]]
149
+ else:
150
+ self._tags = []
151
+
152
+ @property
153
+ def join_approval(self) -> str:
154
+ """Join approval setting: open, approval."""
155
+ return self._join_approval
156
+
157
+ @join_approval.setter
158
+ def join_approval(self, value: str | None):
159
+ """Set join approval with validation."""
160
+ if value in ["open", "approval"]:
161
+ self._join_approval = value
162
+ else:
163
+ self._join_approval = "open" # default
164
+
165
+ @property
166
+ def requires_dues(self) -> bool:
167
+ """Whether the community requires dues."""
168
+ return self._requires_dues
169
+
170
+ @requires_dues.setter
171
+ def requires_dues(self, value: bool):
172
+ self._requires_dues = bool(value)
173
+
174
+ @property
175
+ def dues_monthly(self) -> float | None:
176
+ """Monthly dues amount."""
177
+ return self._dues_monthly
178
+
179
+ @dues_monthly.setter
180
+ def dues_monthly(self, value: float | None):
181
+ if value is not None and value >= 0:
182
+ self._dues_monthly = value
183
+ else:
184
+ self._dues_monthly = None
185
+
186
+ @property
187
+ def dues_yearly(self) -> float | None:
188
+ """Yearly dues amount."""
189
+ return self._dues_yearly
190
+
191
+ @dues_yearly.setter
192
+ def dues_yearly(self, value: float | None):
193
+ if value is not None and value >= 0:
194
+ self._dues_yearly = value
195
+ else:
196
+ self._dues_yearly = None
197
+
198
+ @property
199
+ def owner_id(self) -> str | None:
200
+ """Community owner user ID."""
201
+ return self._owner_id
202
+
203
+ @owner_id.setter
204
+ def owner_id(self, value: str | None):
205
+ self._owner_id = value
206
+
207
+ @property
208
+ def co_owners(self) -> List[str]:
209
+ """Co-owner user IDs."""
210
+ return self._co_owners
211
+
212
+ @co_owners.setter
213
+ def co_owners(self, value: List[str] | None):
214
+ """Set co-owners, ensuring it's always a list."""
215
+ if value is None:
216
+ self._co_owners = []
217
+ elif isinstance(value, list):
218
+ self._co_owners = value
219
+ else:
220
+ self._co_owners = []
221
+
222
+ @property
223
+ def moderators(self) -> List[str]:
224
+ """Moderator user IDs."""
225
+ return self._moderators
226
+
227
+ @moderators.setter
228
+ def moderators(self, value: List[str] | None):
229
+ """Set moderators, ensuring it's always a list."""
230
+ if value is None:
231
+ self._moderators = []
232
+ elif isinstance(value, list):
233
+ self._moderators = value
234
+ else:
235
+ self._moderators = []
236
+
237
+ @property
238
+ def member_count(self) -> int:
239
+ """Cached member count. Use CommunityMemberService.get_member_count() for real-time count."""
240
+ return self._member_count
241
+
242
+ @member_count.setter
243
+ def member_count(self, value: int | None):
244
+ """Set cached member count."""
245
+ self._member_count = value if isinstance(value, int) and value >= 0 else 0
246
+
247
+ def get_user_role(self, user_id: str) -> str:
248
+ """
249
+ Get the leadership role of a user in this community.
250
+
251
+ Note: Only checks leadership roles (owner, co-owner, moderator).
252
+ Use CommunityMemberService to check full membership.
253
+ """
254
+ if self.owner_id == user_id:
255
+ return "owner"
256
+ elif user_id in self.co_owners:
257
+ return "co-owner"
258
+ elif user_id in self.moderators:
259
+ return "moderator"
260
+ else:
261
+ return "guest" # Not in leadership - may still be a member
262
+
263
+ def is_user_member(self, user_id: str) -> bool:
264
+ """
265
+ Check if user is a member (owner/co-owner/moderator).
266
+
267
+ Note: For full membership check, use CommunityMemberService.is_member()
268
+ This only checks leadership roles for quick access control.
269
+ """
270
+ return (self.owner_id == user_id or
271
+ user_id in self.co_owners or
272
+ user_id in self.moderators)
273
+
274
+ def is_user_organizer(self, user_id: str) -> bool:
275
+ """Check if user is an organizer (owner or co-owner)."""
276
+ return self.owner_id == user_id or user_id in self.co_owners
277
+
278
+ def is_user_moderator(self, user_id: str) -> bool:
279
+ """Check if user is a moderator or organizer."""
280
+ return self.is_user_organizer(user_id) or user_id in self.moderators
281
+
282
+ def can_user_view(self, user_id: str) -> bool:
283
+ """
284
+ Check if a user can view this community.
285
+
286
+ Basic implementation - privacy logic will be enhanced later.
287
+ """
288
+ if self.privacy == "public":
289
+ return True
290
+ elif self.privacy == "private":
291
+ # Private communities: only members can see
292
+ return self.is_user_member(user_id)
293
+ return False
294
+
295
+ def can_user_manage(self, user_id: str) -> bool:
296
+ """Check if user can manage this community (organizers only)."""
297
+ return self.is_user_organizer(user_id)
298
+
299
+ def increment_member_count(self):
300
+ """Increment cached member count."""
301
+ self._member_count += 1
302
+
303
+ def decrement_member_count(self):
304
+ """Decrement cached member count."""
305
+ if self._member_count > 0:
306
+ self._member_count -= 1
307
+
308
+ def add_moderator(self, user_id: str):
309
+ """Add a user as moderator."""
310
+ if user_id not in self._moderators:
311
+ self._moderators.append(user_id)
312
+
313
+ def remove_moderator(self, user_id: str):
314
+ """Remove a user from moderators."""
315
+ if user_id in self._moderators:
316
+ self._moderators.remove(user_id)
317
+
318
+ def add_co_owner(self, user_id: str):
319
+ """Add a user as co-owner."""
320
+ if user_id not in self._co_owners:
321
+ self._co_owners.append(user_id)
322
+
323
+ def remove_co_owner(self, user_id: str):
324
+ """Remove a user from co-owners."""
325
+ if user_id in self._co_owners:
326
+ self._co_owners.remove(user_id)