django-nativemojo 0.1.15__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.
Files changed (221) hide show
  1. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/METADATA +3 -1
  2. django_nativemojo-0.1.16.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 +281 -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.16.dist-info}/LICENSE +0 -0
  211. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
  212. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.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,6 +1,7 @@
1
1
  from .redis_metrics import (
2
2
  record,
3
3
  fetch,
4
+ fetch_values,
4
5
  get_categories,
5
6
  fetch_by_category,
6
7
  get_category_slugs,
@@ -8,5 +9,11 @@ from .redis_metrics import (
8
9
  get_view_perms,
9
10
  get_write_perms,
10
11
  set_view_perms,
11
- set_write_perms
12
+ set_write_perms,
13
+ list_accounts,
14
+ add_account,
15
+ delete_account,
16
+ get_accounts_with_permissions,
17
+ set_value,
18
+ get_value
12
19
  )
@@ -84,6 +84,104 @@ def fetch(slug, dt_start=None, dt_end=None, granularity="hours",
84
84
  return values
85
85
  return nobjict(labels=utils.periods_from_dr_slugs(dr_slugs), data={slug: values})
86
86
 
87
+
88
+ def fetch_values(slugs, when=None, granularity="hours", redis_con=None, account="global", timezone=None):
89
+ """
90
+ Fetches specific metric values for multiple slugs at a single point in time.
91
+
92
+ Args:
93
+ slugs (str or list): The slug(s) to fetch values for. If string, should be comma-separated.
94
+ when (datetime): The specific datetime to fetch values for.
95
+ granularity (str, optional): The time granularity to use. Defaults to "hours".
96
+ redis_con: The Redis connection instance (optional).
97
+ account (str, optional): The account under which the metrics are recorded. Defaults to "global".
98
+
99
+ Returns:
100
+ dict: A dictionary containing:
101
+ - 'data': dict mapping slug names to their values
102
+ - 'slugs': list of slug names that were queried
103
+ - 'when': the datetime that was queried (as string)
104
+ - 'granularity': the granularity used
105
+ - 'account': the account queried
106
+ """
107
+ if redis_con is None:
108
+ redis_con = redis.get_connection()
109
+ when = utils.normalize_datetime(when, timezone)
110
+ # Handle comma-separated string input
111
+ if isinstance(slugs, str):
112
+ if ',' in slugs:
113
+ slugs = [s.strip() for s in slugs.split(',')]
114
+ else:
115
+ slugs = [slugs]
116
+
117
+ # Generate Redis keys for each slug at the specific datetime
118
+ redis_keys = []
119
+ for slug in slugs:
120
+ redis_key = utils.generate_slug(slug, when, granularity, account)
121
+ redis_keys.append(redis_key)
122
+
123
+ # Fetch all values with single MGET operation
124
+ values = redis_con.mget(redis_keys)
125
+
126
+ # Build response data dictionary
127
+ data = {}
128
+ for i, slug in enumerate(slugs):
129
+ value = values[i]
130
+ data[slug] = int(value) if value is not None else 0
131
+
132
+ return {
133
+ 'data': data,
134
+ 'slugs': slugs,
135
+ 'when': when.isoformat() if hasattr(when, 'isoformat') else str(when),
136
+ 'granularity': granularity,
137
+ 'account': account
138
+ }
139
+
140
+
141
+ def set_value(slug, value, redis_con=None, account="global"):
142
+ """
143
+ Sets a simple key-value pair in Redis for global storage (not time-series).
144
+
145
+ Args:
146
+ slug (str): The key identifier for the value.
147
+ value: The value to store (will be converted to string).
148
+ redis_con: The Redis connection instance (optional).
149
+ account (str, optional): The account under which the value is stored. Defaults to "global".
150
+
151
+ Returns:
152
+ None
153
+ """
154
+ if redis_con is None:
155
+ redis_con = redis.get_connection()
156
+
157
+ key = utils.generate_value_key(slug, account)
158
+ redis_con.set(key, str(value))
159
+
160
+
161
+ def get_value(slug, redis_con=None, account="global", default=None):
162
+ """
163
+ Retrieves a simple value from Redis for global storage (not time-series).
164
+
165
+ Args:
166
+ slug (str): The key identifier for the value.
167
+ redis_con: The Redis connection instance (optional).
168
+ account (str, optional): The account under which the value is stored. Defaults to "global".
169
+ default: The default value to return if key doesn't exist. Defaults to None.
170
+
171
+ Returns:
172
+ str or default: The stored value as string, or default if key doesn't exist.
173
+ """
174
+ if redis_con is None:
175
+ redis_con = redis.get_connection()
176
+
177
+ key = utils.generate_value_key(slug, account)
178
+ value = redis_con.get(key)
179
+
180
+ if value is not None:
181
+ return value.decode('utf-8')
182
+ return default
183
+
184
+
87
185
  def add_metrics_slug(slug, redis_con=None, account="global"):
88
186
  """
89
187
  Adds a metric slug to a Redis set for the specified account.
@@ -259,6 +357,7 @@ def set_view_perms(account, perms, redis_con=None):
259
357
  if perms is None:
260
358
  redis_con.delete(view_perm_key)
261
359
  else:
360
+ add_account(account, redis_con)
262
361
  if isinstance(perms, list):
263
362
  perms = ','.join(perms)
264
363
  redis_con.set(view_perm_key, perms)
@@ -282,6 +381,7 @@ def set_write_perms(account, perms, redis_con=None):
282
381
  if perms is None:
283
382
  redis_con.delete(write_perm_key)
284
383
  else:
384
+ add_account(account, redis_con)
285
385
  if isinstance(perms, list):
286
386
  perms = ','.join(perms)
287
387
  redis_con.set(write_perm_key, perms)
@@ -329,3 +429,101 @@ def get_write_perms(account, redis_con=None):
329
429
  if ',' in perms:
330
430
  perms = perms.split(',')
331
431
  return perms
432
+
433
+ def add_account(account, redis_con=None):
434
+ """
435
+ Adds a new account to the system.
436
+
437
+ Args:
438
+ account (str): The account to add.
439
+ redis_con: The Redis connection instance (optional).
440
+
441
+ Returns:
442
+ bool: True if the account was added successfully, False otherwise.
443
+ """
444
+ if redis_con is None:
445
+ redis_con = redis.get_connection()
446
+ accounts_key = utils.generate_accounts_key()
447
+ return redis_con.sadd(accounts_key, account) == 1
448
+
449
+ def list_accounts(redis_con=None):
450
+ """
451
+ Lists all accounts in the system.
452
+
453
+ Args:
454
+ redis_con: The Redis connection instance (optional).
455
+
456
+ Returns:
457
+ list: A list of all accounts in the system.
458
+ """
459
+ if redis_con is None:
460
+ redis_con = redis.get_connection()
461
+ accounts_key = utils.generate_accounts_key()
462
+ return [account.decode('utf-8') for account in redis_con.smembers(accounts_key)]
463
+
464
+ def delete_account(account, redis_con=None):
465
+ if redis_con is None:
466
+ redis_con = redis.get_connection()
467
+ set_view_perms(account, None, redis_con)
468
+ set_write_perms(account, None, redis_con)
469
+ accounts_key = utils.generate_accounts_key()
470
+ return redis_con.srem(accounts_key, account)
471
+
472
+
473
+
474
+ def get_accounts_with_permissions(redis_con=None):
475
+ """
476
+ Scans Redis to find all accounts that have view or write permissions configured.
477
+
478
+ Args:
479
+ redis_con: The Redis connection instance (optional).
480
+
481
+ Returns:
482
+ list: A list of dictionaries containing account information and their permissions.
483
+ """
484
+ if redis_con is None:
485
+ redis_con = redis.get_connection()
486
+
487
+ accounts = {}
488
+
489
+ # Scan for view permission keys
490
+ cursor = b'0'
491
+ while cursor != b'0':
492
+ cursor, keys = redis_con.scan(cursor=cursor, match="mets:*:perm:v")
493
+ for key in keys:
494
+ key_str = key.decode('utf-8')
495
+ # Extract account from key: mets:{account}:perm:v
496
+ parts = key_str.split(':')
497
+ if len(parts) >= 4:
498
+ account = parts[1]
499
+ if account not in accounts:
500
+ accounts[account] = {"account": account, "view_permissions": None, "write_permissions": None}
501
+
502
+ perms = redis_con.get(key)
503
+ if perms:
504
+ perms = perms.decode('utf-8')
505
+ if ',' in perms:
506
+ perms = perms.split(',')
507
+ accounts[account]["view_permissions"] = perms
508
+
509
+ # Scan for write permission keys
510
+ cursor = b'0'
511
+ while cursor != b'0':
512
+ cursor, keys = redis_con.scan(cursor=cursor, match="mets:*:perm:w")
513
+ for key in keys:
514
+ key_str = key.decode('utf-8')
515
+ # Extract account from key: mets:{account}:perm:w
516
+ parts = key_str.split(':')
517
+ if len(parts) >= 4:
518
+ account = parts[1]
519
+ if account not in accounts:
520
+ accounts[account] = {"account": account, "view_permissions": None, "write_permissions": None}
521
+
522
+ perms = redis_con.get(key)
523
+ if perms:
524
+ perms = perms.decode('utf-8')
525
+ if ',' in perms:
526
+ perms = perms.split(',')
527
+ accounts[account]["write_permissions"] = perms
528
+
529
+ return list(accounts.values())
@@ -1 +1,4 @@
1
1
  from .base import *
2
+ from .values import *
3
+ from .categories import *
4
+ from .permissions import *
@@ -0,0 +1,266 @@
1
+ from mojo import decorators as md
2
+ from mojo.apps import metrics
3
+ from mojo.helpers.response import JsonResponse
4
+ import mojo.errors
5
+ import datetime
6
+ from .helpers import check_view_permissions, check_write_permissions
7
+
8
+ # Documentation for API endpoints
9
+ CATEGORIES_LIST_DOCS = {
10
+ "summary": "List all categories",
11
+ "description": "Retrieves all categories for a specific account.",
12
+ "parameters": [
13
+ {
14
+ "name": "account",
15
+ "in": "query",
16
+ "schema": {"type": "string", "default": "public"},
17
+ "description": "Account identifier (e.g., 'public', 'global', or 'group_<id>')."
18
+ }
19
+ ],
20
+ "responses": {
21
+ "200": {
22
+ "description": "Successful response with list of categories.",
23
+ "content": {
24
+ "application/json": {
25
+ "example": {
26
+ "response": {
27
+ "categories": ["activity", "engagement", "performance"],
28
+ "account": "public",
29
+ "status": True
30
+ },
31
+ "status_code": 200
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ CATEGORY_SLUGS_DOCS = {
40
+ "summary": "Get slugs in a category",
41
+ "description": "Retrieves all slugs within a specific category.",
42
+ "parameters": [
43
+ {
44
+ "name": "category",
45
+ "in": "query",
46
+ "required": True,
47
+ "schema": {"type": "string"},
48
+ "description": "The category name to get slugs for."
49
+ },
50
+ {
51
+ "name": "account",
52
+ "in": "query",
53
+ "schema": {"type": "string", "default": "public"},
54
+ "description": "Account identifier (e.g., 'public', 'global', or 'group_<id>')."
55
+ }
56
+ ],
57
+ "responses": {
58
+ "200": {
59
+ "description": "Successful response with list of slugs in the category.",
60
+ "content": {
61
+ "application/json": {
62
+ "example": {
63
+ "response": {
64
+ "slugs": ["user_login", "user_signup", "user_logout"],
65
+ "category": "activity",
66
+ "account": "public",
67
+ "status": True
68
+ },
69
+ "status_code": 200
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ CATEGORY_FETCH_DOCS = {
78
+ "summary": "Fetch metrics for category",
79
+ "description": "Retrieves metrics data for all slugs within a category, date range, and granularity.",
80
+ "parameters": [
81
+ {
82
+ "name": "category",
83
+ "in": "query",
84
+ "required": True,
85
+ "schema": {"type": "string"},
86
+ "description": "The category name to fetch metrics for."
87
+ },
88
+ {
89
+ "name": "dt_start",
90
+ "in": "query",
91
+ "schema": {"type": "string", "format": "date-time"},
92
+ "description": "Start date and time for the data range."
93
+ },
94
+ {
95
+ "name": "dt_end",
96
+ "in": "query",
97
+ "schema": {"type": "string", "format": "date-time"},
98
+ "description": "End date and time for the data range."
99
+ },
100
+ {
101
+ "name": "account",
102
+ "in": "query",
103
+ "schema": {"type": "string", "default": "public"},
104
+ "description": "Account identifier (e.g., 'public', 'global', or 'group_<id>')."
105
+ },
106
+ {
107
+ "name": "granularity",
108
+ "in": "query",
109
+ "schema": {"type": "string", "default": "hours"},
110
+ "description": "Granularity of the data (e.g., 'hours', 'days', 'months', 'years')."
111
+ },
112
+ {
113
+ "name": "with_labels",
114
+ "in": "query",
115
+ "schema": {"type": "boolean", "default": False},
116
+ "description": "Include timestamp labels in response data."
117
+ }
118
+ ],
119
+ "responses": {
120
+ "200": {
121
+ "description": "Successful response with category metrics data.",
122
+ "content": {
123
+ "application/json": {
124
+ "example": {
125
+ "response": {
126
+ "data": {
127
+ "user_login": [1, 2, 3, 4],
128
+ "user_signup": [0, 1, 0, 2]
129
+ },
130
+ "category": "activity",
131
+ "account": "public",
132
+ "status": True
133
+ },
134
+ "status_code": 200
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ CATEGORY_DELETE_DOCS = {
143
+ "summary": "Delete category",
144
+ "description": "Deletes an entire category and all its associated slugs and data.",
145
+ "parameters": [
146
+ {
147
+ "name": "category",
148
+ "in": "body",
149
+ "required": True,
150
+ "schema": {"type": "string"},
151
+ "description": "The category name to delete."
152
+ },
153
+ {
154
+ "name": "account",
155
+ "in": "body",
156
+ "schema": {"type": "string", "default": "public"},
157
+ "description": "Account identifier (e.g., 'public', 'global', or 'group_<id>')."
158
+ }
159
+ ],
160
+ "responses": {
161
+ "200": {
162
+ "description": "Successful deletion of category.",
163
+ "content": {
164
+ "application/json": {
165
+ "example": {
166
+ "response": {
167
+ "deleted_category": "activity",
168
+ "account": "public",
169
+ "status": True
170
+ },
171
+ "status_code": 200
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+
180
+ @md.GET('categories', docs=CATEGORIES_LIST_DOCS)
181
+ def on_categories_list(request):
182
+ """
183
+ List all categories for an account.
184
+ """
185
+ account = request.DATA.get("account", "public")
186
+ check_view_permissions(request, account)
187
+
188
+ categories = list(metrics.get_categories(account=account))
189
+
190
+ return JsonResponse({
191
+ "categories": categories,
192
+ "account": account,
193
+ "status": True
194
+ })
195
+
196
+
197
+ @md.GET('category_slugs', docs=CATEGORY_SLUGS_DOCS)
198
+ @md.requires_params("category")
199
+ def on_category_slugs(request):
200
+ """
201
+ Get all slugs within a specific category.
202
+ """
203
+ category = request.DATA.get("category")
204
+ account = request.DATA.get("account", "public")
205
+ check_view_permissions(request, account)
206
+
207
+ slugs = list(metrics.get_category_slugs(category, account=account))
208
+
209
+ return JsonResponse({
210
+ "slugs": slugs,
211
+ "category": category,
212
+ "account": account,
213
+ "status": True
214
+ })
215
+
216
+
217
+ @md.GET('category_fetch', docs=CATEGORY_FETCH_DOCS)
218
+ @md.requires_params("category")
219
+ def on_category_fetch(request):
220
+ """
221
+ Fetch metrics for all slugs within a category.
222
+ """
223
+ category = request.DATA.get("category")
224
+ dt_start = request.DATA.get_typed("dt_start", typed=datetime.datetime)
225
+ dt_end = request.DATA.get_typed("dt_end", typed=datetime.datetime)
226
+ account = request.DATA.get("account", "public")
227
+ granularity = request.DATA.get("granularity", "hours")
228
+ with_labels = request.DATA.get_typed("with_labels", default=False, typed=bool)
229
+
230
+ check_view_permissions(request, account)
231
+
232
+ data = metrics.fetch_by_category(
233
+ category,
234
+ dt_start=dt_start,
235
+ dt_end=dt_end,
236
+ granularity=granularity,
237
+ account=account,
238
+ with_labels=with_labels
239
+ )
240
+
241
+ return JsonResponse({
242
+ "data": data,
243
+ "category": category,
244
+ "account": account,
245
+ "status": True
246
+ })
247
+
248
+
249
+ @md.DELETE('category_delete', docs=CATEGORY_DELETE_DOCS)
250
+ @md.requires_params("category")
251
+ def on_category_delete(request):
252
+ """
253
+ Delete an entire category and all its associated slugs and data.
254
+ """
255
+ category = request.DATA.get("category")
256
+ account = request.DATA.get("account", "public")
257
+
258
+ check_write_permissions(request, account)
259
+
260
+ metrics.delete_category(category, account=account)
261
+
262
+ return JsonResponse({
263
+ "deleted_category": category,
264
+ "account": account,
265
+ "status": True
266
+ })
@@ -0,0 +1,48 @@
1
+ from mojo.apps import metrics
2
+ import mojo.errors
3
+
4
+
5
+ def check_view_permissions(request, account="public"):
6
+ """
7
+ Helper function to check view permissions for metrics operations.
8
+
9
+ Args:
10
+ request: The Django request object
11
+ account: The account to check permissions for
12
+
13
+ Raises:
14
+ PermissionDeniedException: If user doesn't have proper permissions
15
+ """
16
+ if account == "global":
17
+ if not request.user.is_authenticated or not request.user.has_permission("view_metrics"):
18
+ raise mojo.errors.PermissionDeniedException()
19
+ elif account != "public":
20
+ perms = metrics.get_view_perms(account)
21
+ if not perms:
22
+ raise mojo.errors.PermissionDeniedException()
23
+ if perms != "public":
24
+ if not request.user.is_authenticated or not request.user.has_permission(perms):
25
+ raise mojo.errors.PermissionDeniedException()
26
+
27
+
28
+ def check_write_permissions(request, account="public"):
29
+ """
30
+ Helper function to check write permissions for metrics operations.
31
+
32
+ Args:
33
+ request: The Django request object
34
+ account: The account to check permissions for
35
+
36
+ Raises:
37
+ PermissionDeniedException: If user doesn't have proper permissions
38
+ """
39
+ if account == "global":
40
+ if not request.user.is_authenticated or not request.user.has_permission("write_metrics"):
41
+ raise mojo.errors.PermissionDeniedException()
42
+ elif account != "public":
43
+ perms = metrics.get_write_perms(account)
44
+ if not perms:
45
+ raise mojo.errors.PermissionDeniedException()
46
+ if perms != "public":
47
+ if not request.user.is_authenticated or not request.user.has_permission(perms):
48
+ raise mojo.errors.PermissionDeniedException()
@@ -0,0 +1,99 @@
1
+ from mojo import decorators as md
2
+ from mojo.apps import metrics
3
+ from mojo.helpers.response import JsonResponse
4
+ import mojo.errors
5
+
6
+
7
+ @md.URL('permissions')
8
+ @md.URL('permissions/<str:account>')
9
+ @md.requires_perms("manage_incidents")
10
+ def on_permissions(request, account=None):
11
+ if request.method == 'GET':
12
+ if account is None:
13
+ return on_list_permissions(request)
14
+ return on_get_permissions(request, account)
15
+ if request.method == 'POST':
16
+ if not account:
17
+ account = request.DATA.get("account", None)
18
+ if account:
19
+ return on_set_permissions(request, account)
20
+ if request.method == 'DELETE' and account:
21
+ return on_delete_permissions(request, account)
22
+ return JsonResponse({
23
+ "method": request.method,
24
+ "error": "Invalid method",
25
+ "status": False
26
+ })
27
+
28
+ def on_get_permissions(request, account):
29
+ """
30
+ Get current view and write permissions for an account.
31
+ """
32
+ view_perms = metrics.get_view_perms(account)
33
+ write_perms = metrics.get_write_perms(account)
34
+
35
+ return JsonResponse({
36
+ "id": account,
37
+ "account": account,
38
+ "view_permissions": view_perms,
39
+ "write_permissions": write_perms,
40
+ "status": True
41
+ })
42
+
43
+
44
+ def on_set_permissions(request, account):
45
+ """
46
+ Set view permissions for an account.
47
+ """
48
+ view_perms = request.DATA.get("view_permissions", "").split(",")
49
+ write_perms = request.DATA.get("write_permissions", "").split(",")
50
+
51
+ if view_perms:
52
+ metrics.set_view_perms(account, view_perms)
53
+ if write_perms:
54
+ metrics.set_write_perms(account, write_perms)
55
+
56
+ return JsonResponse({
57
+ "id": account,
58
+ "account": account,
59
+ "view_permissions": view_perms,
60
+ "write_permissions": write_perms,
61
+ "action": "set",
62
+ "status": True
63
+ })
64
+
65
+
66
+ def on_delete_permissions(request, account):
67
+ """
68
+ Remove all permissions for an account.
69
+ """
70
+ # Remove both view and write permissions
71
+ metrics.set_view_perms(account, None)
72
+ metrics.set_write_perms(account, None)
73
+
74
+ return JsonResponse({
75
+ "account": account,
76
+ "action": "deleted",
77
+ "status": True
78
+ })
79
+
80
+
81
+ def on_list_permissions(request):
82
+ """
83
+ List all accounts that have permissions configured.
84
+ """
85
+ accounts = metrics.list_accounts()
86
+ data = []
87
+ for account in accounts:
88
+ info = {"account": account, "id": account}
89
+ info["view_permissions"] = metrics.get_view_perms(account)
90
+ info["write_permissions"] = metrics.get_write_perms(account)
91
+ data.append(info)
92
+
93
+ return JsonResponse({
94
+ "data": data,
95
+ "size": 10,
96
+ "start": 0,
97
+ "count": len(accounts),
98
+ "status": True
99
+ })