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,80 @@
1
+ """
2
+ Request validation middleware for Lambda handlers.
3
+ """
4
+
5
+ import json
6
+ import functools
7
+ from ..core.service_errors import ValidationError
8
+ from typing import Any, Callable, Dict, List, Optional
9
+
10
+
11
+ def validate_request_body(
12
+ required_fields: Optional[List[str]] = None,
13
+ optional_fields: Optional[List[str]] = None
14
+ ) -> Callable:
15
+ """
16
+ Decorator that validates request body JSON and required fields.
17
+
18
+ Args:
19
+ required_fields: List of required field names in the request body
20
+ optional_fields: List of optional field names (for documentation/validation)
21
+
22
+ Returns:
23
+ Decorated function that validates request body before calling handler
24
+ """
25
+
26
+ def decorator(handler: Callable) -> Callable:
27
+ @functools.wraps(handler)
28
+ def wrapper(
29
+ event: Dict[str, Any], context: Any, *args, **kwargs
30
+ ) -> Dict[str, Any]:
31
+
32
+ def _create_error_response(error: str, error_code: str, message: str) -> Dict[str, Any]:
33
+ """Helper to create consistent error responses."""
34
+ return {
35
+ "statusCode": 400,
36
+ "headers": {
37
+ "Content-Type": "application/json",
38
+ "Access-Control-Allow-Origin": "*",
39
+ },
40
+ "body": json.dumps({
41
+ "error": error,
42
+ "error_code": error_code,
43
+ "message": message,
44
+ }),
45
+ }
46
+
47
+ # Get and parse request body
48
+ body = event.get("body", "{}")
49
+
50
+ try:
51
+ parsed_body = json.loads(body) if isinstance(body, str) else body
52
+ if parsed_body is None:
53
+ raise json.JSONDecodeError("Body is None", "", 0)
54
+ except json.JSONDecodeError:
55
+ return _create_error_response(
56
+ "Invalid JSON",
57
+ "INVALID_JSON",
58
+ "Request body must be valid JSON"
59
+ )
60
+
61
+ # Validate required fields
62
+ if required_fields:
63
+ missing_fields = [field for field in required_fields if field not in parsed_body]
64
+ if missing_fields:
65
+ field = missing_fields[0] # Return first missing field
66
+ return _create_error_response(
67
+ f"Missing required field: {field}",
68
+ "MISSING_REQUIRED_FIELD",
69
+ f'The field "{field}" is required'
70
+ )
71
+
72
+ # Add parsed body to event for handler use
73
+ event["parsed_body"] = parsed_body
74
+
75
+ # Call the original handler
76
+ return handler(event, context, *args, **kwargs)
77
+
78
+ return wrapper
79
+
80
+ return decorator
@@ -0,0 +1,20 @@
1
+ """
2
+ Copyright 2024-2025 Geek Cafe, LLC
3
+ MIT License. See Project Root for the license information.
4
+
5
+ Geek Cafe SaaS SDK Models
6
+
7
+ NOTE: Models have been reorganized into domain-driven structure.
8
+ Import models directly from their domain modules:
9
+ - geek_cafe_saas_sdk.domains.auth.models
10
+ - geek_cafe_saas_sdk.domains.tenancy.models
11
+ - geek_cafe_saas_sdk.domains.communities.models
12
+ - geek_cafe_saas_sdk.domains.events.models
13
+ - geek_cafe_saas_sdk.domains.messaging.models
14
+ - geek_cafe_saas_sdk.domains.voting.models
15
+ - geek_cafe_saas_sdk.domains.analytics.models
16
+ """
17
+
18
+ from geek_cafe_saas_sdk.models.base_model import BaseModel
19
+
20
+ __all__ = ["BaseModel"]
@@ -0,0 +1,233 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ MIT License. See Project Root for the license information.
4
+ """
5
+
6
+ import datetime as dt
7
+ from boto3_assist.utilities.string_utility import StringUtility
8
+ from boto3_assist.dynamodb.dynamodb_model_base import (
9
+ DynamoDBModelBase,
10
+ )
11
+ from boto3_assist.utilities.serialization_utility import JsonConversions
12
+ from typing import Optional, Dict, Any, Union, List
13
+
14
+
15
+ class BaseModel(DynamoDBModelBase):
16
+ """
17
+ The Base DB Model
18
+ Sets a common set of properties for all models
19
+ """
20
+
21
+ def __init__(self) -> None:
22
+ super().__init__()
23
+ self.id: str | None= None # make the id's sortable
24
+ self.tenant_id: str | None = None
25
+ self.user_id: str | None = None
26
+ self.created_utc_ts: float | None = None
27
+ self.updated_utc_ts: float | None = None
28
+ self.deleted_utc_ts: Optional[float] = None
29
+
30
+ self.__model_version: str = "1.0.0"
31
+ self._metadata: Dict[str, Any] | None = None
32
+
33
+ self._table_name: Optional[str] = None
34
+ self.created_by_id: Optional[str]= None
35
+ self.updated_by_id: Optional[str]= None
36
+ self.deleted_by_id: Optional[str]= None
37
+ self.version: float= 1.0
38
+ self.is_multi_record: bool = False
39
+
40
+ def prep_for_save(self):
41
+ """
42
+ Prepares the model for saving by setting the id and timestamps
43
+ """
44
+ self.id = self.id or StringUtility.generate_sortable_uuid()
45
+ self.created_utc_ts = self.created_utc_ts or dt.datetime.now(dt.UTC).timestamp()
46
+ # always update the updated ts to the latest timestamp
47
+ self.updated_utc_ts = dt.datetime.now(dt.UTC).timestamp()
48
+
49
+ def is_deleted(self) -> bool:
50
+ """
51
+ Returns True if the model is deleted (has a deleted timestamp).
52
+ """
53
+ from decimal import Decimal
54
+ return self.deleted_utc_ts is not None and isinstance(self.deleted_utc_ts, (int, float, Decimal))
55
+
56
+
57
+
58
+ @property
59
+ def model_version(self) -> str:
60
+ """
61
+ Returns the model version for this model
62
+ """
63
+ return self.__model_version
64
+
65
+ @model_version.setter
66
+ def model_version(self, value: str):
67
+ """
68
+ Defines a model version. All will start with the base model
69
+ version, but you can override this as your model changes.
70
+ Use your services to parse the older models correct (if needed)
71
+ Which means a custom mapping of data between versioning for
72
+ backward compatibility
73
+ """
74
+ self.__model_version = value
75
+
76
+ @property
77
+ def model_name(self) -> str:
78
+ """
79
+ Returns the record type for this model
80
+ """
81
+ return StringUtility.camel_to_snake(self.__class__.__name__)
82
+
83
+ @model_name.setter
84
+ def model_name(self, value: str):
85
+ """
86
+ This is read-only but we don't want an error during serialization
87
+ """
88
+ pass
89
+
90
+ @property
91
+ def model_name_plural(self) -> str:
92
+ """
93
+ Returns the record type for this model
94
+ """
95
+ return self.model_name + "s"
96
+
97
+ @model_name_plural.setter
98
+ def model_name_plural(self, value: str):
99
+ """
100
+ This is read-only but we don't want an error during serialization
101
+ """
102
+ pass
103
+
104
+ @property
105
+ def table_name(self) -> str | None:
106
+ """
107
+ Returns the table name for this model.
108
+ This is useful if you create multiple tables
109
+ For a single table design you can leave this as null
110
+ """
111
+ return self._table_name
112
+
113
+ @table_name.setter
114
+ def table_name(self, value: str | None):
115
+ """
116
+ Defines the table name for this model
117
+ """
118
+ self._table_name = value
119
+
120
+
121
+ @property
122
+ def metadata(self) -> Dict[str, Any] | None:
123
+ """
124
+ Returns the metadata for this model
125
+ """
126
+ return self._metadata
127
+
128
+ @metadata.setter
129
+ def metadata(self, value: Dict[str, Any] | None):
130
+ """
131
+ Defines the metadata for this model
132
+ """
133
+
134
+ if value is not None and not isinstance(value, dict):
135
+ raise ValueError("metadata must be a dictionary")
136
+
137
+ self._metadata = value
138
+
139
+ def get_pk_id(self) -> str:
140
+ """
141
+ Returns the fully formed primary key for this model.
142
+ This is typically in the form of "<resource_type>#<guid>"
143
+ """
144
+ pk = self.to_resource_dictionary().get("pk", None)
145
+ if not pk:
146
+ raise ValueError("The primary key is not set")
147
+ return pk
148
+
149
+ def get_sk_id(self) -> str | None:
150
+ """
151
+ Returns the fully formed sort key for this model.
152
+ This is typically in the form of "<resource_type>#<guid>"
153
+ """
154
+ sk = self.to_resource_dictionary().get("sk", None)
155
+
156
+ return sk
157
+
158
+ def to_float_or_none(self, value: Any) -> float | None:
159
+ """
160
+ Converts a value to a float or None
161
+ """
162
+ if isinstance(value, str):
163
+ value = value.strip().replace("$", "").replace(",", "").replace(".", "")
164
+
165
+ if value is None:
166
+ return None
167
+ try:
168
+ return float(value)
169
+ except:
170
+ return None
171
+
172
+ @classmethod
173
+ def load(cls, payload: Dict[str, Any]) -> 'BaseDBModel':
174
+ """
175
+ Create a model instance from a UI payload (camelCase).
176
+ Automatically converts camelCase to snake_case before model creation.
177
+
178
+ Args:
179
+ payload: Dictionary with camelCase keys from the UI
180
+
181
+ Returns:
182
+ Model instance with data loaded from the converted payload
183
+
184
+ Raises:
185
+ ValueError: If payload is None or not a dictionary
186
+ """
187
+ if payload is None:
188
+ raise ValueError("Payload cannot be None")
189
+ if not isinstance(payload, dict):
190
+ raise ValueError(f"Payload must be a dictionary, got {type(payload)}")
191
+
192
+ # Convert camelCase to snake_case
193
+ snake_case_payload = JsonConversions.json_camel_to_snake(payload)
194
+
195
+ # Create instance and load data
196
+ instance = cls()
197
+ instance.load_from_dictionary(snake_case_payload)
198
+ return instance
199
+
200
+ def to_camel_case(self) -> Dict[str, Any]:
201
+ """
202
+ Convert model to UI payload format (camelCase).
203
+ Automatically converts snake_case to camelCase for UI consumption.
204
+
205
+ Returns:
206
+ Dictionary with camelCase keys for the UI
207
+ """
208
+ # Get the model as a dictionary
209
+ model_dict = self.to_dictionary()
210
+
211
+ # Convert snake_case to camelCase
212
+ return JsonConversions.json_snake_to_camel(model_dict)
213
+
214
+ @staticmethod
215
+ def to_snake_case(payload: Dict[str, Any]) -> Dict[str, Any]:
216
+ """
217
+ Static method to convert UI payload to backend format.
218
+ Useful for preprocessing payloads before model operations.
219
+
220
+ Args:
221
+ payload: Dictionary with camelCase keys from the UI
222
+
223
+ Returns:
224
+ Dictionary with snake_case keys for backend processing
225
+ """
226
+ if payload is None:
227
+ raise ValueError("Payload cannot be None")
228
+ if not isinstance(payload, dict):
229
+ raise ValueError(f"Payload must be a dictionary, got {type(payload)}")
230
+
231
+ return JsonConversions.json_camel_to_snake(payload)
232
+
233
+
@@ -0,0 +1,18 @@
1
+ """
2
+ Services Package
3
+ Contains all service classes for Geek Cafe Services
4
+
5
+ NOTE: Services have been reorganized into domain-driven structure.
6
+ Import services directly from their domain modules:
7
+ - geek_cafe_saas_sdk.domains.auth.services
8
+ - geek_cafe_saas_sdk.domains.tenancy.services
9
+ - geek_cafe_saas_sdk.domains.communities.services
10
+ - geek_cafe_saas_sdk.domains.events.services
11
+ - geek_cafe_saas_sdk.domains.messaging.services
12
+ - geek_cafe_saas_sdk.domains.voting.services
13
+ - geek_cafe_saas_sdk.domains.analytics.services
14
+ """
15
+
16
+ from .database_service import DatabaseService
17
+
18
+ __all__ = ['DatabaseService']