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,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,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)
|