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.
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/METADATA +3 -1
- django_nativemojo-0.1.16.dist-info/RECORD +302 -0
- mojo/__init__.py +1 -1
- mojo/apps/account/management/commands/serializer_admin.py +121 -1
- 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 +294 -8
- mojo/apps/account/models/member.py +14 -1
- 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 +190 -17
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +8 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +95 -5
- 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 +6 -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/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/backends/s3.py +209 -0
- mojo/apps/fileman/models/file.py +45 -9
- mojo/apps/fileman/models/manager.py +269 -3
- 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 +1 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/incident.py +2 -0
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -3
- mojo/apps/incident/rest/__init__.py +1 -0
- 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/models/log.py +3 -0
- 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 +17 -0
- mojo/decorators/http.py +40 -1
- mojo/helpers/aws/__init__.py +11 -7
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -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 +8 -0
- mojo/middleware/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +5 -0
- mojo/models/rest.py +271 -57
- mojo/models/secrets.py +86 -0
- mojo/serializers/__init__.py +16 -10
- 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/{manager.py → core/manager.py} +53 -4
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/{advanced/formats → formats}/csv.py +116 -139
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +14 -0
- testit/runner.py +23 -6
- django_nativemojo-0.1.15.dist-info/RECORD +0 -234
- 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/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 -44
- mojo/apps/tasks/manager.py +0 -644
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -76
- mojo/apps/tasks/runner.py +0 -439
- mojo/apps/tasks/task.py +0 -99
- mojo/apps/tasks/tq_handlers.py +0 -132
- mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
- mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
- mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- mojo/serializers/advanced/README.md +0 -363
- mojo/serializers/advanced/__init__.py +0 -247
- mojo/serializers/advanced/formats/__init__.py +0 -28
- mojo/serializers/advanced/formats/excel.py +0 -516
- mojo/serializers/advanced/formats/json.py +0 -239
- mojo/serializers/advanced/formats/response.py +0 -485
- mojo/serializers/advanced/serializer.py +0 -568
- mojo/serializers/optimized.py +0 -618
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/providers → jobs/examples}/__init__.py +0 -0
- /mojo/apps/{notify/rest → jobs/migrations}/__init__.py +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/serializers/{settings_example.py → examples/settings.py} +0 -0
- /mojo/{apps/notify/handlers/ses/bounce.py → serializers/formats/__init__.py} +0 -0
- /mojo/serializers/{advanced/formats → formats}/localizers.py +0 -0
mojo/apps/metrics/__init__.py
CHANGED
@@ -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())
|
@@ -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
|
+
})
|