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.
- geek_cafe_saas_sdk/__init__.py +9 -0
- geek_cafe_saas_sdk/core/__init__.py +11 -0
- geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
- geek_cafe_saas_sdk/core/error_codes.py +132 -0
- geek_cafe_saas_sdk/core/service_errors.py +19 -0
- geek_cafe_saas_sdk/core/service_result.py +121 -0
- geek_cafe_saas_sdk/decorators/__init__.py +64 -0
- geek_cafe_saas_sdk/decorators/auth.py +373 -0
- geek_cafe_saas_sdk/decorators/core.py +358 -0
- geek_cafe_saas_sdk/domains/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
- geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
- geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
- geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
- geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
- geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
- geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
- geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
- geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
- geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
- geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
- geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
- geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
- geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
- geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
- geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
- geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
- geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
- geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
- geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
- geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
- geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
- geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
- geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
- geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
- geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
- geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
- geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
- geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
- geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
- geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
- geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
- geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
- geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
- geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
- geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
- geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
- geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
- geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
- geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
- geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
- geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
- geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
- geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
- geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
- geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
- geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
- geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
- geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
- geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
- geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
- geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
- geek_cafe_saas_sdk/middleware/__init__.py +36 -0
- geek_cafe_saas_sdk/middleware/auth.py +85 -0
- geek_cafe_saas_sdk/middleware/authorization.py +523 -0
- geek_cafe_saas_sdk/middleware/cors.py +63 -0
- geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
- geek_cafe_saas_sdk/middleware/validation.py +80 -0
- geek_cafe_saas_sdk/models/__init__.py +20 -0
- geek_cafe_saas_sdk/models/base_model.py +233 -0
- geek_cafe_saas_sdk/services/__init__.py +18 -0
- geek_cafe_saas_sdk/services/database_service.py +441 -0
- geek_cafe_saas_sdk/utilities/__init__.py +88 -0
- geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
- geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
- geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
- geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
- geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
- geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
- geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
- geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
- geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
- geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
- geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
- geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
- geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
- geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
- geek_cafe_saas_sdk/utilities/response.py +209 -0
- geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
- 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']
|