django-nativemojo 0.1.15__py3-none-any.whl → 0.1.17__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.
Files changed (221) hide show
  1. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/METADATA +3 -2
  2. django_nativemojo-0.1.17.dist-info/RECORD +302 -0
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/commands/serializer_admin.py +121 -1
  5. mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
  6. mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
  7. mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
  8. mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
  9. mojo/apps/account/migrations/0010_group_avatar.py +20 -0
  10. mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
  11. mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
  12. mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
  13. mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
  14. mojo/apps/account/models/__init__.py +2 -0
  15. mojo/apps/account/models/device.py +279 -0
  16. mojo/apps/account/models/group.py +294 -8
  17. mojo/apps/account/models/member.py +14 -1
  18. mojo/apps/account/models/push/__init__.py +4 -0
  19. mojo/apps/account/models/push/config.py +112 -0
  20. mojo/apps/account/models/push/delivery.py +93 -0
  21. mojo/apps/account/models/push/device.py +66 -0
  22. mojo/apps/account/models/push/template.py +99 -0
  23. mojo/apps/account/models/user.py +190 -17
  24. mojo/apps/account/rest/__init__.py +2 -0
  25. mojo/apps/account/rest/device.py +39 -0
  26. mojo/apps/account/rest/group.py +8 -0
  27. mojo/apps/account/rest/push.py +187 -0
  28. mojo/apps/account/rest/user.py +95 -5
  29. mojo/apps/account/services/__init__.py +1 -0
  30. mojo/apps/account/services/push.py +363 -0
  31. mojo/apps/aws/migrations/0001_initial.py +206 -0
  32. mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
  33. mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
  34. mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
  35. mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
  36. mojo/apps/aws/models/__init__.py +19 -0
  37. mojo/apps/aws/models/email_attachment.py +99 -0
  38. mojo/apps/aws/models/email_domain.py +218 -0
  39. mojo/apps/aws/models/email_template.py +132 -0
  40. mojo/apps/aws/models/incoming_email.py +197 -0
  41. mojo/apps/aws/models/mailbox.py +288 -0
  42. mojo/apps/aws/models/sent_message.py +175 -0
  43. mojo/apps/aws/rest/__init__.py +6 -0
  44. mojo/apps/aws/rest/email.py +33 -0
  45. mojo/apps/aws/rest/email_ops.py +183 -0
  46. mojo/apps/aws/rest/messages.py +32 -0
  47. mojo/apps/aws/rest/send.py +101 -0
  48. mojo/apps/aws/rest/sns.py +403 -0
  49. mojo/apps/aws/rest/templates.py +19 -0
  50. mojo/apps/aws/services/__init__.py +32 -0
  51. mojo/apps/aws/services/email.py +390 -0
  52. mojo/apps/aws/services/email_ops.py +548 -0
  53. mojo/apps/docit/__init__.py +6 -0
  54. mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
  55. mojo/apps/docit/markdown_plugins/toc.py +12 -0
  56. mojo/apps/docit/migrations/0001_initial.py +113 -0
  57. mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
  58. mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
  59. mojo/apps/docit/models/__init__.py +17 -0
  60. mojo/apps/docit/models/asset.py +231 -0
  61. mojo/apps/docit/models/book.py +227 -0
  62. mojo/apps/docit/models/page.py +319 -0
  63. mojo/apps/docit/models/page_revision.py +203 -0
  64. mojo/apps/docit/rest/__init__.py +10 -0
  65. mojo/apps/docit/rest/asset.py +17 -0
  66. mojo/apps/docit/rest/book.py +22 -0
  67. mojo/apps/docit/rest/page.py +22 -0
  68. mojo/apps/docit/rest/page_revision.py +17 -0
  69. mojo/apps/docit/services/__init__.py +11 -0
  70. mojo/apps/docit/services/docit.py +315 -0
  71. mojo/apps/docit/services/markdown.py +44 -0
  72. mojo/apps/fileman/backends/s3.py +209 -0
  73. mojo/apps/fileman/models/file.py +45 -9
  74. mojo/apps/fileman/models/manager.py +269 -3
  75. mojo/apps/incident/migrations/0007_event_uid.py +18 -0
  76. mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
  77. mojo/apps/incident/migrations/0009_incident_status.py +18 -0
  78. mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
  79. mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
  80. mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
  81. mojo/apps/incident/models/__init__.py +1 -0
  82. mojo/apps/incident/models/event.py +35 -0
  83. mojo/apps/incident/models/incident.py +2 -0
  84. mojo/apps/incident/models/ticket.py +62 -0
  85. mojo/apps/incident/reporter.py +21 -3
  86. mojo/apps/incident/rest/__init__.py +1 -0
  87. mojo/apps/incident/rest/ticket.py +43 -0
  88. mojo/apps/jobs/__init__.py +489 -0
  89. mojo/apps/jobs/adapters.py +24 -0
  90. mojo/apps/jobs/cli.py +616 -0
  91. mojo/apps/jobs/daemon.py +370 -0
  92. mojo/apps/jobs/examples/sample_jobs.py +376 -0
  93. mojo/apps/jobs/examples/webhook_examples.py +203 -0
  94. mojo/apps/jobs/handlers/__init__.py +5 -0
  95. mojo/apps/jobs/handlers/webhook.py +317 -0
  96. mojo/apps/jobs/job_engine.py +734 -0
  97. mojo/apps/jobs/keys.py +203 -0
  98. mojo/apps/jobs/local_queue.py +363 -0
  99. mojo/apps/jobs/management/__init__.py +3 -0
  100. mojo/apps/jobs/management/commands/__init__.py +3 -0
  101. mojo/apps/jobs/manager.py +1327 -0
  102. mojo/apps/jobs/migrations/0001_initial.py +97 -0
  103. mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
  104. mojo/apps/jobs/models/__init__.py +6 -0
  105. mojo/apps/jobs/models/job.py +441 -0
  106. mojo/apps/jobs/rest/__init__.py +2 -0
  107. mojo/apps/jobs/rest/control.py +466 -0
  108. mojo/apps/jobs/rest/jobs.py +421 -0
  109. mojo/apps/jobs/scheduler.py +571 -0
  110. mojo/apps/jobs/services/__init__.py +6 -0
  111. mojo/apps/jobs/services/job_actions.py +465 -0
  112. mojo/apps/jobs/settings.py +209 -0
  113. mojo/apps/logit/models/log.py +3 -0
  114. mojo/apps/metrics/__init__.py +8 -1
  115. mojo/apps/metrics/redis_metrics.py +198 -0
  116. mojo/apps/metrics/rest/__init__.py +3 -0
  117. mojo/apps/metrics/rest/categories.py +266 -0
  118. mojo/apps/metrics/rest/helpers.py +48 -0
  119. mojo/apps/metrics/rest/permissions.py +99 -0
  120. mojo/apps/metrics/rest/values.py +277 -0
  121. mojo/apps/metrics/utils.py +17 -0
  122. mojo/decorators/http.py +40 -1
  123. mojo/helpers/aws/__init__.py +11 -7
  124. mojo/helpers/aws/inbound_email.py +309 -0
  125. mojo/helpers/aws/kms.py +413 -0
  126. mojo/helpers/aws/ses_domain.py +959 -0
  127. mojo/helpers/crypto/__init__.py +1 -1
  128. mojo/helpers/crypto/utils.py +15 -0
  129. mojo/helpers/location/__init__.py +2 -0
  130. mojo/helpers/location/countries.py +262 -0
  131. mojo/helpers/location/geolocation.py +196 -0
  132. mojo/helpers/logit.py +37 -0
  133. mojo/helpers/redis/__init__.py +2 -0
  134. mojo/helpers/redis/adapter.py +606 -0
  135. mojo/helpers/redis/client.py +48 -0
  136. mojo/helpers/redis/pool.py +225 -0
  137. mojo/helpers/request.py +8 -0
  138. mojo/helpers/response.py +8 -0
  139. mojo/middleware/auth.py +1 -1
  140. mojo/middleware/cors.py +40 -0
  141. mojo/middleware/logging.py +131 -12
  142. mojo/middleware/mojo.py +5 -0
  143. mojo/models/rest.py +271 -57
  144. mojo/models/secrets.py +86 -0
  145. mojo/serializers/__init__.py +16 -10
  146. mojo/serializers/core/__init__.py +90 -0
  147. mojo/serializers/core/cache/__init__.py +121 -0
  148. mojo/serializers/core/cache/backends.py +518 -0
  149. mojo/serializers/core/cache/base.py +102 -0
  150. mojo/serializers/core/cache/disabled.py +181 -0
  151. mojo/serializers/core/cache/memory.py +287 -0
  152. mojo/serializers/core/cache/redis.py +533 -0
  153. mojo/serializers/core/cache/utils.py +454 -0
  154. mojo/serializers/{manager.py → core/manager.py} +53 -4
  155. mojo/serializers/core/serializer.py +475 -0
  156. mojo/serializers/{advanced/formats → formats}/csv.py +116 -139
  157. mojo/serializers/suggested_improvements.md +388 -0
  158. testit/client.py +1 -1
  159. testit/helpers.py +14 -0
  160. testit/runner.py +23 -6
  161. django_nativemojo-0.1.15.dist-info/RECORD +0 -234
  162. mojo/apps/notify/README.md +0 -91
  163. mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
  164. mojo/apps/notify/admin.py +0 -52
  165. mojo/apps/notify/handlers/example_handlers.py +0 -516
  166. mojo/apps/notify/handlers/ses/__init__.py +0 -25
  167. mojo/apps/notify/handlers/ses/complaint.py +0 -25
  168. mojo/apps/notify/handlers/ses/message.py +0 -86
  169. mojo/apps/notify/management/commands/__init__.py +0 -1
  170. mojo/apps/notify/management/commands/process_notifications.py +0 -370
  171. mojo/apps/notify/mod +0 -0
  172. mojo/apps/notify/models/__init__.py +0 -12
  173. mojo/apps/notify/models/account.py +0 -128
  174. mojo/apps/notify/models/attachment.py +0 -24
  175. mojo/apps/notify/models/bounce.py +0 -68
  176. mojo/apps/notify/models/complaint.py +0 -40
  177. mojo/apps/notify/models/inbox.py +0 -113
  178. mojo/apps/notify/models/inbox_message.py +0 -173
  179. mojo/apps/notify/models/outbox.py +0 -129
  180. mojo/apps/notify/models/outbox_message.py +0 -288
  181. mojo/apps/notify/models/template.py +0 -30
  182. mojo/apps/notify/providers/aws.py +0 -73
  183. mojo/apps/notify/rest/ses.py +0 -0
  184. mojo/apps/notify/utils/__init__.py +0 -2
  185. mojo/apps/notify/utils/notifications.py +0 -404
  186. mojo/apps/notify/utils/parsing.py +0 -202
  187. mojo/apps/notify/utils/render.py +0 -144
  188. mojo/apps/tasks/README.md +0 -118
  189. mojo/apps/tasks/__init__.py +0 -44
  190. mojo/apps/tasks/manager.py +0 -644
  191. mojo/apps/tasks/rest/__init__.py +0 -2
  192. mojo/apps/tasks/rest/hooks.py +0 -0
  193. mojo/apps/tasks/rest/tasks.py +0 -76
  194. mojo/apps/tasks/runner.py +0 -439
  195. mojo/apps/tasks/task.py +0 -99
  196. mojo/apps/tasks/tq_handlers.py +0 -132
  197. mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
  198. mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
  199. mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
  200. mojo/helpers/redis.py +0 -10
  201. mojo/models/meta.py +0 -262
  202. mojo/serializers/advanced/README.md +0 -363
  203. mojo/serializers/advanced/__init__.py +0 -247
  204. mojo/serializers/advanced/formats/__init__.py +0 -28
  205. mojo/serializers/advanced/formats/excel.py +0 -516
  206. mojo/serializers/advanced/formats/json.py +0 -239
  207. mojo/serializers/advanced/formats/response.py +0 -485
  208. mojo/serializers/advanced/serializer.py +0 -568
  209. mojo/serializers/optimized.py +0 -618
  210. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/LICENSE +0 -0
  211. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/NOTICE +0 -0
  212. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/WHEEL +0 -0
  213. /mojo/apps/{notify → aws/migrations}/__init__.py +0 -0
  214. /mojo/apps/{notify/handlers → docit/markdown_plugins}/__init__.py +0 -0
  215. /mojo/apps/{notify/management → docit/migrations}/__init__.py +0 -0
  216. /mojo/apps/{notify/providers → jobs/examples}/__init__.py +0 -0
  217. /mojo/apps/{notify/rest → jobs/migrations}/__init__.py +0 -0
  218. /mojo/{serializers → rest}/openapi.py +0 -0
  219. /mojo/serializers/{settings_example.py → examples/settings.py} +0 -0
  220. /mojo/{apps/notify/handlers/ses/bounce.py → serializers/formats/__init__.py} +0 -0
  221. /mojo/serializers/{advanced/formats → formats}/localizers.py +0 -0
@@ -1,516 +0,0 @@
1
- """
2
- Example message handlers for the notification system.
3
-
4
- These handlers demonstrate how to process incoming and outgoing messages
5
- across different notification channels (email, SMS, WhatsApp, etc.).
6
- """
7
-
8
- import logging
9
- import asyncio
10
- from typing import Dict, Any
11
- from django.conf import settings
12
- from django.utils import timezone
13
- from django.core.mail import send_mail
14
- from django.template import Template, Context
15
-
16
- from ..models import InboxMessage, OutboxMessage, Account
17
- from ..utils.notifications import NotificationError
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
-
22
- # =============================================================================
23
- # INBOX MESSAGE HANDLERS (for processing received messages)
24
- # =============================================================================
25
-
26
- def on_email_received(inbox_message: InboxMessage) -> bool:
27
- """
28
- Example handler for processing received email messages.
29
-
30
- This handler demonstrates:
31
- - Basic message processing
32
- - Extracting message details
33
- - Logging and error handling
34
- - Auto-reply functionality
35
-
36
- Usage in Inbox model:
37
- sync_handler = "mojo.apps.notify.handlers.example_handlers.on_email_received"
38
- """
39
- try:
40
- logger.info(f"Processing email from {inbox_message.from_address} to {inbox_message.to_address}")
41
-
42
- # Extract message details
43
- sender = inbox_message.from_address
44
- recipient = inbox_message.to_address
45
- subject = inbox_message.subject or "No Subject"
46
- message_body = inbox_message.message
47
-
48
- # Example: Check if this is an auto-reply request
49
- if "auto-reply" in subject.lower() or "out of office" in subject.lower():
50
- # Send automatic response
51
- send_auto_reply(inbox_message)
52
-
53
- # Example: Process support tickets
54
- if recipient.startswith("support@"):
55
- process_support_ticket(inbox_message)
56
-
57
- # Example: Forward certain messages
58
- if should_forward_message(inbox_message):
59
- forward_message(inbox_message)
60
-
61
- # Log successful processing
62
- logger.info(f"Successfully processed email message {inbox_message.id}")
63
- return True
64
-
65
- except Exception as e:
66
- logger.error(f"Error processing email message {inbox_message.id}: {e}")
67
- return False
68
-
69
-
70
- async def on_email_received_async(inbox_message: InboxMessage) -> bool:
71
- """
72
- Async version of email handler for high-volume processing.
73
-
74
- Usage in Inbox model:
75
- async_handler = "mojo.apps.notify.handlers.example_handlers.on_email_received_async"
76
- """
77
- try:
78
- # Use async processing for heavy operations
79
- await asyncio.sleep(0.1) # Simulate async work
80
-
81
- # Process message asynchronously
82
- result = await process_email_async(inbox_message)
83
-
84
- logger.info(f"Async processed email message {inbox_message.id}")
85
- return result
86
-
87
- except Exception as e:
88
- logger.error(f"Async error processing email message {inbox_message.id}: {e}")
89
- return False
90
-
91
-
92
- def on_sms_received(inbox_message: InboxMessage) -> bool:
93
- """
94
- Example handler for processing received SMS messages.
95
-
96
- Usage in Inbox model:
97
- sync_handler = "mojo.apps.notify.handlers.example_handlers.on_sms_received"
98
- """
99
- try:
100
- logger.info(f"Processing SMS from {inbox_message.from_address}")
101
-
102
- # Extract SMS content
103
- sender_phone = inbox_message.from_address
104
- message_text = inbox_message.message.strip()
105
-
106
- # Example: Handle SMS commands
107
- if message_text.upper().startswith("STOP"):
108
- handle_sms_unsubscribe(sender_phone)
109
- elif message_text.upper().startswith("START"):
110
- handle_sms_subscribe(sender_phone)
111
- elif message_text.upper().startswith("HELP"):
112
- send_sms_help(sender_phone)
113
- else:
114
- # Process regular SMS message
115
- process_sms_message(inbox_message)
116
-
117
- return True
118
-
119
- except Exception as e:
120
- logger.error(f"Error processing SMS message {inbox_message.id}: {e}")
121
- return False
122
-
123
-
124
- def on_whatsapp_received(inbox_message: InboxMessage) -> bool:
125
- """
126
- Example handler for processing received WhatsApp messages.
127
-
128
- Usage in Inbox model:
129
- sync_handler = "mojo.apps.notify.handlers.example_handlers.on_whatsapp_received"
130
- """
131
- try:
132
- logger.info(f"Processing WhatsApp from {inbox_message.from_address}")
133
-
134
- # Extract message metadata (WhatsApp specific)
135
- metadata = inbox_message.metadata
136
- message_type = metadata.get('message_type', 'text')
137
-
138
- if message_type == 'text':
139
- process_whatsapp_text(inbox_message)
140
- elif message_type == 'image':
141
- process_whatsapp_image(inbox_message)
142
- elif message_type == 'document':
143
- process_whatsapp_document(inbox_message)
144
-
145
- return True
146
-
147
- except Exception as e:
148
- logger.error(f"Error processing WhatsApp message {inbox_message.id}: {e}")
149
- return False
150
-
151
-
152
- # =============================================================================
153
- # OUTBOX MESSAGE HANDLERS (for sending messages)
154
- # =============================================================================
155
-
156
- def on_email_send(outbox_message: OutboxMessage) -> bool:
157
- """
158
- Example handler for sending email messages.
159
-
160
- Usage in Outbox model:
161
- handler = "mojo.apps.notify.handlers.example_handlers.on_email_send"
162
- """
163
- try:
164
- # Mark as sending
165
- outbox_message.mark_sending()
166
-
167
- # Extract email details
168
- recipient = outbox_message.to_address
169
- sender = outbox_message.from_address
170
- subject = outbox_message.subject or "No Subject"
171
- message_body = outbox_message.message
172
-
173
- # Get email settings from account
174
- account = outbox_message.account
175
- smtp_settings = account.get_setting('smtp', {})
176
-
177
- # Send email using Django's email backend or custom SMTP
178
- if smtp_settings:
179
- send_via_custom_smtp(outbox_message, smtp_settings)
180
- else:
181
- send_via_django_email(outbox_message)
182
-
183
- # Mark as sent
184
- outbox_message.mark_sent()
185
- logger.info(f"Email sent successfully to {recipient}")
186
- return True
187
-
188
- except Exception as e:
189
- error_msg = f"Failed to send email to {outbox_message.to_address}: {e}"
190
- outbox_message.mark_failed(error_msg)
191
- logger.error(error_msg)
192
- return False
193
-
194
-
195
- def on_sms_send(outbox_message: OutboxMessage) -> bool:
196
- """
197
- Example handler for sending SMS messages.
198
-
199
- Usage in Outbox model:
200
- handler = "mojo.apps.notify.handlers.example_handlers.on_sms_send"
201
- """
202
- try:
203
- outbox_message.mark_sending()
204
-
205
- # Extract SMS details
206
- phone_number = outbox_message.to_address
207
- message_text = outbox_message.message
208
-
209
- # Get SMS provider settings
210
- account = outbox_message.account
211
- sms_settings = account.get_setting('sms_provider', {})
212
-
213
- provider = sms_settings.get('provider', 'twilio')
214
-
215
- if provider == 'twilio':
216
- send_via_twilio(outbox_message, sms_settings)
217
- elif provider == 'aws_sns':
218
- send_via_aws_sns(outbox_message, sms_settings)
219
- else:
220
- raise NotificationError(f"Unknown SMS provider: {provider}")
221
-
222
- outbox_message.mark_sent()
223
- logger.info(f"SMS sent successfully to {phone_number}")
224
- return True
225
-
226
- except Exception as e:
227
- error_msg = f"Failed to send SMS to {outbox_message.to_address}: {e}"
228
- outbox_message.mark_failed(error_msg)
229
- logger.error(error_msg)
230
- return False
231
-
232
-
233
- def on_whatsapp_send(outbox_message: OutboxMessage) -> bool:
234
- """
235
- Example handler for sending WhatsApp messages.
236
-
237
- Usage in Outbox model:
238
- handler = "mojo.apps.notify.handlers.example_handlers.on_whatsapp_send"
239
- """
240
- try:
241
- outbox_message.mark_sending()
242
-
243
- # Extract WhatsApp details
244
- phone_number = outbox_message.to_address
245
- message_text = outbox_message.message
246
-
247
- # Get WhatsApp Business API settings
248
- account = outbox_message.account
249
- wa_settings = account.get_setting('whatsapp_api', {})
250
-
251
- # Send via WhatsApp Business API
252
- send_via_whatsapp_api(outbox_message, wa_settings)
253
-
254
- outbox_message.mark_sent()
255
- logger.info(f"WhatsApp sent successfully to {phone_number}")
256
- return True
257
-
258
- except Exception as e:
259
- error_msg = f"Failed to send WhatsApp to {outbox_message.to_address}: {e}"
260
- outbox_message.mark_failed(error_msg)
261
- logger.error(error_msg)
262
- return False
263
-
264
-
265
- def on_push_send(outbox_message: OutboxMessage) -> bool:
266
- """
267
- Example handler for sending push notifications.
268
-
269
- Usage in Outbox model:
270
- handler = "mojo.apps.notify.handlers.example_handlers.on_push_send"
271
- """
272
- try:
273
- outbox_message.mark_sending()
274
-
275
- # Extract push notification details
276
- device_token = outbox_message.to_address
277
- title = outbox_message.subject or "Notification"
278
- message_body = outbox_message.message
279
- metadata = outbox_message.metadata
280
-
281
- # Get push notification settings
282
- account = outbox_message.account
283
- push_settings = account.get_setting('push_provider', {})
284
-
285
- provider = push_settings.get('provider', 'fcm')
286
-
287
- if provider == 'fcm':
288
- send_via_fcm(outbox_message, push_settings)
289
- elif provider == 'apns':
290
- send_via_apns(outbox_message, push_settings)
291
- else:
292
- raise NotificationError(f"Unknown push provider: {provider}")
293
-
294
- outbox_message.mark_sent()
295
- logger.info(f"Push notification sent successfully to {device_token}")
296
- return True
297
-
298
- except Exception as e:
299
- error_msg = f"Failed to send push notification to {outbox_message.to_address}: {e}"
300
- outbox_message.mark_failed(error_msg)
301
- logger.error(error_msg)
302
- return False
303
-
304
-
305
- # =============================================================================
306
- # HELPER FUNCTIONS
307
- # =============================================================================
308
-
309
- def send_auto_reply(inbox_message: InboxMessage):
310
- """Send an automatic reply to the sender"""
311
- from ..utils.notifications import send_email
312
-
313
- auto_reply_template = """
314
- Thank you for your message. This is an automated response.
315
-
316
- We have received your email and will respond within 24 hours.
317
-
318
- Original subject: {subject}
319
- Received at: {received_at}
320
- """
321
-
322
- reply_message = auto_reply_template.format(
323
- subject=inbox_message.subject or "No Subject",
324
- received_at=inbox_message.created.strftime("%Y-%m-%d %H:%M:%S")
325
- )
326
-
327
- send_email(
328
- to_address=inbox_message.from_address,
329
- subject=f"Re: {inbox_message.subject or 'Your Message'}",
330
- message=reply_message,
331
- from_address=inbox_message.to_address
332
- )
333
-
334
-
335
- def process_support_ticket(inbox_message: InboxMessage):
336
- """Process a support ticket from email"""
337
- # This would integrate with your ticketing system
338
- logger.info(f"Creating support ticket for: {inbox_message.subject}")
339
-
340
- # Example: Create a ticket record
341
- ticket_data = {
342
- 'email': inbox_message.from_address,
343
- 'subject': inbox_message.subject,
344
- 'description': inbox_message.message,
345
- 'created_at': inbox_message.created,
346
- }
347
-
348
- # Save to your ticketing system
349
- # Ticket.objects.create(**ticket_data)
350
-
351
-
352
- def should_forward_message(inbox_message: InboxMessage) -> bool:
353
- """Determine if message should be forwarded"""
354
- # Example logic for forwarding
355
- keywords = ['urgent', 'asap', 'emergency']
356
- message_lower = inbox_message.message.lower()
357
- subject_lower = (inbox_message.subject or "").lower()
358
-
359
- return any(keyword in message_lower or keyword in subject_lower for keyword in keywords)
360
-
361
-
362
- def forward_message(inbox_message: InboxMessage):
363
- """Forward message to designated recipients"""
364
- from ..utils.notifications import send_email
365
-
366
- forward_addresses = ['manager@example.com', 'support-lead@example.com']
367
-
368
- forward_message = f"""
369
- Forwarded message from: {inbox_message.from_address}
370
- Original recipient: {inbox_message.to_address}
371
- Subject: {inbox_message.subject}
372
- Received: {inbox_message.created}
373
-
374
- Message:
375
- {inbox_message.message}
376
- """
377
-
378
- for address in forward_addresses:
379
- send_email(
380
- to_address=address,
381
- subject=f"FWD: {inbox_message.subject}",
382
- message=forward_message,
383
- from_address=inbox_message.to_address
384
- )
385
-
386
-
387
- async def process_email_async(inbox_message: InboxMessage) -> bool:
388
- """Async email processing"""
389
- # Simulate async work like API calls, database operations, etc.
390
- await asyncio.sleep(0.1)
391
-
392
- # Process message
393
- return True
394
-
395
-
396
- def handle_sms_unsubscribe(phone_number: str):
397
- """Handle SMS unsubscribe request"""
398
- logger.info(f"Processing SMS unsubscribe for {phone_number}")
399
- # Add to unsubscribe list
400
- # SMSUnsubscribe.objects.get_or_create(phone_number=phone_number)
401
-
402
-
403
- def handle_sms_subscribe(phone_number: str):
404
- """Handle SMS subscribe request"""
405
- logger.info(f"Processing SMS subscribe for {phone_number}")
406
- # Remove from unsubscribe list
407
- # SMSUnsubscribe.objects.filter(phone_number=phone_number).delete()
408
-
409
-
410
- def send_sms_help(phone_number: str):
411
- """Send SMS help message"""
412
- from ..utils.notifications import send_sms
413
-
414
- help_message = """
415
- SMS Commands:
416
- - STOP: Unsubscribe from messages
417
- - START: Subscribe to messages
418
- - HELP: Show this help message
419
- """
420
-
421
- send_sms(to_address=phone_number, message=help_message)
422
-
423
-
424
- def process_sms_message(inbox_message: InboxMessage):
425
- """Process regular SMS message"""
426
- logger.info(f"Processing SMS message: {inbox_message.message[:50]}...")
427
-
428
-
429
- def process_whatsapp_text(inbox_message: InboxMessage):
430
- """Process WhatsApp text message"""
431
- logger.info(f"Processing WhatsApp text: {inbox_message.message[:50]}...")
432
-
433
-
434
- def process_whatsapp_image(inbox_message: InboxMessage):
435
- """Process WhatsApp image message"""
436
- logger.info("Processing WhatsApp image message")
437
- # Handle image processing
438
-
439
-
440
- def process_whatsapp_document(inbox_message: InboxMessage):
441
- """Process WhatsApp document message"""
442
- logger.info("Processing WhatsApp document message")
443
- # Handle document processing
444
-
445
-
446
- def send_via_custom_smtp(outbox_message: OutboxMessage, smtp_settings: Dict[str, Any]):
447
- """Send email via custom SMTP settings"""
448
- import smtplib
449
- from email.mime.text import MIMEText
450
- from email.mime.multipart import MIMEMultipart
451
-
452
- # Create message
453
- msg = MIMEMultipart()
454
- msg['From'] = outbox_message.from_address
455
- msg['To'] = outbox_message.to_address
456
- msg['Subject'] = outbox_message.subject or "No Subject"
457
-
458
- # Add body
459
- msg.attach(MIMEText(outbox_message.message, 'plain'))
460
-
461
- # Connect and send
462
- server = smtplib.SMTP(smtp_settings['host'], smtp_settings.get('port', 587))
463
- if smtp_settings.get('use_tls', True):
464
- server.starttls()
465
-
466
- if smtp_settings.get('username') and smtp_settings.get('password'):
467
- server.login(smtp_settings['username'], smtp_settings['password'])
468
-
469
- server.send_message(msg)
470
- server.quit()
471
-
472
-
473
- def send_via_django_email(outbox_message: OutboxMessage):
474
- """Send email via Django's default email backend"""
475
- send_mail(
476
- subject=outbox_message.subject or "No Subject",
477
- message=outbox_message.message,
478
- from_email=outbox_message.from_address,
479
- recipient_list=[outbox_message.to_address],
480
- fail_silently=False
481
- )
482
-
483
-
484
- def send_via_twilio(outbox_message: OutboxMessage, sms_settings: Dict[str, Any]):
485
- """Send SMS via Twilio"""
486
- # This would use the Twilio Python SDK
487
- logger.info(f"Sending SMS via Twilio to {outbox_message.to_address}")
488
- # from twilio.rest import Client
489
- # client = Client(sms_settings['account_sid'], sms_settings['auth_token'])
490
- # message = client.messages.create(...)
491
-
492
-
493
- def send_via_aws_sns(outbox_message: OutboxMessage, sms_settings: Dict[str, Any]):
494
- """Send SMS via AWS SNS"""
495
- logger.info(f"Sending SMS via AWS SNS to {outbox_message.to_address}")
496
- # import boto3
497
- # sns = boto3.client('sns', ...)
498
- # sns.publish(...)
499
-
500
-
501
- def send_via_whatsapp_api(outbox_message: OutboxMessage, wa_settings: Dict[str, Any]):
502
- """Send WhatsApp message via Business API"""
503
- logger.info(f"Sending WhatsApp via API to {outbox_message.to_address}")
504
- # Implement WhatsApp Business API call
505
-
506
-
507
- def send_via_fcm(outbox_message: OutboxMessage, push_settings: Dict[str, Any]):
508
- """Send push notification via Firebase Cloud Messaging"""
509
- logger.info(f"Sending FCM push to {outbox_message.to_address}")
510
- # Implement FCM API call
511
-
512
-
513
- def send_via_apns(outbox_message: OutboxMessage, push_settings: Dict[str, Any]):
514
- """Send push notification via Apple Push Notification Service"""
515
- logger.info(f"Sending APNS push to {outbox_message.to_address}")
516
- # Implement APNS API call
@@ -1,25 +0,0 @@
1
- from .message import on_message, on_confirm_subscription, on_notification, JsonResponse
2
- from .complaint import on_complaint
3
- from .bounce import on_bounce
4
- from mojo.helpers import modules
5
-
6
- INBOX_HANDLERS = {
7
- "Received": on_message,
8
- "SubscriptionConfirmation": on_confrim_subscription,
9
- "Complaint": on_complaint,
10
- "Notification": on_notification,
11
- "Bounce": on_bounce
12
-
13
- }
14
-
15
- def handle_request(request):
16
- msg = request.DATA.get("Message", "")
17
- handler = INBOX_HANDLERS.get(msg.notificationType, None)
18
- if handler is not None:
19
- handler(request, msg)
20
- else:
21
- Event = modules.get_model("incidents", "Event")
22
- if Event is None:
23
- Event.report(f"no ses mailman handler for: {msg.notificationType}",
24
- "mailman_ses", raw_message=request.DATA)
25
- return JsonResponse(dict(status=True))
@@ -1,25 +0,0 @@
1
- from mojo.apps.mailmain.models import Complaint
2
- from mojo.models import User
3
-
4
-
5
- def record(kind, address, reason, user_agent=None, code=None, source=None, source_ip=None, user=None):
6
- complaint = Complaint(
7
- kind=kind,
8
- address=address,
9
- reason=reason,
10
- user_agent=user_agent,
11
- code=code,
12
- source=source,
13
- source_ip=source_ip
14
- )
15
-
16
- if user is None:
17
- user = User.objects.filter(email=address).last()
18
- if user:
19
- user.is_email_verified = False
20
- user.save()
21
- user.log(f"email complaint: {kind} to {address} from {source_ip}", kind="complaint", level="warning")
22
- user.log("Email notifications have been disabled because of complaints.", kind="email", level="warning")
23
-
24
- complaint.user = user
25
- complaint.save()
@@ -1,86 +0,0 @@
1
- from mojo.helpers.response import JsonResponse
2
- from mojo.apps.mailman import utils
3
- from mojo.apps.mailman.models import Message
4
- import requests
5
-
6
-
7
- def on_confrim_subscription(request):
8
- url = request.DATA.get("SubscribeURL", None)
9
- resp = requests.get(url)
10
- # TODO record this as an event/incident
11
- return JsonResponse(dict(status=True))
12
-
13
-
14
- def on_message(request):
15
- """
16
- Email receiving can be configured in 2 ways.
17
- 1. raw email via SNS which would have content field
18
- 2. s3 bucket stored email which will have a receipt.action.bucketName
19
- """
20
- msg = request.DATA
21
- if msg.content is None and msg.receipt and msg.receipt.action:
22
- action = msg.receipt.action
23
- if action.type == "S3":
24
- return on_s3_email(request, msg, action.bucketName, action.objectKey)
25
-
26
- if msg.content is None:
27
- logger.error("message has no content", msg)
28
- return
29
-
30
- on_raw_email(utils.parse_raw_message(msg.content))
31
- return
32
-
33
-
34
- def on_s3_email(request, msg, bucket_name, object_key):
35
- msg_data = utils.parse_raw_message(s3store.getObjectContent(bucket_name, object_key))
36
- return on_raw_email(request, msg, msg_data)
37
-
38
-
39
- def on_raw_email(request, imsg, msg_data):
40
- to_email = imsg.receipt.recipients[0]
41
- # logger.info("parsed", msg_data)
42
- msg = createMessage(to_email, msg_data)
43
- metrics.metric("emails_received", category="email", min_granularity="hours")
44
-
45
- attachments = []
46
- for msg_atch in msg_data.attachments:
47
- atch = Attachment(message=msg, name=msg_atch.name, content_type=msg_atch.content_type)
48
- if msg_atch.encoding == "base64":
49
- atch.saveMediaFile(msg_atch.payload, "media", msg_atch.name, is_base64=True)
50
- elif msg_atch.encoding == "quoted-printable":
51
- obj = mailtils.toFileObject(msg_atch)
52
- atch.saveMediaFile(obj, "media", msg_atch.name)
53
- else:
54
- logger.error("unknown encoding", msg_atch.encoding)
55
- continue
56
- atch.save()
57
- attachments.append(atch)
58
- # add the recipients mailbox
59
- addToMailbox(msg)
60
- # now lets check if we have more recipients
61
- for to_email in imsg.receipt.recipients[1:]:
62
- msg = createMessage(to_email, msg_data)
63
- for atch in attachments:
64
- # create a copy of the attachment, for the new msg
65
- atch.pk = None
66
- atch.message = msg
67
- atch.save()
68
- addToMailbox(msg)
69
- return rv.restStatus(request, True)
70
-
71
-
72
-
73
- def create_message(to_email, msg_data):
74
- msg = Message(
75
- to_email=to_email,
76
- sent_at=msg_data.sent_at,
77
- subject=msg_data.subject,
78
- message=msg_data.message,
79
- html=msg_data.html,
80
- body=msg_data.body,
81
- to=msg_data.to,
82
- cc=msg_data.cc,
83
- from_email=msg_data.from_email,
84
- from_name=msg_data.from_name)
85
- msg.save()
86
- return msg
@@ -1 +0,0 @@
1
- ```