django-nativemojo 0.1.10__py3-none-any.whl → 0.1.16__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.
- django_nativemojo-0.1.16.dist-info/METADATA +138 -0
- django_nativemojo-0.1.16.dist-info/RECORD +302 -0
- mojo/__init__.py +1 -1
- mojo/apps/account/management/__init__.py +5 -0
- mojo/apps/account/management/commands/__init__.py +6 -0
- mojo/apps/account/management/commands/serializer_admin.py +651 -0
- mojo/apps/account/migrations/0004_user_avatar.py +20 -0
- mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
- mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
- mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
- mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
- mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
- mojo/apps/account/migrations/0010_group_avatar.py +20 -0
- mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
- mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
- mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
- mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
- mojo/apps/account/models/__init__.py +2 -0
- mojo/apps/account/models/device.py +281 -0
- mojo/apps/account/models/group.py +319 -15
- mojo/apps/account/models/member.py +29 -5
- mojo/apps/account/models/push/__init__.py +4 -0
- mojo/apps/account/models/push/config.py +112 -0
- mojo/apps/account/models/push/delivery.py +93 -0
- mojo/apps/account/models/push/device.py +66 -0
- mojo/apps/account/models/push/template.py +99 -0
- mojo/apps/account/models/user.py +369 -19
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +9 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +100 -6
- mojo/apps/account/services/__init__.py +1 -0
- mojo/apps/account/services/push.py +363 -0
- mojo/apps/aws/migrations/0001_initial.py +206 -0
- mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
- mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
- mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
- mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
- mojo/apps/aws/models/__init__.py +19 -0
- mojo/apps/aws/models/email_attachment.py +99 -0
- mojo/apps/aws/models/email_domain.py +218 -0
- mojo/apps/aws/models/email_template.py +132 -0
- mojo/apps/aws/models/incoming_email.py +197 -0
- mojo/apps/aws/models/mailbox.py +288 -0
- mojo/apps/aws/models/sent_message.py +175 -0
- mojo/apps/aws/rest/__init__.py +7 -0
- mojo/apps/aws/rest/email.py +33 -0
- mojo/apps/aws/rest/email_ops.py +183 -0
- mojo/apps/aws/rest/messages.py +32 -0
- mojo/apps/aws/rest/s3.py +64 -0
- mojo/apps/aws/rest/send.py +101 -0
- mojo/apps/aws/rest/sns.py +403 -0
- mojo/apps/aws/rest/templates.py +19 -0
- mojo/apps/aws/services/__init__.py +32 -0
- mojo/apps/aws/services/email.py +390 -0
- mojo/apps/aws/services/email_ops.py +548 -0
- mojo/apps/docit/__init__.py +6 -0
- mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
- mojo/apps/docit/markdown_plugins/toc.py +12 -0
- mojo/apps/docit/migrations/0001_initial.py +113 -0
- mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
- mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
- mojo/apps/docit/models/__init__.py +17 -0
- mojo/apps/docit/models/asset.py +231 -0
- mojo/apps/docit/models/book.py +227 -0
- mojo/apps/docit/models/page.py +319 -0
- mojo/apps/docit/models/page_revision.py +203 -0
- mojo/apps/docit/rest/__init__.py +10 -0
- mojo/apps/docit/rest/asset.py +17 -0
- mojo/apps/docit/rest/book.py +22 -0
- mojo/apps/docit/rest/page.py +22 -0
- mojo/apps/docit/rest/page_revision.py +17 -0
- mojo/apps/docit/services/__init__.py +11 -0
- mojo/apps/docit/services/docit.py +315 -0
- mojo/apps/docit/services/markdown.py +44 -0
- mojo/apps/fileman/README.md +8 -8
- mojo/apps/fileman/backends/base.py +76 -70
- mojo/apps/fileman/backends/filesystem.py +86 -86
- mojo/apps/fileman/backends/s3.py +409 -108
- mojo/apps/fileman/migrations/0001_initial.py +106 -0
- mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
- mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
- mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
- mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
- mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
- mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
- mojo/apps/fileman/migrations/0008_file_category.py +18 -0
- mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
- mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
- mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
- mojo/apps/fileman/models/__init__.py +1 -5
- mojo/apps/fileman/models/file.py +240 -58
- mojo/apps/fileman/models/manager.py +427 -31
- mojo/apps/fileman/models/rendition.py +118 -0
- mojo/apps/fileman/renderer/__init__.py +111 -0
- mojo/apps/fileman/renderer/audio.py +403 -0
- mojo/apps/fileman/renderer/base.py +205 -0
- mojo/apps/fileman/renderer/document.py +404 -0
- mojo/apps/fileman/renderer/image.py +222 -0
- mojo/apps/fileman/renderer/utils.py +297 -0
- mojo/apps/fileman/renderer/video.py +304 -0
- mojo/apps/fileman/rest/__init__.py +1 -18
- mojo/apps/fileman/rest/upload.py +22 -32
- mojo/apps/fileman/signals.py +58 -0
- mojo/apps/fileman/tasks.py +254 -0
- mojo/apps/fileman/utils/__init__.py +40 -16
- mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
- mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
- mojo/apps/incident/migrations/0007_event_uid.py +18 -0
- mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
- mojo/apps/incident/migrations/0009_incident_status.py +18 -0
- mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
- mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
- mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
- mojo/apps/incident/models/__init__.py +2 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +3 -1
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -1
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/event.py +7 -1
- mojo/apps/incident/rest/ticket.py +43 -0
- mojo/apps/jobs/__init__.py +489 -0
- mojo/apps/jobs/adapters.py +24 -0
- mojo/apps/jobs/cli.py +616 -0
- mojo/apps/jobs/daemon.py +370 -0
- mojo/apps/jobs/examples/sample_jobs.py +376 -0
- mojo/apps/jobs/examples/webhook_examples.py +203 -0
- mojo/apps/jobs/handlers/__init__.py +5 -0
- mojo/apps/jobs/handlers/webhook.py +317 -0
- mojo/apps/jobs/job_engine.py +734 -0
- mojo/apps/jobs/keys.py +203 -0
- mojo/apps/jobs/local_queue.py +363 -0
- mojo/apps/jobs/management/__init__.py +3 -0
- mojo/apps/jobs/management/commands/__init__.py +3 -0
- mojo/apps/jobs/manager.py +1327 -0
- mojo/apps/jobs/migrations/0001_initial.py +97 -0
- mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
- mojo/apps/jobs/models/__init__.py +6 -0
- mojo/apps/jobs/models/job.py +441 -0
- mojo/apps/jobs/rest/__init__.py +2 -0
- mojo/apps/jobs/rest/control.py +466 -0
- mojo/apps/jobs/rest/jobs.py +421 -0
- mojo/apps/jobs/scheduler.py +571 -0
- mojo/apps/jobs/services/__init__.py +6 -0
- mojo/apps/jobs/services/job_actions.py +465 -0
- mojo/apps/jobs/settings.py +209 -0
- mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +7 -1
- mojo/apps/metrics/__init__.py +8 -1
- mojo/apps/metrics/redis_metrics.py +198 -0
- mojo/apps/metrics/rest/__init__.py +3 -0
- mojo/apps/metrics/rest/categories.py +266 -0
- mojo/apps/metrics/rest/helpers.py +48 -0
- mojo/apps/metrics/rest/permissions.py +99 -0
- mojo/apps/metrics/rest/values.py +277 -0
- mojo/apps/metrics/utils.py +19 -2
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +47 -3
- mojo/helpers/aws/__init__.py +45 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/aws/sns.py +461 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/dates.py +18 -0
- mojo/helpers/location/__init__.py +2 -0
- mojo/helpers/location/countries.py +262 -0
- mojo/helpers/location/geolocation.py +196 -0
- mojo/helpers/logit.py +37 -0
- mojo/helpers/redis/__init__.py +2 -0
- mojo/helpers/redis/adapter.py +606 -0
- mojo/helpers/redis/client.py +48 -0
- mojo/helpers/redis/pool.py +225 -0
- mojo/helpers/request.py +8 -0
- mojo/helpers/response.py +14 -2
- mojo/helpers/settings/__init__.py +2 -0
- mojo/helpers/{settings.py → settings/helper.py} +1 -37
- mojo/helpers/settings/parser.py +132 -0
- mojo/middleware/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +10 -0
- mojo/models/rest.py +494 -65
- mojo/models/secrets.py +98 -3
- mojo/serializers/__init__.py +106 -0
- mojo/serializers/core/__init__.py +90 -0
- mojo/serializers/core/cache/__init__.py +121 -0
- mojo/serializers/core/cache/backends.py +518 -0
- mojo/serializers/core/cache/base.py +102 -0
- mojo/serializers/core/cache/disabled.py +181 -0
- mojo/serializers/core/cache/memory.py +287 -0
- mojo/serializers/core/cache/redis.py +533 -0
- mojo/serializers/core/cache/utils.py +454 -0
- mojo/serializers/core/manager.py +550 -0
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/examples/settings.py +322 -0
- mojo/serializers/formats/csv.py +393 -0
- mojo/serializers/formats/localizers.py +509 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +35 -4
- testit/runner.py +23 -6
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- django_nativemojo-0.1.10.dist-info/RECORD +0 -194
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/notify/README.md +0 -91
- mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
- mojo/apps/notify/admin.py +0 -52
- mojo/apps/notify/handlers/example_handlers.py +0 -516
- mojo/apps/notify/handlers/ses/__init__.py +0 -25
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +0 -25
- mojo/apps/notify/handlers/ses/message.py +0 -86
- mojo/apps/notify/management/commands/__init__.py +0 -1
- mojo/apps/notify/management/commands/process_notifications.py +0 -370
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +0 -12
- mojo/apps/notify/models/account.py +0 -128
- mojo/apps/notify/models/attachment.py +0 -24
- mojo/apps/notify/models/bounce.py +0 -68
- mojo/apps/notify/models/complaint.py +0 -40
- mojo/apps/notify/models/inbox.py +0 -113
- mojo/apps/notify/models/inbox_message.py +0 -173
- mojo/apps/notify/models/outbox.py +0 -129
- mojo/apps/notify/models/outbox_message.py +0 -288
- mojo/apps/notify/models/template.py +0 -30
- mojo/apps/notify/providers/aws.py +0 -73
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +0 -2
- mojo/apps/notify/utils/notifications.py +0 -404
- mojo/apps/notify/utils/parsing.py +0 -202
- mojo/apps/notify/utils/render.py +0 -144
- mojo/apps/tasks/README.md +0 -118
- mojo/apps/tasks/__init__.py +0 -11
- mojo/apps/tasks/manager.py +0 -489
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -62
- mojo/apps/tasks/runner.py +0 -174
- mojo/apps/tasks/tq_handlers.py +0 -14
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- mojo/ws4redis/README.md +0 -174
- mojo/ws4redis/__init__.py +0 -2
- mojo/ws4redis/client.py +0 -283
- mojo/ws4redis/connection.py +0 -327
- mojo/ws4redis/exceptions.py +0 -32
- mojo/ws4redis/redis.py +0 -183
- mojo/ws4redis/servers/base.py +0 -86
- mojo/ws4redis/servers/django.py +0 -171
- mojo/ws4redis/servers/uwsgi.py +0 -63
- mojo/ws4redis/settings.py +0 -45
- mojo/ws4redis/utf8validator.py +0 -128
- mojo/ws4redis/websocket.py +0 -403
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
- /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
mojo/helpers/aws/sns.py
ADDED
@@ -0,0 +1,461 @@
|
|
1
|
+
"""
|
2
|
+
AWS SNS Helper Module
|
3
|
+
|
4
|
+
Provides simple interfaces for managing AWS SNS (Simple Notification Service).
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
import logging
|
9
|
+
import boto3
|
10
|
+
import botocore
|
11
|
+
from typing import Dict, List, Optional, Union, Any
|
12
|
+
|
13
|
+
from .client import get_session
|
14
|
+
from mojo.helpers.settings import settings
|
15
|
+
from mojo.helpers import logit
|
16
|
+
|
17
|
+
logger = logit.get_logger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
class SNSTopic:
|
21
|
+
"""
|
22
|
+
Simple interface for managing SNS topics.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, name: str, access_key: Optional[str] = None,
|
26
|
+
secret_key: Optional[str] = None, region: Optional[str] = None):
|
27
|
+
"""
|
28
|
+
Initialize a topic manager for the specified SNS topic.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
name: The topic name
|
32
|
+
access_key: AWS access key, defaults to settings.AWS_KEY
|
33
|
+
secret_key: AWS secret key, defaults to settings.AWS_SECRET
|
34
|
+
region: AWS region, defaults to settings.AWS_REGION if available
|
35
|
+
"""
|
36
|
+
self.name = name
|
37
|
+
self.access_key = access_key or settings.AWS_KEY
|
38
|
+
self.secret_key = secret_key or settings.AWS_SECRET
|
39
|
+
self.region = region or getattr(settings, 'AWS_REGION', 'us-east-1')
|
40
|
+
|
41
|
+
session = get_session(self.access_key, self.secret_key, self.region)
|
42
|
+
self.client = session.client('sns')
|
43
|
+
self.arn = None
|
44
|
+
self.exists = self._check_exists()
|
45
|
+
|
46
|
+
def _check_exists(self) -> bool:
|
47
|
+
"""
|
48
|
+
Check if the topic exists and set ARN if it does.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
True if the topic exists, False otherwise
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
# List topics and check if this one exists
|
55
|
+
response = self.client.list_topics()
|
56
|
+
|
57
|
+
for topic in response.get('Topics', []):
|
58
|
+
topic_arn = topic['TopicArn']
|
59
|
+
if topic_arn.split(':')[-1] == self.name:
|
60
|
+
self.arn = topic_arn
|
61
|
+
return True
|
62
|
+
|
63
|
+
return False
|
64
|
+
except botocore.exceptions.ClientError as e:
|
65
|
+
logger.error(f"Error checking topic existence: {e}")
|
66
|
+
return False
|
67
|
+
|
68
|
+
def create(self, display_name: Optional[str] = None,
|
69
|
+
tags: Optional[List[Dict[str, str]]] = None) -> bool:
|
70
|
+
"""
|
71
|
+
Create an SNS topic.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
display_name: Display name for the topic
|
75
|
+
tags: Optional tags for the topic
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
True if topic was created, False if it already exists
|
79
|
+
"""
|
80
|
+
if self.exists:
|
81
|
+
logger.info(f"Topic {self.name} already exists")
|
82
|
+
return False
|
83
|
+
|
84
|
+
try:
|
85
|
+
# Prepare creation parameters
|
86
|
+
create_params = {'Name': self.name}
|
87
|
+
attributes = {}
|
88
|
+
|
89
|
+
if display_name:
|
90
|
+
attributes['DisplayName'] = display_name
|
91
|
+
|
92
|
+
if attributes:
|
93
|
+
create_params['Attributes'] = attributes
|
94
|
+
|
95
|
+
if tags:
|
96
|
+
create_params['Tags'] = tags
|
97
|
+
|
98
|
+
# Create the topic
|
99
|
+
response = self.client.create_topic(**create_params)
|
100
|
+
self.arn = response['TopicArn']
|
101
|
+
self.exists = True
|
102
|
+
return True
|
103
|
+
except botocore.exceptions.ClientError as e:
|
104
|
+
logger.error(f"Failed to create topic {self.name}: {e}")
|
105
|
+
return False
|
106
|
+
|
107
|
+
def delete(self) -> bool:
|
108
|
+
"""
|
109
|
+
Delete the SNS topic.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
True if successfully deleted, False otherwise
|
113
|
+
"""
|
114
|
+
if not self.exists:
|
115
|
+
logger.info(f"Topic {self.name} does not exist")
|
116
|
+
return False
|
117
|
+
|
118
|
+
try:
|
119
|
+
self.client.delete_topic(TopicArn=self.arn)
|
120
|
+
self.arn = None
|
121
|
+
self.exists = False
|
122
|
+
return True
|
123
|
+
except botocore.exceptions.ClientError as e:
|
124
|
+
logger.error(f"Failed to delete topic {self.name}: {e}")
|
125
|
+
return False
|
126
|
+
|
127
|
+
def publish(self, message: str, subject: Optional[str] = None,
|
128
|
+
attributes: Optional[Dict[str, Dict]] = None,
|
129
|
+
message_structure: Optional[str] = None) -> Dict:
|
130
|
+
"""
|
131
|
+
Publish a message to the topic.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
message: Message to publish
|
135
|
+
subject: Optional subject for the message
|
136
|
+
attributes: Optional message attributes
|
137
|
+
message_structure: Optional message structure (e.g., 'json')
|
138
|
+
|
139
|
+
Returns:
|
140
|
+
Response dict containing MessageId if successful
|
141
|
+
"""
|
142
|
+
if not self.exists:
|
143
|
+
logger.warning(f"Topic {self.name} does not exist")
|
144
|
+
return {'Error': 'Topic does not exist'}
|
145
|
+
|
146
|
+
try:
|
147
|
+
# Prepare publish parameters
|
148
|
+
publish_params = {
|
149
|
+
'TopicArn': self.arn,
|
150
|
+
'Message': message
|
151
|
+
}
|
152
|
+
|
153
|
+
if subject:
|
154
|
+
publish_params['Subject'] = subject
|
155
|
+
|
156
|
+
if attributes:
|
157
|
+
publish_params['MessageAttributes'] = attributes
|
158
|
+
|
159
|
+
if message_structure:
|
160
|
+
publish_params['MessageStructure'] = message_structure
|
161
|
+
|
162
|
+
# Publish the message
|
163
|
+
response = self.client.publish(**publish_params)
|
164
|
+
logger.info(f"Message published successfully with MessageId: {response['MessageId']}")
|
165
|
+
return response
|
166
|
+
except botocore.exceptions.ClientError as e:
|
167
|
+
logger.error(f"Failed to publish message to topic {self.name}: {e}")
|
168
|
+
return {'Error': str(e)}
|
169
|
+
|
170
|
+
def set_attributes(self, attributes: Dict[str, str]) -> bool:
|
171
|
+
"""
|
172
|
+
Set topic attributes.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
attributes: Dictionary of attribute name-value pairs
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
True if successful, False otherwise
|
179
|
+
"""
|
180
|
+
if not self.exists:
|
181
|
+
logger.warning(f"Topic {self.name} does not exist")
|
182
|
+
return False
|
183
|
+
|
184
|
+
try:
|
185
|
+
self.client.set_topic_attributes(
|
186
|
+
TopicArn=self.arn,
|
187
|
+
AttributeName=list(attributes.keys())[0], # Can only set one at a time
|
188
|
+
AttributeValue=list(attributes.values())[0]
|
189
|
+
)
|
190
|
+
return True
|
191
|
+
except botocore.exceptions.ClientError as e:
|
192
|
+
logger.error(f"Failed to set attributes for topic {self.name}: {e}")
|
193
|
+
return False
|
194
|
+
|
195
|
+
def get_attributes(self) -> Dict[str, str]:
|
196
|
+
"""
|
197
|
+
Get topic attributes.
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
Dictionary of topic attributes
|
201
|
+
"""
|
202
|
+
if not self.exists:
|
203
|
+
logger.warning(f"Topic {self.name} does not exist")
|
204
|
+
return {}
|
205
|
+
|
206
|
+
try:
|
207
|
+
response = self.client.get_topic_attributes(TopicArn=self.arn)
|
208
|
+
return response.get('Attributes', {})
|
209
|
+
except botocore.exceptions.ClientError as e:
|
210
|
+
logger.error(f"Failed to get attributes for topic {self.name}: {e}")
|
211
|
+
return {}
|
212
|
+
|
213
|
+
def list_subscriptions(self) -> List[Dict]:
|
214
|
+
"""
|
215
|
+
List all subscriptions to this topic.
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
List of subscription dictionaries
|
219
|
+
"""
|
220
|
+
if not self.exists:
|
221
|
+
logger.warning(f"Topic {self.name} does not exist")
|
222
|
+
return []
|
223
|
+
|
224
|
+
try:
|
225
|
+
response = self.client.list_subscriptions_by_topic(TopicArn=self.arn)
|
226
|
+
return response.get('Subscriptions', [])
|
227
|
+
except botocore.exceptions.ClientError as e:
|
228
|
+
logger.error(f"Failed to list subscriptions for topic {self.name}: {e}")
|
229
|
+
return []
|
230
|
+
|
231
|
+
@staticmethod
|
232
|
+
def list_all_topics() -> List[Dict]:
|
233
|
+
"""
|
234
|
+
List all SNS topics.
|
235
|
+
|
236
|
+
Returns:
|
237
|
+
List of topic dictionaries
|
238
|
+
"""
|
239
|
+
client = boto3.client('sns',
|
240
|
+
aws_access_key_id=settings.AWS_KEY,
|
241
|
+
aws_secret_access_key=settings.AWS_SECRET,
|
242
|
+
region_name=getattr(settings, 'AWS_REGION', 'us-east-1'))
|
243
|
+
|
244
|
+
try:
|
245
|
+
response = client.list_topics()
|
246
|
+
return response.get('Topics', [])
|
247
|
+
except botocore.exceptions.ClientError as e:
|
248
|
+
logger.error(f"Failed to list topics: {e}")
|
249
|
+
return []
|
250
|
+
|
251
|
+
|
252
|
+
class SNSSubscription:
|
253
|
+
"""
|
254
|
+
Simple interface for managing SNS subscriptions.
|
255
|
+
"""
|
256
|
+
|
257
|
+
def __init__(self, topic_arn: str, access_key: Optional[str] = None,
|
258
|
+
secret_key: Optional[str] = None, region: Optional[str] = None):
|
259
|
+
"""
|
260
|
+
Initialize a subscription manager for the specified SNS topic.
|
261
|
+
|
262
|
+
Args:
|
263
|
+
topic_arn: The ARN of the topic
|
264
|
+
access_key: AWS access key, defaults to settings.AWS_KEY
|
265
|
+
secret_key: AWS secret key, defaults to settings.AWS_SECRET
|
266
|
+
region: AWS region, defaults to settings.AWS_REGION if available
|
267
|
+
"""
|
268
|
+
self.topic_arn = topic_arn
|
269
|
+
self.access_key = access_key or settings.AWS_KEY
|
270
|
+
self.secret_key = secret_key or settings.AWS_SECRET
|
271
|
+
self.region = region or getattr(settings, 'AWS_REGION', 'us-east-1')
|
272
|
+
|
273
|
+
session = get_session(self.access_key, self.secret_key, self.region)
|
274
|
+
self.client = session.client('sns')
|
275
|
+
|
276
|
+
def subscribe(self, protocol: str, endpoint: str,
|
277
|
+
attributes: Optional[Dict[str, str]] = None,
|
278
|
+
return_subscription_arn: bool = False) -> Dict:
|
279
|
+
"""
|
280
|
+
Subscribe an endpoint to the topic.
|
281
|
+
|
282
|
+
Args:
|
283
|
+
protocol: Protocol to use (http, https, email, sms, sqs, application, lambda)
|
284
|
+
endpoint: Endpoint to subscribe
|
285
|
+
attributes: Optional subscription attributes
|
286
|
+
return_subscription_arn: Whether to return the subscription ARN
|
287
|
+
|
288
|
+
Returns:
|
289
|
+
Response dict containing SubscriptionArn
|
290
|
+
"""
|
291
|
+
try:
|
292
|
+
# Prepare subscription parameters
|
293
|
+
subscribe_params = {
|
294
|
+
'TopicArn': self.topic_arn,
|
295
|
+
'Protocol': protocol,
|
296
|
+
'Endpoint': endpoint,
|
297
|
+
'ReturnSubscriptionArn': return_subscription_arn
|
298
|
+
}
|
299
|
+
|
300
|
+
if attributes:
|
301
|
+
subscribe_params['Attributes'] = attributes
|
302
|
+
|
303
|
+
# Create the subscription
|
304
|
+
response = self.client.subscribe(**subscribe_params)
|
305
|
+
logger.info(f"Subscription created: {response.get('SubscriptionArn')}")
|
306
|
+
return response
|
307
|
+
except botocore.exceptions.ClientError as e:
|
308
|
+
logger.error(f"Failed to create subscription: {e}")
|
309
|
+
return {'Error': str(e)}
|
310
|
+
|
311
|
+
def unsubscribe(self, subscription_arn: str) -> bool:
|
312
|
+
"""
|
313
|
+
Unsubscribe from the topic.
|
314
|
+
|
315
|
+
Args:
|
316
|
+
subscription_arn: ARN of the subscription to delete
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
True if successful, False otherwise
|
320
|
+
"""
|
321
|
+
try:
|
322
|
+
self.client.unsubscribe(SubscriptionArn=subscription_arn)
|
323
|
+
logger.info(f"Subscription {subscription_arn} deleted")
|
324
|
+
return True
|
325
|
+
except botocore.exceptions.ClientError as e:
|
326
|
+
logger.error(f"Failed to delete subscription {subscription_arn}: {e}")
|
327
|
+
return False
|
328
|
+
|
329
|
+
def set_attributes(self, subscription_arn: str,
|
330
|
+
attribute_name: str, attribute_value: str) -> bool:
|
331
|
+
"""
|
332
|
+
Set subscription attributes.
|
333
|
+
|
334
|
+
Args:
|
335
|
+
subscription_arn: ARN of the subscription
|
336
|
+
attribute_name: Name of the attribute to set
|
337
|
+
attribute_value: Value of the attribute
|
338
|
+
|
339
|
+
Returns:
|
340
|
+
True if successful, False otherwise
|
341
|
+
"""
|
342
|
+
try:
|
343
|
+
self.client.set_subscription_attributes(
|
344
|
+
SubscriptionArn=subscription_arn,
|
345
|
+
AttributeName=attribute_name,
|
346
|
+
AttributeValue=attribute_value
|
347
|
+
)
|
348
|
+
return True
|
349
|
+
except botocore.exceptions.ClientError as e:
|
350
|
+
logger.error(f"Failed to set attributes for subscription {subscription_arn}: {e}")
|
351
|
+
return False
|
352
|
+
|
353
|
+
def get_attributes(self, subscription_arn: str) -> Dict[str, str]:
|
354
|
+
"""
|
355
|
+
Get subscription attributes.
|
356
|
+
|
357
|
+
Args:
|
358
|
+
subscription_arn: ARN of the subscription
|
359
|
+
|
360
|
+
Returns:
|
361
|
+
Dictionary of subscription attributes
|
362
|
+
"""
|
363
|
+
try:
|
364
|
+
response = self.client.get_subscription_attributes(
|
365
|
+
SubscriptionArn=subscription_arn
|
366
|
+
)
|
367
|
+
return response.get('Attributes', {})
|
368
|
+
except botocore.exceptions.ClientError as e:
|
369
|
+
logger.error(f"Failed to get attributes for subscription {subscription_arn}: {e}")
|
370
|
+
return {}
|
371
|
+
|
372
|
+
@staticmethod
|
373
|
+
def list_all_subscriptions() -> List[Dict]:
|
374
|
+
"""
|
375
|
+
List all SNS subscriptions.
|
376
|
+
|
377
|
+
Returns:
|
378
|
+
List of subscription dictionaries
|
379
|
+
"""
|
380
|
+
client = boto3.client('sns',
|
381
|
+
aws_access_key_id=settings.AWS_KEY,
|
382
|
+
aws_secret_access_key=settings.AWS_SECRET,
|
383
|
+
region_name=getattr(settings, 'AWS_REGION', 'us-east-1'))
|
384
|
+
|
385
|
+
try:
|
386
|
+
response = client.list_subscriptions()
|
387
|
+
return response.get('Subscriptions', [])
|
388
|
+
except botocore.exceptions.ClientError as e:
|
389
|
+
logger.error(f"Failed to list subscriptions: {e}")
|
390
|
+
return []
|
391
|
+
|
392
|
+
|
393
|
+
# Utility functions
|
394
|
+
def create_topic_and_subscribe(topic_name: str, protocol: str, endpoint: str,
|
395
|
+
display_name: Optional[str] = None) -> Dict:
|
396
|
+
"""
|
397
|
+
Create a topic and subscribe an endpoint in one operation.
|
398
|
+
|
399
|
+
Args:
|
400
|
+
topic_name: Name of the topic to create
|
401
|
+
protocol: Protocol to use for subscription
|
402
|
+
endpoint: Endpoint to subscribe
|
403
|
+
display_name: Optional display name for the topic
|
404
|
+
|
405
|
+
Returns:
|
406
|
+
Dict with topic ARN and subscription ARN
|
407
|
+
"""
|
408
|
+
result = {}
|
409
|
+
|
410
|
+
# Create the topic
|
411
|
+
topic = SNSTopic(topic_name)
|
412
|
+
if not topic.exists:
|
413
|
+
if not topic.create(display_name=display_name):
|
414
|
+
return {'Error': f"Failed to create topic {topic_name}"}
|
415
|
+
|
416
|
+
result['TopicArn'] = topic.arn
|
417
|
+
|
418
|
+
# Create the subscription
|
419
|
+
subscription = SNSSubscription(topic.arn)
|
420
|
+
response = subscription.subscribe(protocol, endpoint, return_subscription_arn=True)
|
421
|
+
|
422
|
+
if 'Error' in response:
|
423
|
+
result['Error'] = response['Error']
|
424
|
+
else:
|
425
|
+
result['SubscriptionArn'] = response.get('SubscriptionArn')
|
426
|
+
|
427
|
+
return result
|
428
|
+
|
429
|
+
|
430
|
+
def publish_message(topic_name: str, message: str, subject: Optional[str] = None) -> Dict:
|
431
|
+
"""
|
432
|
+
Publish a message to a topic by name.
|
433
|
+
|
434
|
+
Args:
|
435
|
+
topic_name: Name of the topic
|
436
|
+
message: Message to publish
|
437
|
+
subject: Optional message subject
|
438
|
+
|
439
|
+
Returns:
|
440
|
+
Response dict containing MessageId if successful
|
441
|
+
"""
|
442
|
+
topic = SNSTopic(topic_name)
|
443
|
+
|
444
|
+
if not topic.exists:
|
445
|
+
return {'Error': f"Topic {topic_name} does not exist"}
|
446
|
+
|
447
|
+
return topic.publish(message, subject)
|
448
|
+
|
449
|
+
|
450
|
+
def get_topic_arn(topic_name: str) -> Optional[str]:
|
451
|
+
"""
|
452
|
+
Get the ARN for a topic by name.
|
453
|
+
|
454
|
+
Args:
|
455
|
+
topic_name: Name of the topic
|
456
|
+
|
457
|
+
Returns:
|
458
|
+
Topic ARN if found, None otherwise
|
459
|
+
"""
|
460
|
+
topic = SNSTopic(topic_name)
|
461
|
+
return topic.arn if topic.exists else None
|
mojo/helpers/crypto/__init__.py
CHANGED
mojo/helpers/crypto/utils.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
from Crypto.Random import get_random_bytes
|
2
2
|
import string
|
3
|
+
from base64 import b64encode, b64decode
|
4
|
+
import json
|
3
5
|
|
4
6
|
|
5
7
|
def generate_key(bit_size=128):
|
@@ -24,3 +26,16 @@ def random_string(length, allow_digits=True, allow_chars=True, allow_special=Tru
|
|
24
26
|
raise ValueError("At least one character set (digits, chars, special) must be allowed")
|
25
27
|
random_bytes = get_random_bytes(length)
|
26
28
|
return ''.join(characters[b % len(characters)] for b in random_bytes)
|
29
|
+
|
30
|
+
|
31
|
+
def b64_encode(data):
|
32
|
+
if isinstance(data, dict):
|
33
|
+
data = json.dumps(data)
|
34
|
+
return b64encode(data.encode('utf-8')).decode('utf-8')
|
35
|
+
|
36
|
+
|
37
|
+
def b64_decode(data):
|
38
|
+
dec = b64decode(data.encode('utf-8')).decode('utf-8')
|
39
|
+
if dec[0] == '{':
|
40
|
+
return json.loads(dec)
|
41
|
+
return dec
|
mojo/helpers/dates.py
CHANGED
@@ -67,3 +67,21 @@ def subtract(when=None, seconds=None, minutes=None, hours=None, days=None):
|
|
67
67
|
|
68
68
|
def has_time_elsapsed(when, seconds=None, minutes=None, hours=None, days=None):
|
69
69
|
return utcnow() >= add(when, seconds, minutes, hours, days)
|
70
|
+
|
71
|
+
|
72
|
+
def is_today(when, timezone=None):
|
73
|
+
if timezone is None:
|
74
|
+
timezone = 'UTC'
|
75
|
+
|
76
|
+
# Convert when to the specified timezone
|
77
|
+
if when.tzinfo is None:
|
78
|
+
when = pytz.UTC.localize(when)
|
79
|
+
local_tz = pytz.timezone(timezone)
|
80
|
+
when_local = when.astimezone(local_tz)
|
81
|
+
|
82
|
+
# Get today's date in the specified timezone
|
83
|
+
now_utc = utcnow()
|
84
|
+
now_local = now_utc.astimezone(local_tz)
|
85
|
+
|
86
|
+
# Compare dates
|
87
|
+
return when_local.date() == now_local.date()
|