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/users/delete/app.py
2
+
3
+ from typing import Dict, Any
4
+
5
+ from geek_cafe_saas_sdk.services import UserService
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
+ user_service_pool = ServicePool(UserService)
11
+
12
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
13
+ """
14
+ Lambda handler for deleting a user by its ID.
15
+
16
+ Args:
17
+ event: API Gateway event
18
+ context: Lambda context
19
+ injected_service: Optional UserService for testing
20
+ """
21
+ try:
22
+ user_service = injected_service if injected_service else user_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("User ID is required in the path.", "VALIDATION_ERROR", 400)
29
+
30
+ result = user_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="User 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/users/get/app.py
2
+
3
+ from typing import Dict, Any
4
+
5
+ from geek_cafe_saas_sdk.services import UserService
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
+ user_service_pool = ServicePool(UserService)
11
+
12
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
13
+ """
14
+ Lambda handler for retrieving a single user by its ID.
15
+
16
+ Args:
17
+ event: API Gateway event
18
+ context: Lambda context
19
+ injected_service: Optional UserService for testing
20
+ """
21
+ try:
22
+ user_service = injected_service if injected_service else user_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("User ID is required in the path.", "VALIDATION_ERROR", 400)
29
+
30
+ result = user_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/users/list/app.py
2
+
3
+ from typing import Dict, Any
4
+
5
+ from geek_cafe_saas_sdk.services import UserService
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
+ user_service_pool = ServicePool(UserService)
11
+
12
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
13
+ """
14
+ Lambda handler for listing users with optional filters.
15
+
16
+ Args:
17
+ event: API Gateway event
18
+ context: Lambda context
19
+ injected_service: Optional UserService for testing
20
+ """
21
+ try:
22
+ user_service = injected_service if injected_service else user_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
+ # UserService has list_by_tenant method
28
+ result = user_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/users/update/app.py
2
+
3
+ import json
4
+ from typing import Dict, Any
5
+
6
+ from geek_cafe_saas_sdk.services import UserService
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
+ user_service_pool = ServicePool(UserService)
12
+
13
+ def handler(event: Dict[str, Any], context: object, injected_service=None) -> Dict[str, Any]:
14
+ """
15
+ Lambda handler for updating an existing user.
16
+
17
+ Args:
18
+ event: API Gateway event
19
+ context: Lambda context
20
+ injected_service: Optional UserService for testing
21
+ """
22
+ try:
23
+ user_service = injected_service if injected_service else user_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("User ID is required in the path.", "VALIDATION_ERROR", 400)
31
+
32
+ result = user_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,13 @@
1
+ # Auth Domain Models
2
+
3
+ from .user import User
4
+ from .permission import Permission
5
+ from .role import Role
6
+ from .resource_permission import ResourcePermission
7
+
8
+ __all__ = [
9
+ "User",
10
+ "Permission",
11
+ "Role",
12
+ "ResourcePermission",
13
+ ]
@@ -0,0 +1,134 @@
1
+ """
2
+ Copyright 2024-2025 Geek Cafe, LLC
3
+ MIT License. See Project Root for the license information.
4
+
5
+ Permission model for fine-grained access control.
6
+ Supports extensible permission definitions.
7
+ """
8
+
9
+ from typing import Dict, Any
10
+ from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
11
+ from geek_cafe_saas_sdk.models.base_model import BaseModel
12
+
13
+
14
+ class Permission(BaseModel):
15
+ """
16
+ Permission definition model.
17
+
18
+ Defines individual permissions that can be granted to roles or users.
19
+ Extensible - applications can register custom permissions.
20
+
21
+ Examples:
22
+ - events:read, events:write, events:delete
23
+ - chat:send_message, chat:manage_channel
24
+ - analytics:view_dashboard
25
+
26
+ Access Patterns:
27
+ - Get permission by code (primary key)
28
+ - List all permissions (scan/query all)
29
+ - List permissions by category (GSI1)
30
+ """
31
+
32
+ def __init__(self):
33
+ super().__init__()
34
+
35
+ # Core fields
36
+ self._code: str | None = None # Unique code: "events:read"
37
+ self._name: str | None = None # Display name: "Read Events"
38
+ self._description: str | None = None
39
+ self._category: str | None = None # "events", "chat", "analytics", etc.
40
+
41
+ # Metadata
42
+ self._is_system: bool = True # System permissions can't be deleted
43
+ self._metadata: Dict[str, Any] = {}
44
+
45
+ self._setup_indexes()
46
+
47
+ def _setup_indexes(self):
48
+ """Setup DynamoDB indexes for permission queries."""
49
+
50
+ # Primary index: permission by code
51
+ primary: DynamoDBIndex = DynamoDBIndex()
52
+ primary.name = "primary"
53
+ primary.partition_key.attribute_name = "pk"
54
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(
55
+ ("permission", self.code)
56
+ )
57
+ primary.sort_key.attribute_name = "sk"
58
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(
59
+ ("permission", self.code)
60
+ )
61
+ self.indexes.add_primary(primary)
62
+
63
+ # GSI1: Permissions by category
64
+ gsi: DynamoDBIndex = DynamoDBIndex()
65
+ gsi.name = "gsi1"
66
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
67
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
68
+ ("permission_category", self.category or "uncategorized")
69
+ )
70
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
71
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
72
+ ("code", self.code)
73
+ )
74
+ self.indexes.add_secondary(gsi)
75
+
76
+ # Code
77
+ @property
78
+ def code(self) -> str | None:
79
+ """Permission code (e.g., 'events:read')."""
80
+ return self._code
81
+
82
+ @code.setter
83
+ def code(self, value: str | None):
84
+ self._code = value
85
+
86
+ # Name
87
+ @property
88
+ def name(self) -> str | None:
89
+ """Display name."""
90
+ return self._name
91
+
92
+ @name.setter
93
+ def name(self, value: str | None):
94
+ self._name = value
95
+
96
+ # Description
97
+ @property
98
+ def description(self) -> str | None:
99
+ """Permission description."""
100
+ return self._description
101
+
102
+ @description.setter
103
+ def description(self, value: str | None):
104
+ self._description = value
105
+
106
+ # Category
107
+ @property
108
+ def category(self) -> str | None:
109
+ """Permission category (e.g., 'events', 'chat')."""
110
+ return self._category
111
+
112
+ @category.setter
113
+ def category(self, value: str | None):
114
+ self._category = value
115
+
116
+ # Is System
117
+ @property
118
+ def is_system(self) -> bool:
119
+ """Whether this is a system permission (cannot be deleted)."""
120
+ return self._is_system
121
+
122
+ @is_system.setter
123
+ def is_system(self, value: bool):
124
+ self._is_system = value
125
+
126
+ # Metadata
127
+ @property
128
+ def metadata(self) -> Dict[str, Any]:
129
+ """Additional metadata."""
130
+ return self._metadata
131
+
132
+ @metadata.setter
133
+ def metadata(self, value: Dict[str, Any]):
134
+ self._metadata = value if value else {}
@@ -0,0 +1,245 @@
1
+ """
2
+ Copyright 2024-2025 Geek Cafe, LLC
3
+ MIT License. See Project Root for the license information.
4
+
5
+ ResourcePermission model for ABAC (Attribute-Based Access Control).
6
+ Grants specific permissions to users on specific resources.
7
+ """
8
+
9
+ from typing import List, Dict, Any
10
+ from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
11
+ from geek_cafe_saas_sdk.models.base_model import BaseModel
12
+
13
+
14
+ class ResourcePermission(BaseModel):
15
+ """
16
+ Resource-level permission grant.
17
+
18
+ Allows granting specific permissions to users on specific resources.
19
+ Supports resource sharing and delegation.
20
+
21
+ Examples:
22
+ - Grant "events:write" to user-456 on event-789
23
+ - Grant "chat:admin" to user-123 on channel-abc
24
+ - Grant "analytics:read" to user-999 on tenant-def
25
+
26
+ Access Patterns:
27
+ - Get grants for user on resource (GSI1: user + resource)
28
+ - List all user's grants (GSI2: user only)
29
+ - List all grants on resource (GSI3: resource only)
30
+ - Check specific grant (primary key)
31
+ """
32
+
33
+ def __init__(self):
34
+ super().__init__()
35
+
36
+ # Who has access
37
+ self._user_id: str | None = None
38
+ self._tenant_id: str | None = None
39
+
40
+ # What they can access
41
+ self._resource_type: str | None = None # "event", "chat_channel", "group", etc.
42
+ self._resource_id: str | None = None
43
+
44
+ # What they can do
45
+ self._permissions: List[str] = [] # ["read", "write", "delete"]
46
+
47
+ # Context
48
+ self._granted_by: str | None = None # User ID who granted this
49
+ self._granted_at: int | None = None # UTC timestamp
50
+ self._expires_at: int | None = None # Optional expiration
51
+ self._reason: str | None = None # Why granted
52
+
53
+ # Metadata
54
+ self._metadata: Dict[str, Any] = {}
55
+
56
+ self._setup_indexes()
57
+
58
+ def _setup_indexes(self):
59
+ """Setup DynamoDB indexes for resource permission queries."""
60
+
61
+ # Primary index: specific grant
62
+ primary: DynamoDBIndex = DynamoDBIndex()
63
+ primary.name = "primary"
64
+ primary.partition_key.attribute_name = "pk"
65
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(
66
+ ("user", self.user_id),
67
+ ("resource", self.resource_type),
68
+ ("resource_id", self.resource_id)
69
+ )
70
+ primary.sort_key.attribute_name = "sk"
71
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(
72
+ ("grant", self.id)
73
+ )
74
+ self.indexes.add_primary(primary)
75
+
76
+ # GSI1: User's grants on a specific resource
77
+ gsi: DynamoDBIndex = DynamoDBIndex()
78
+ gsi.name = "gsi1"
79
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
80
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
81
+ ("user", self.user_id)
82
+ )
83
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
84
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
85
+ ("resource", self.resource_type),
86
+ ("resource_id", self.resource_id)
87
+ )
88
+ self.indexes.add_secondary(gsi)
89
+
90
+ # GSI2: All grants for a user (across all resources)
91
+ gsi: DynamoDBIndex = DynamoDBIndex()
92
+ gsi.name = "gsi2"
93
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
94
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
95
+ ("user_grants", self.user_id)
96
+ )
97
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
98
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
99
+ ("resource", self.resource_type),
100
+ ("resource_id", self.resource_id),
101
+ ("grant", self.id)
102
+ )
103
+ self.indexes.add_secondary(gsi)
104
+
105
+ # GSI3: All grants on a resource (who has access)
106
+ gsi: DynamoDBIndex = DynamoDBIndex()
107
+ gsi.name = "gsi3"
108
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
109
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
110
+ ("resource", self.resource_type),
111
+ ("resource_id", self.resource_id)
112
+ )
113
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
114
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
115
+ ("user", self.user_id)
116
+ )
117
+ self.indexes.add_secondary(gsi)
118
+
119
+ # GSI4: Grants by tenant (for admin view)
120
+ gsi: DynamoDBIndex = DynamoDBIndex()
121
+ gsi.name = "gsi4"
122
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
123
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
124
+ ("tenant", self.tenant_id)
125
+ )
126
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
127
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
128
+ ("resource", self.resource_type),
129
+ ("resource_id", self.resource_id),
130
+ ("user", self.user_id)
131
+ )
132
+ self.indexes.add_secondary(gsi)
133
+
134
+ # User ID
135
+ @property
136
+ def user_id(self) -> str | None:
137
+ """User being granted access."""
138
+ return self._user_id
139
+
140
+ @user_id.setter
141
+ def user_id(self, value: str | None):
142
+ self._user_id = value
143
+
144
+ # Tenant ID
145
+ @property
146
+ def tenant_id(self) -> str | None:
147
+ """Tenant context for the grant."""
148
+ return self._tenant_id
149
+
150
+ @tenant_id.setter
151
+ def tenant_id(self, value: str | None):
152
+ self._tenant_id = value
153
+
154
+ # Resource Type
155
+ @property
156
+ def resource_type(self) -> str | None:
157
+ """Type of resource (e.g., 'event', 'chat_channel')."""
158
+ return self._resource_type
159
+
160
+ @resource_type.setter
161
+ def resource_type(self, value: str | None):
162
+ self._resource_type = value
163
+
164
+ # Resource ID
165
+ @property
166
+ def resource_id(self) -> str | None:
167
+ """ID of the specific resource."""
168
+ return self._resource_id
169
+
170
+ @resource_id.setter
171
+ def resource_id(self, value: str | None):
172
+ self._resource_id = value
173
+
174
+ # Permissions
175
+ @property
176
+ def permissions(self) -> List[str]:
177
+ """Permissions granted (e.g., ['read', 'write'])."""
178
+ return self._permissions
179
+
180
+ @permissions.setter
181
+ def permissions(self, value: List[str]):
182
+ self._permissions = value if value else []
183
+
184
+ # Granted By
185
+ @property
186
+ def granted_by(self) -> str | None:
187
+ """User ID who granted this permission."""
188
+ return self._granted_by
189
+
190
+ @granted_by.setter
191
+ def granted_by(self, value: str | None):
192
+ self._granted_by = value
193
+
194
+ # Granted At
195
+ @property
196
+ def granted_at(self) -> int | None:
197
+ """UTC timestamp when granted."""
198
+ return self._granted_at
199
+
200
+ @granted_at.setter
201
+ def granted_at(self, value: int | None):
202
+ self._granted_at = value
203
+
204
+ # Expires At
205
+ @property
206
+ def expires_at(self) -> int | None:
207
+ """UTC timestamp when grant expires (None = never)."""
208
+ return self._expires_at
209
+
210
+ @expires_at.setter
211
+ def expires_at(self, value: int | None):
212
+ self._expires_at = value
213
+
214
+ # Reason
215
+ @property
216
+ def reason(self) -> str | None:
217
+ """Reason for granting permission."""
218
+ return self._reason
219
+
220
+ @reason.setter
221
+ def reason(self, value: str | None):
222
+ self._reason = value
223
+
224
+ # Metadata
225
+ @property
226
+ def metadata(self) -> Dict[str, Any]:
227
+ """Additional metadata."""
228
+ return self._metadata
229
+
230
+ @metadata.setter
231
+ def metadata(self, value: Dict[str, Any]):
232
+ self._metadata = value if value else {}
233
+
234
+ # Helper Methods
235
+
236
+ def is_expired(self) -> bool:
237
+ """Check if grant has expired."""
238
+ if self.expires_at is None:
239
+ return False
240
+ import time
241
+ return time.time() > self.expires_at
242
+
243
+ def has_permission(self, permission: str) -> bool:
244
+ """Check if grant includes specific permission."""
245
+ return permission in self.permissions