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
@@ -0,0 +1,421 @@
1
+ from mojo import decorators as md
2
+ from mojo.helpers.response import JsonResponse
3
+ from mojo.helpers import logit
4
+ from mojo.helpers.settings import settings
5
+ from mojo.apps.jobs.models import Job, JobEvent, JobLog
6
+ from mojo.apps.jobs.manager import get_manager
7
+ from mojo.apps.jobs import publish, cancel, status
8
+ from django.utils import timezone
9
+ from django.db.models import Q
10
+ import json
11
+
12
+
13
+ # Basic CRUD for Jobs (with RestMeta permissions)
14
+ @md.URL('job')
15
+ @md.URL('job/<str:pk>')
16
+ def on_job(request, pk=None):
17
+ """Standard CRUD operations for jobs with automatic permission handling."""
18
+
19
+ return Job.on_rest_request(request, pk)
20
+
21
+
22
+ # Basic CRUD for Job Events
23
+ @md.URL('event')
24
+ @md.URL('event/<int:pk>')
25
+ def on_job_event(request, pk=None):
26
+ """Standard CRUD operations for job events."""
27
+ return JobEvent.on_rest_request(request, pk)
28
+
29
+
30
+ # Basic CRUD for Job Logs
31
+ @md.URL('logs')
32
+ @md.URL('logs/<int:pk>')
33
+ def on_job_logs(request, pk=None):
34
+ """Standard CRUD operations for job logs."""
35
+ return JobLog.on_rest_request(request, pk)
36
+
37
+
38
+ # Get job status
39
+ @md.GET('status/<str:job_id>')
40
+ @md.requires_perms('manage_jobs', 'view_jobs')
41
+ def on_get_job_status(request, job_id):
42
+ """Get the current status of a job."""
43
+ try:
44
+ job_status = status(job_id)
45
+
46
+ if job_status is None:
47
+ return JsonResponse({
48
+ 'status': False,
49
+ 'error': 'Job not found'
50
+ }, status=404)
51
+
52
+ return JsonResponse({
53
+ 'status': True,
54
+ 'data': job_status
55
+ })
56
+
57
+ except Exception as e:
58
+ return JsonResponse({
59
+ 'status': False,
60
+ 'error': str(e)
61
+ }, status=400)
62
+
63
+
64
+ # Cancel a job
65
+ @md.POST('cancel')
66
+ @md.requires_perms('manage_jobs')
67
+ @md.requires_params('job_id')
68
+ def on_cancel_job(request):
69
+ """Request cancellation of a job."""
70
+ try:
71
+ job_id = request.DATA['job_id']
72
+ result = cancel(job_id)
73
+
74
+ return JsonResponse({
75
+ 'status': result,
76
+ 'message': f'Job {job_id} cancellation {"requested" if result else "failed"}'
77
+ })
78
+
79
+ except Exception as e:
80
+ return JsonResponse({
81
+ 'status': False,
82
+ 'error': str(e)
83
+ }, status=400)
84
+
85
+
86
+ # Retry a job
87
+ @md.POST('retry')
88
+ @md.requires_perms('manage_jobs')
89
+ @md.requires_params('job_id')
90
+ def on_retry_job(request):
91
+ """Retry a failed or canceled job."""
92
+ try:
93
+ job_id = request.DATA['job_id']
94
+ delay = request.DATA.get('delay')
95
+
96
+ # Get the job
97
+ try:
98
+ job = Job.objects.get(id=job_id)
99
+ except Job.DoesNotExist:
100
+ return JsonResponse({
101
+ 'status': False,
102
+ 'error': 'Job not found'
103
+ }, status=404)
104
+
105
+ # Use the service to retry
106
+ from mojo.apps.jobs.services import JobActionsService
107
+ result = JobActionsService.retry_job(job, delay=delay)
108
+
109
+ return JsonResponse(result)
110
+
111
+ except Exception as e:
112
+ return JsonResponse({
113
+ 'status': False,
114
+ 'error': str(e)
115
+ }, status=400)
116
+
117
+
118
+
119
+ # Get channel health
120
+ @md.GET('health/<str:channel>')
121
+ @md.requires_perms('manage_jobs', 'view_jobs')
122
+ def on_channel_health(request, channel):
123
+ """Get comprehensive health metrics for a channel."""
124
+ try:
125
+ manager = get_manager()
126
+ health = manager.get_channel_health(channel)
127
+
128
+ return JsonResponse({
129
+ 'status': True,
130
+ 'data': health
131
+ })
132
+
133
+ except Exception as e:
134
+ return JsonResponse({
135
+ 'status': False,
136
+ 'error': str(e)
137
+ }, status=400)
138
+
139
+
140
+ # Get all channels health
141
+ @md.GET('health')
142
+ @md.requires_perms('manage_jobs', 'view_jobs')
143
+ def on_health_overview(request):
144
+ """Get health overview for all configured channels."""
145
+ try:
146
+ from django.conf import settings
147
+ manager = get_manager()
148
+
149
+ channels = getattr(settings, 'JOBS_CHANNELS', ['default'])
150
+ health_data = {}
151
+
152
+ for channel in channels:
153
+ health_data[channel] = manager.get_channel_health(channel)
154
+
155
+ # Calculate aggregate stats
156
+ total_unclaimed = sum(h['messages']['unclaimed'] for h in health_data.values())
157
+ total_pending = sum(h['messages']['pending'] for h in health_data.values())
158
+ total_stuck = sum(h['messages']['stuck'] for h in health_data.values())
159
+ total_runners = sum(h['runners']['active'] for h in health_data.values())
160
+
161
+ # Determine overall status
162
+ overall_status = 'healthy'
163
+ if any(h['status'] == 'critical' for h in health_data.values()):
164
+ overall_status = 'critical'
165
+ elif any(h['status'] == 'warning' for h in health_data.values()):
166
+ overall_status = 'warning'
167
+
168
+ return JsonResponse({
169
+ 'status': True,
170
+ 'data': {
171
+ 'overall_status': overall_status,
172
+ 'totals': {
173
+ 'unclaimed': total_unclaimed,
174
+ 'pending': total_pending,
175
+ 'stuck': total_stuck,
176
+ 'runners': total_runners
177
+ },
178
+ 'channels': health_data
179
+ }
180
+ })
181
+
182
+ except Exception as e:
183
+ return JsonResponse({
184
+ 'status': False,
185
+ 'error': str(e)
186
+ }, status=400)
187
+
188
+
189
+ # Get active runners
190
+ @md.GET('runners')
191
+ @md.requires_perms('manage_jobs', 'view_jobs')
192
+ def on_list_runners(request):
193
+ """List all active runners with their status."""
194
+ try:
195
+ manager = get_manager()
196
+
197
+ # Optional channel filter
198
+ channel = request.DATA.get('channel')
199
+ runners = manager.get_runners(channel=channel)
200
+
201
+ # Set id field for each runner
202
+ for r in runners:
203
+ r["id"] = r["runner_id"]
204
+
205
+ return JsonResponse({
206
+ 'status': True,
207
+ 'count': len(runners),
208
+ 'data': runners
209
+ })
210
+
211
+ except Exception as e:
212
+ return JsonResponse({
213
+ 'status': False,
214
+ 'error': str(e)
215
+ }, status=400)
216
+
217
+
218
+ # Ping a specific runner
219
+ @md.POST('runners/ping')
220
+ @md.requires_perms('manage_jobs')
221
+ @md.requires_params('runner_id')
222
+ def on_ping_runner(request):
223
+ """Ping a specific runner to check if it's responsive."""
224
+ try:
225
+ manager = get_manager()
226
+ runner_id = request.DATA['runner_id']
227
+ timeout = float(request.DATA.get('timeout', 2.0))
228
+
229
+ result = manager.ping(runner_id, timeout=timeout)
230
+
231
+ return JsonResponse({
232
+ 'status': True,
233
+ 'runner_id': runner_id,
234
+ 'responsive': result
235
+ })
236
+
237
+ except Exception as e:
238
+ return JsonResponse({
239
+ 'status': False,
240
+ 'error': str(e)
241
+ }, status=400)
242
+
243
+
244
+ # Shutdown a runner
245
+ @md.POST('runners/shutdown')
246
+ @md.requires_perms('manage_jobs')
247
+ @md.requires_params('runner_id')
248
+ def on_shutdown_runner(request):
249
+ """Request a runner to shutdown gracefully."""
250
+ try:
251
+ manager = get_manager()
252
+ runner_id = request.DATA['runner_id']
253
+ graceful = request.DATA.get('graceful', True)
254
+
255
+ manager.shutdown(runner_id, graceful=bool(graceful))
256
+
257
+ return JsonResponse({
258
+ 'status': True,
259
+ 'message': f'Shutdown command sent to runner {runner_id}'
260
+ })
261
+
262
+ except Exception as e:
263
+ return JsonResponse({
264
+ 'status': False,
265
+ 'error': str(e)
266
+ }, status=400)
267
+
268
+
269
+ # Broadcast command to all runners
270
+ @md.POST('runners/broadcast')
271
+ @md.requires_perms('manage_jobs')
272
+ @md.requires_params('command')
273
+ def on_broadcast_command(request):
274
+ """Broadcast a command to all runners."""
275
+ try:
276
+ manager = get_manager()
277
+ command = request.DATA['command']
278
+ data = request.DATA.get('data', {})
279
+ timeout = float(request.DATA.get('timeout', 2.0))
280
+
281
+ # Validate command
282
+ valid_commands = ['status', 'shutdown', 'pause', 'resume', 'reload']
283
+ if command not in valid_commands:
284
+ return JsonResponse({
285
+ 'status': False,
286
+ 'error': f'Invalid command. Must be one of: {", ".join(valid_commands)}'
287
+ }, status=400)
288
+
289
+ responses = manager.broadcast_command(command, data=data, timeout=timeout)
290
+
291
+ return JsonResponse({
292
+ 'status': True,
293
+ 'command': command,
294
+ 'responses_count': len(responses),
295
+ 'responses': responses
296
+ })
297
+
298
+ except Exception as e:
299
+ return JsonResponse({
300
+ 'status': False,
301
+ 'error': str(e)
302
+ }, status=400)
303
+
304
+
305
+ # Get system stats
306
+ @md.GET('stats')
307
+ @md.requires_perms('manage_jobs', 'view_jobs')
308
+ def on_system_stats(request):
309
+ """Get overall system statistics."""
310
+ try:
311
+ manager = get_manager()
312
+ stats = manager.get_stats()
313
+
314
+ return JsonResponse({
315
+ 'status': True,
316
+ 'data': stats
317
+ })
318
+
319
+ except Exception as e:
320
+ return JsonResponse({
321
+ 'status': False,
322
+ 'error': str(e)
323
+ }, status=400)
324
+
325
+
326
+
327
+ @md.POST('test')
328
+ @md.requires_perms('manage_jobs')
329
+ def on_system_test(request):
330
+ from mojo.apps import jobs
331
+ jobs.publish(
332
+ "mojo.apps.jobs.examples.sample_jobs.send_email",
333
+ {
334
+ "recipients": ["user@example.com"],
335
+ "subject": "Test Email",
336
+ "body": "This is a test email."
337
+ },
338
+ delay=30
339
+ )
340
+
341
+ jobs.publish(
342
+ "mojo.apps.jobs.examples.sample_jobs.simulate_long_job",
343
+ {
344
+ "delay": 15
345
+ },
346
+ channel='priority'
347
+ )
348
+ return JsonResponse({
349
+ 'status': True,
350
+ 'message': 'Test job should be running.'
351
+ })
352
+
353
+
354
+ @md.POST('tests')
355
+ @md.requires_perms('manage_jobs')
356
+ def on_system_tests(request):
357
+ from mojo.apps import jobs
358
+ import random
359
+
360
+ base_job_list = [
361
+ {
362
+ "func": "mojo.apps.jobs.examples.sample_jobs.send_email",
363
+ "payload": {
364
+ "recipients": ["user@example.com"],
365
+ "subject": "Test Email",
366
+ "body": "This is a test email."
367
+ },
368
+ "channel": 'email'
369
+ },
370
+ {
371
+ "func": "mojo.apps.jobs.examples.sample_jobs.process_file_upload",
372
+ "payload": {
373
+ "file_path": "/path/to/file"
374
+ },
375
+ "channel": 'priority'
376
+ },
377
+ {
378
+ "func": "mojo.apps.jobs.examples.sample_jobs.process_file_upload",
379
+ "payload": {
380
+ "file_error_path": "/path/to/file"
381
+ },
382
+ "channel": 'default'
383
+ }
384
+ ]
385
+
386
+ fetch_job = {
387
+ "func": "mojo.apps.jobs.examples.sample_jobs.fetch_external_api",
388
+ "payload": {
389
+ "url": "https://nativemojo.com/"
390
+ },
391
+ "channel": 'webhooks'
392
+ }
393
+
394
+ job_list = []
395
+ channels = settings.get("JOBS_CHANNELS", ["email"])
396
+ for channel in channels:
397
+ j = random.choice(base_job_list)
398
+ j["channel"] = channel
399
+ job_list.append(j)
400
+
401
+ job_list.append(fetch_job)
402
+ # lets schedule some jobs as well
403
+ for i in range(10):
404
+ j = random.choice(base_job_list)
405
+ j = j.copy()
406
+ j["delay"] = random.randint(30, 300)
407
+ job_list.append(j)
408
+
409
+ for i in range(50):
410
+ j = random.choice(base_job_list)
411
+ job_list.append(j.copy())
412
+
413
+
414
+
415
+
416
+ for jd in job_list:
417
+ jobs.publish(**jd)
418
+ return JsonResponse({
419
+ 'status': True,
420
+ 'message': 'Test job should be running.'
421
+ })