django-nativemojo 0.1.10__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.10.dist-info/LICENSE +19 -0
- django_nativemojo-0.1.10.dist-info/METADATA +96 -0
- django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
- django_nativemojo-0.1.10.dist-info/RECORD +194 -0
- django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
- mojo/__init__.py +3 -0
- mojo/apps/account/__init__.py +1 -0
- mojo/apps/account/admin.py +91 -0
- mojo/apps/account/apps.py +16 -0
- mojo/apps/account/migrations/0001_initial.py +77 -0
- mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
- mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
- mojo/apps/account/migrations/__init__.py +0 -0
- mojo/apps/account/models/__init__.py +3 -0
- mojo/apps/account/models/group.py +98 -0
- mojo/apps/account/models/member.py +95 -0
- mojo/apps/account/models/pkey.py +18 -0
- mojo/apps/account/models/user.py +211 -0
- mojo/apps/account/rest/__init__.py +3 -0
- mojo/apps/account/rest/group.py +25 -0
- mojo/apps/account/rest/user.py +47 -0
- mojo/apps/account/utils/__init__.py +0 -0
- mojo/apps/account/utils/jwtoken.py +72 -0
- mojo/apps/account/utils/passkeys.py +54 -0
- mojo/apps/fileman/README.md +549 -0
- mojo/apps/fileman/__init__.py +0 -0
- mojo/apps/fileman/apps.py +15 -0
- mojo/apps/fileman/backends/__init__.py +117 -0
- mojo/apps/fileman/backends/base.py +319 -0
- mojo/apps/fileman/backends/filesystem.py +397 -0
- mojo/apps/fileman/backends/s3.py +398 -0
- mojo/apps/fileman/examples/configurations.py +378 -0
- mojo/apps/fileman/examples/usage_example.py +665 -0
- mojo/apps/fileman/management/__init__.py +1 -0
- mojo/apps/fileman/management/commands/__init__.py +1 -0
- mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
- mojo/apps/fileman/models/__init__.py +7 -0
- mojo/apps/fileman/models/file.py +292 -0
- mojo/apps/fileman/models/manager.py +227 -0
- mojo/apps/fileman/models/render.py +0 -0
- mojo/apps/fileman/rest/__init__ +0 -0
- mojo/apps/fileman/rest/__init__.py +23 -0
- mojo/apps/fileman/rest/fileman.py +13 -0
- mojo/apps/fileman/rest/upload.py +92 -0
- mojo/apps/fileman/utils/__init__.py +19 -0
- mojo/apps/fileman/utils/upload.py +616 -0
- mojo/apps/incident/__init__.py +1 -0
- mojo/apps/incident/handlers/__init__.py +3 -0
- mojo/apps/incident/handlers/event_handlers.py +142 -0
- mojo/apps/incident/migrations/0001_initial.py +83 -0
- mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
- mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
- mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
- mojo/apps/incident/migrations/__init__.py +0 -0
- mojo/apps/incident/models/__init__.py +3 -0
- mojo/apps/incident/models/event.py +135 -0
- mojo/apps/incident/models/incident.py +33 -0
- mojo/apps/incident/models/rule.py +247 -0
- mojo/apps/incident/parsers/__init__.py +0 -0
- mojo/apps/incident/parsers/ossec/__init__.py +1 -0
- mojo/apps/incident/parsers/ossec/core.py +82 -0
- mojo/apps/incident/parsers/ossec/parsed.py +23 -0
- mojo/apps/incident/parsers/ossec/rules.py +124 -0
- mojo/apps/incident/parsers/ossec/utils.py +169 -0
- mojo/apps/incident/reporter.py +42 -0
- mojo/apps/incident/rest/__init__.py +2 -0
- mojo/apps/incident/rest/event.py +23 -0
- mojo/apps/incident/rest/ossec.py +22 -0
- mojo/apps/logit/__init__.py +0 -0
- mojo/apps/logit/admin.py +37 -0
- mojo/apps/logit/migrations/0001_initial.py +32 -0
- mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
- mojo/apps/logit/migrations/0003_log_level.py +18 -0
- mojo/apps/logit/migrations/__init__.py +0 -0
- mojo/apps/logit/models/__init__.py +1 -0
- mojo/apps/logit/models/log.py +57 -0
- mojo/apps/logit/rest.py +9 -0
- mojo/apps/metrics/README.md +79 -0
- mojo/apps/metrics/__init__.py +12 -0
- mojo/apps/metrics/redis_metrics.py +331 -0
- mojo/apps/metrics/rest/__init__.py +1 -0
- mojo/apps/metrics/rest/base.py +152 -0
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/metrics/utils.py +227 -0
- mojo/apps/notify/README.md +91 -0
- mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
- mojo/apps/notify/__init__.py +0 -0
- mojo/apps/notify/admin.py +52 -0
- mojo/apps/notify/handlers/__init__.py +0 -0
- mojo/apps/notify/handlers/example_handlers.py +516 -0
- mojo/apps/notify/handlers/ses/__init__.py +25 -0
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +25 -0
- mojo/apps/notify/handlers/ses/message.py +86 -0
- mojo/apps/notify/management/__init__.py +0 -0
- mojo/apps/notify/management/commands/__init__.py +1 -0
- mojo/apps/notify/management/commands/process_notifications.py +370 -0
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +12 -0
- mojo/apps/notify/models/account.py +128 -0
- mojo/apps/notify/models/attachment.py +24 -0
- mojo/apps/notify/models/bounce.py +68 -0
- mojo/apps/notify/models/complaint.py +40 -0
- mojo/apps/notify/models/inbox.py +113 -0
- mojo/apps/notify/models/inbox_message.py +173 -0
- mojo/apps/notify/models/outbox.py +129 -0
- mojo/apps/notify/models/outbox_message.py +288 -0
- mojo/apps/notify/models/template.py +30 -0
- mojo/apps/notify/providers/__init__.py +0 -0
- mojo/apps/notify/providers/aws.py +73 -0
- mojo/apps/notify/rest/__init__.py +0 -0
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +2 -0
- mojo/apps/notify/utils/notifications.py +404 -0
- mojo/apps/notify/utils/parsing.py +202 -0
- mojo/apps/notify/utils/render.py +144 -0
- mojo/apps/tasks/README.md +118 -0
- mojo/apps/tasks/__init__.py +11 -0
- mojo/apps/tasks/manager.py +489 -0
- mojo/apps/tasks/rest/__init__.py +2 -0
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +62 -0
- mojo/apps/tasks/runner.py +174 -0
- mojo/apps/tasks/tq_handlers.py +14 -0
- mojo/decorators/__init__.py +3 -0
- mojo/decorators/auth.py +25 -0
- mojo/decorators/cron.py +31 -0
- mojo/decorators/http.py +132 -0
- mojo/decorators/validate.py +14 -0
- mojo/errors.py +88 -0
- mojo/helpers/__init__.py +0 -0
- mojo/helpers/aws/__init__.py +0 -0
- mojo/helpers/aws/client.py +8 -0
- mojo/helpers/aws/s3.py +268 -0
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/cron.py +79 -0
- mojo/helpers/crypto/__init__.py +4 -0
- mojo/helpers/crypto/aes.py +60 -0
- mojo/helpers/crypto/hash.py +59 -0
- mojo/helpers/crypto/privpub/__init__.py +1 -0
- mojo/helpers/crypto/privpub/hybrid.py +97 -0
- mojo/helpers/crypto/privpub/rsa.py +104 -0
- mojo/helpers/crypto/sign.py +36 -0
- mojo/helpers/crypto/too.l.py +25 -0
- mojo/helpers/crypto/utils.py +26 -0
- mojo/helpers/daemon.py +94 -0
- mojo/helpers/dates.py +69 -0
- mojo/helpers/dns/__init__.py +0 -0
- mojo/helpers/dns/godaddy.py +62 -0
- mojo/helpers/filetypes.py +128 -0
- mojo/helpers/logit.py +310 -0
- mojo/helpers/modules.py +95 -0
- mojo/helpers/paths.py +63 -0
- mojo/helpers/redis.py +10 -0
- mojo/helpers/request.py +89 -0
- mojo/helpers/request_parser.py +269 -0
- mojo/helpers/response.py +14 -0
- mojo/helpers/settings.py +146 -0
- mojo/helpers/sysinfo.py +140 -0
- mojo/helpers/ua.py +0 -0
- mojo/middleware/__init__.py +0 -0
- mojo/middleware/auth.py +26 -0
- mojo/middleware/logging.py +55 -0
- mojo/middleware/mojo.py +21 -0
- mojo/migrations/0001_initial.py +32 -0
- mojo/migrations/__init__.py +0 -0
- mojo/models/__init__.py +2 -0
- mojo/models/meta.py +262 -0
- mojo/models/rest.py +538 -0
- mojo/models/secrets.py +59 -0
- mojo/rest/__init__.py +1 -0
- mojo/rest/info.py +26 -0
- mojo/serializers/__init__.py +0 -0
- mojo/serializers/models.py +165 -0
- mojo/serializers/openapi.py +188 -0
- mojo/urls.py +38 -0
- mojo/ws4redis/README.md +174 -0
- mojo/ws4redis/__init__.py +2 -0
- mojo/ws4redis/client.py +283 -0
- mojo/ws4redis/connection.py +327 -0
- mojo/ws4redis/exceptions.py +32 -0
- mojo/ws4redis/redis.py +183 -0
- mojo/ws4redis/servers/__init__.py +0 -0
- mojo/ws4redis/servers/base.py +86 -0
- mojo/ws4redis/servers/django.py +171 -0
- mojo/ws4redis/servers/uwsgi.py +63 -0
- mojo/ws4redis/settings.py +45 -0
- mojo/ws4redis/utf8validator.py +128 -0
- mojo/ws4redis/websocket.py +403 -0
- testit/__init__.py +0 -0
- testit/client.py +147 -0
- testit/faker.py +20 -0
- testit/helpers.py +198 -0
- testit/runner.py +262 -0
@@ -0,0 +1,152 @@
|
|
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
|
+
import datetime
|
7
|
+
@md.POST('record', docs={
|
8
|
+
"summary": "Record metrics data",
|
9
|
+
"description": "Records metrics data for specified slug for the specified account, increments by 1 each call.",
|
10
|
+
"parameters": [
|
11
|
+
{
|
12
|
+
"name": "slug",
|
13
|
+
"in": "body",
|
14
|
+
"required": True,
|
15
|
+
"schema": {"type": "string"},
|
16
|
+
"description": "Unique identifier for the metric to be recorded."
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"name": "account",
|
20
|
+
"in": "body",
|
21
|
+
"required": False,
|
22
|
+
"schema": {"type": "string", "default": "public"},
|
23
|
+
"description": "Account identifier (e.g., 'public', 'global', or 'group_<id>')."
|
24
|
+
},
|
25
|
+
{
|
26
|
+
"name": "count",
|
27
|
+
"in": "body",
|
28
|
+
"required": False,
|
29
|
+
"schema": {"type": "integer", "default": 1},
|
30
|
+
"description": "The count by which the metric data should be incremented."
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"name": "min_granularity",
|
34
|
+
"in": "body",
|
35
|
+
"required": False,
|
36
|
+
"schema": {"type": "string", "default": "hours"},
|
37
|
+
"description": "Minimum granularity of the metric (e.g., 'hours')."
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"name": "max_granularity",
|
41
|
+
"in": "body",
|
42
|
+
"required": False,
|
43
|
+
"schema": {"type": "string", "default": "years"},
|
44
|
+
"description": "Maximum granularity of the metric (e.g., 'years')."
|
45
|
+
}
|
46
|
+
]
|
47
|
+
})
|
48
|
+
@md.requires_params("slug")
|
49
|
+
def on_metrics_record(request):
|
50
|
+
account = request.DATA.get("account", "public")
|
51
|
+
count = request.DATA.get_typed("count", default=1, typed=int)
|
52
|
+
min_granularity = request.DATA.get("min_granularity", "hours")
|
53
|
+
max_granularity = request.DATA.get("max_granularity", "years")
|
54
|
+
if account != "public":
|
55
|
+
perms = metrics.get_write_perms(account)
|
56
|
+
if not perms:
|
57
|
+
raise mojo.errors.PermissionDeniedException()
|
58
|
+
if perms != "public":
|
59
|
+
if not request.user.is_authenticated or not request.user.has_permission(perms):
|
60
|
+
raise mojo.errors.PermissionDeniedException()
|
61
|
+
metrics.record(request.DATA.slug, count=count, min_granularity=min_granularity,
|
62
|
+
max_granularity=max_granularity)
|
63
|
+
return JsonResponse(dict(status=True))
|
64
|
+
|
65
|
+
|
66
|
+
@md.GET('fetch', docs={
|
67
|
+
"summary": "Fetch metrics data",
|
68
|
+
"description": "Retrieves metrics data for specified slugs within a given date range and granularity.",
|
69
|
+
"parameters": [
|
70
|
+
{
|
71
|
+
"name": "slugs",
|
72
|
+
"in": "query",
|
73
|
+
"required": True,
|
74
|
+
"schema": {"type": "array", "items": {"type": "string"}},
|
75
|
+
"description": "List of slugs to fetch metrics for."
|
76
|
+
},
|
77
|
+
{
|
78
|
+
"name": "dt_start",
|
79
|
+
"in": "query",
|
80
|
+
"schema": {"type": "string", "format": "date-time"},
|
81
|
+
"description": "Start date and time for the data range."
|
82
|
+
},
|
83
|
+
{
|
84
|
+
"name": "dt_end",
|
85
|
+
"in": "query",
|
86
|
+
"schema": {"type": "string", "format": "date-time"},
|
87
|
+
"description": "End date and time for the data range."
|
88
|
+
},
|
89
|
+
{
|
90
|
+
"name": "account",
|
91
|
+
"in": "query",
|
92
|
+
"schema": {"type": "string"},
|
93
|
+
"description": "Account identifier (e.g., 'public', 'global', or 'group_<id>')."
|
94
|
+
},
|
95
|
+
{
|
96
|
+
"name": "granularity",
|
97
|
+
"in": "query",
|
98
|
+
"schema": {"type": "string", "default": "hours"},
|
99
|
+
"description": "Granularity of the data (e.g., 'hours')."
|
100
|
+
}
|
101
|
+
],
|
102
|
+
"responses": {
|
103
|
+
"200": {
|
104
|
+
"description": "Successful response with metrics data.",
|
105
|
+
"content": {
|
106
|
+
"application/json": {
|
107
|
+
"example": {
|
108
|
+
"response": {
|
109
|
+
"data": {
|
110
|
+
"data": {
|
111
|
+
"slug": "c3",
|
112
|
+
"values": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
113
|
+
},
|
114
|
+
"periods": [
|
115
|
+
"03:00", "04:00", "05:00", "06:00", "07:00",
|
116
|
+
"08:00", "09:00", "10:00", "11:00", "12:00",
|
117
|
+
"13:00", "14:00"
|
118
|
+
]
|
119
|
+
},
|
120
|
+
"status": True
|
121
|
+
},
|
122
|
+
"status_code": 200
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
})
|
129
|
+
@md.requires_params("slugs")
|
130
|
+
def on_metrics_data(request):
|
131
|
+
"""
|
132
|
+
TODO add support for group based permissions where account == "group_<id>"
|
133
|
+
"""
|
134
|
+
dt_start = request.DATA.get_typed("dt_start", typed=datetime.datetime)
|
135
|
+
dt_end = request.DATA.get_typed("dt_end", typed=datetime.datetime)
|
136
|
+
account = request.DATA.get("account", "public")
|
137
|
+
granularity = request.DATA.get("granularity", "hours")
|
138
|
+
if account == "global":
|
139
|
+
if not request.user.is_authenticated or not request.user.has_permission("view_metrics"):
|
140
|
+
raise mojo.errors.PermissionDeniedException()
|
141
|
+
elif account != "public":
|
142
|
+
perms = metrics.get_view_perms(account)
|
143
|
+
if not perms:
|
144
|
+
raise mojo.errors.PermissionDeniedException()
|
145
|
+
if perms and not request.user.has_permission(perms):
|
146
|
+
raise mojo.errors.PermissionDeniedException()
|
147
|
+
slugs = request.DATA.get_typed("slugs", typed=list)
|
148
|
+
if len(slugs) == 1:
|
149
|
+
slugs = slugs[0]
|
150
|
+
records = metrics.fetch(slugs, dt_start=dt_start, dt_end=dt_end,
|
151
|
+
granularity=granularity, account=account, with_labels=True)
|
152
|
+
return JsonResponse(dict(status=True, data=records))
|
File without changes
|
@@ -0,0 +1,227 @@
|
|
1
|
+
import time
|
2
|
+
from datetime import timedelta
|
3
|
+
import datetime
|
4
|
+
from mojo.helpers.settings import settings
|
5
|
+
from mojo.helpers import dates
|
6
|
+
|
7
|
+
GRANULARITIES = ['minutes', 'hours', 'days', 'weeks', 'months', 'years']
|
8
|
+
GRANULARITY_PREFIX_MAP = {
|
9
|
+
'minutes': 'min',
|
10
|
+
'hours': 'hr',
|
11
|
+
'days': 'day',
|
12
|
+
'weeks': 'wk',
|
13
|
+
'months': 'mon',
|
14
|
+
'years': 'yr'
|
15
|
+
}
|
16
|
+
|
17
|
+
GRANULARITY_EXPIRES_DAYS = {
|
18
|
+
'minutes': 1, # 24 hours?
|
19
|
+
'hours': 3, # 72 hours?
|
20
|
+
'days': 360,
|
21
|
+
'weeks': 360,
|
22
|
+
'months': None,
|
23
|
+
'years': None
|
24
|
+
}
|
25
|
+
|
26
|
+
GRANULARITY_OFFSET_MAP = {
|
27
|
+
'minutes': timedelta(minutes=1),
|
28
|
+
'hours': timedelta(hours=1),
|
29
|
+
'days': timedelta(days=1),
|
30
|
+
'weeks': timedelta(weeks=1),
|
31
|
+
'months': 'months',
|
32
|
+
'years': 'years'
|
33
|
+
}
|
34
|
+
|
35
|
+
GRANULARITY_END_MAP = {
|
36
|
+
'minutes': timedelta(minutes=29),
|
37
|
+
'hours': timedelta(hours=11),
|
38
|
+
'days': timedelta(days=11),
|
39
|
+
'weeks': timedelta(weeks=11),
|
40
|
+
'months': timedelta(days=12*30),
|
41
|
+
'years': timedelta(days=11*360)
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
DEFAULT_MIN_GRANULARITY = settings.get("METRICS_DEFAULT_MIN_GRANULARITY", "hours")
|
46
|
+
DEFAULT_MAX_GRANULARITY = settings.get("METRICS_DEFAULT_MAX_GRANULARITY", "years")
|
47
|
+
|
48
|
+
METRICS_TIMEZONE = settings.get("METRICS_TIMEZONE", "America/Los_Angeles")
|
49
|
+
|
50
|
+
def generate_granularities(min_granularity=DEFAULT_MIN_GRANULARITY,
|
51
|
+
max_granularity=DEFAULT_MAX_GRANULARITY):
|
52
|
+
"""
|
53
|
+
Generate a list of granularities between a minimum and maximum level.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
min_granularity (str): The minimum granularity level.
|
57
|
+
max_granularity (str): The maximum granularity level.
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
list: A list of granularities from min_granularity to max_granularity.
|
61
|
+
|
62
|
+
Raises:
|
63
|
+
ValueError: If the specified granularities are invalid or
|
64
|
+
min_granularity is greater than max_granularity.
|
65
|
+
"""
|
66
|
+
all_granularities = ['minutes', 'hours', 'days', 'weeks', 'months', 'years']
|
67
|
+
|
68
|
+
if min_granularity not in all_granularities or max_granularity not in all_granularities:
|
69
|
+
raise ValueError(
|
70
|
+
"Invalid granularity. Choose from 'minutes', 'hours', 'days', "
|
71
|
+
"'weeks', 'months', 'years'."
|
72
|
+
)
|
73
|
+
|
74
|
+
min_index = all_granularities.index(min_granularity)
|
75
|
+
max_index = all_granularities.index(max_granularity)
|
76
|
+
if min_index > max_index:
|
77
|
+
raise ValueError("min_granularity must be less than or equal to max_granularity.")
|
78
|
+
return all_granularities[min_index:max_index + 1]
|
79
|
+
|
80
|
+
|
81
|
+
def generate_slug(slug, date, granularity, account="global"):
|
82
|
+
"""
|
83
|
+
Generate a slug for a given date and granularity.
|
84
|
+
|
85
|
+
Args:
|
86
|
+
date: The date to format.
|
87
|
+
granularity (str): The granularity level for the slug.
|
88
|
+
*args: Additional strings to include in the slug prefix, separated by
|
89
|
+
colons.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
str: A formatted slug.
|
93
|
+
|
94
|
+
Raises:
|
95
|
+
ValueError: If the specified granularity is invalid for slug generation.
|
96
|
+
"""
|
97
|
+
if granularity not in ['minutes', 'hours', 'days', 'weeks', 'months', 'years']:
|
98
|
+
raise ValueError("Invalid granularity for slug generation.")
|
99
|
+
gran_prefix = GRANULARITY_PREFIX_MAP.get(granularity)
|
100
|
+
if granularity == 'minutes':
|
101
|
+
date_slug = date.strftime('%Y-%m-%dT%H-%M')
|
102
|
+
elif granularity == 'hours':
|
103
|
+
date_slug = date.strftime('%Y-%m-%dT%H')
|
104
|
+
elif granularity == 'days':
|
105
|
+
date_slug = date.strftime('%Y-%m-%d')
|
106
|
+
elif granularity == 'weeks':
|
107
|
+
date_slug = date.strftime('%Y-%U')
|
108
|
+
elif granularity == 'months':
|
109
|
+
date_slug = date.strftime('%Y-%m')
|
110
|
+
elif granularity == 'years':
|
111
|
+
date_slug = date.strftime('%Y')
|
112
|
+
else:
|
113
|
+
raise ValueError("Unhandled granularity.")
|
114
|
+
prefix = generate_slug_prefix(slug, account)
|
115
|
+
return f"{prefix}:{gran_prefix}:{date_slug}"
|
116
|
+
|
117
|
+
|
118
|
+
def generate_slug_prefix(slug, account):
|
119
|
+
# this is the slug without the date
|
120
|
+
slug = normalize_slug(slug)
|
121
|
+
return f"mets:{account}::{slug}"
|
122
|
+
|
123
|
+
|
124
|
+
def generate_slugs_for_range(slug, dt_start, dt_end, granularity, account="global"):
|
125
|
+
"""
|
126
|
+
Generate slugs for dates in a specified range with the given granularity.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
slug (str): The base slug to use in generating slugs for the range.
|
130
|
+
dt_start (datetime): The start date of the range.
|
131
|
+
dt_end (datetime): The end date of the range.
|
132
|
+
granularity (str): The granularity level for iteration.
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
list: A list of generated slugs for each date/time in the range at the specified granularity.
|
136
|
+
|
137
|
+
Raises:
|
138
|
+
ValueError: If the specified granularity is invalid.
|
139
|
+
"""
|
140
|
+
|
141
|
+
|
142
|
+
if granularity not in GRANULARITY_OFFSET_MAP:
|
143
|
+
raise ValueError("Invalid granularity for slug generation.")
|
144
|
+
dt_start, dt_end = get_date_range(dt_start, dt_end, granularity)
|
145
|
+
current = dt_start
|
146
|
+
slugs = []
|
147
|
+
|
148
|
+
if granularity in ['minutes', 'hours', 'days', 'weeks']:
|
149
|
+
delta = GRANULARITY_OFFSET_MAP[granularity]
|
150
|
+
while current <= dt_end:
|
151
|
+
slugs.append(generate_slug(slug, current, granularity, account))
|
152
|
+
current += delta
|
153
|
+
elif granularity == 'months':
|
154
|
+
while current <= dt_end:
|
155
|
+
slugs.append(generate_slug(slug, current, granularity, account))
|
156
|
+
current = datetime.datetime(current.year + (current.month // 12), ((current.month % 12) + 1), 1, tzinfo=current.tzinfo)
|
157
|
+
elif granularity == 'years':
|
158
|
+
while current <= dt_end:
|
159
|
+
slugs.append(generate_slug(slug, current, granularity, account))
|
160
|
+
current = datetime.datetime(current.year + 1, 1, 1, tzinfo=current.tzinfo)
|
161
|
+
return slugs
|
162
|
+
|
163
|
+
|
164
|
+
def generate_category_slug(account, category):
|
165
|
+
return f"mets:{account}:c:{category}"
|
166
|
+
|
167
|
+
|
168
|
+
def generate_category_key(account):
|
169
|
+
return f"mets:{account}:cats"
|
170
|
+
|
171
|
+
def generate_slugs_key(account):
|
172
|
+
return f"mets:{account}:slugs"
|
173
|
+
|
174
|
+
|
175
|
+
def generate_perm_write_key(account):
|
176
|
+
return f"mets:{account}:perm:w"
|
177
|
+
|
178
|
+
|
179
|
+
def generate_perm_view_key(account):
|
180
|
+
return f"mets:{account}:perm:v"
|
181
|
+
|
182
|
+
|
183
|
+
def normalize_slug(slug):
|
184
|
+
return slug.replace(':', '|')
|
185
|
+
|
186
|
+
|
187
|
+
def periods_from_dr_slugs(slugs):
|
188
|
+
if isinstance(slugs, (list, set)):
|
189
|
+
result = []
|
190
|
+
for s in slugs:
|
191
|
+
result.append(period_from_dr_slug(s))
|
192
|
+
return result
|
193
|
+
|
194
|
+
def period_from_dr_slug(slug):
|
195
|
+
parts = slug.split(":")
|
196
|
+
gran_lbl = parts[-2]
|
197
|
+
period = parts[-1]
|
198
|
+
if gran_lbl == GRANULARITY_PREFIX_MAP["hours"]:
|
199
|
+
return f"{period[-2:]}:00"
|
200
|
+
if gran_lbl == GRANULARITY_PREFIX_MAP["minutes"]:
|
201
|
+
return period[-5:].replace('-', ':')
|
202
|
+
return period
|
203
|
+
|
204
|
+
|
205
|
+
def get_expires_at(granularity, slug, category=None):
|
206
|
+
days = GRANULARITY_EXPIRES_DAYS.get(granularity, None)
|
207
|
+
if days is None:
|
208
|
+
return None
|
209
|
+
return int(time.time() + (days * 24 * 60 * 60))
|
210
|
+
|
211
|
+
def normalize_datetime(when, timezone=None):
|
212
|
+
if when is not None and when.tzinfo is not None:
|
213
|
+
return when
|
214
|
+
if timezone is None:
|
215
|
+
timezone = METRICS_TIMEZONE
|
216
|
+
return dates.get_local_time(timezone, when)
|
217
|
+
|
218
|
+
|
219
|
+
def get_date_range(dt_start, dt_end, granularity):
|
220
|
+
if dt_start is None and dt_end is None:
|
221
|
+
dt_end = normalize_datetime(None)
|
222
|
+
dt_start = dt_end - GRANULARITY_END_MAP[granularity]
|
223
|
+
elif dt_end is None:
|
224
|
+
dt_end = dt_start + GRANULARITY_END_MAP[granularity]
|
225
|
+
elif dt_start is None:
|
226
|
+
dt_start = dt_end - GRANULARITY_END_MAP[granularity]
|
227
|
+
return dt_start, dt_end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# AWS SES (Simple Email Service)
|
2
|
+
|
3
|
+
AWS SES is an email sending and receiving service provided by **Amazon Web Services**. It provides a robust and scalable platform for marketers and developers to send marketing, transactional, and notification emails.
|
4
|
+
|
5
|
+
## How it Works:
|
6
|
+
|
7
|
+
### 1. **Email Sending**:
|
8
|
+
To send emails, users first authenticate themselves using SMTP (Simple Mail Transfer Protocol) credentials or a standard AWS SDK. After authentication, users send a request to SES that contains the content of their email and information about who should receive it. SES then sends the email on the user's behalf.
|
9
|
+
|
10
|
+
|
11
|
+
## INBOX SETUP
|
12
|
+
|
13
|
+
### Setup IAM user with SES Sending permissions
|
14
|
+
|
15
|
+
```json
|
16
|
+
{
|
17
|
+
"Version": "2012-10-17",
|
18
|
+
"Statement": [
|
19
|
+
{
|
20
|
+
"Effect": "Allow",
|
21
|
+
"Action": "ses:SendRawEmail",
|
22
|
+
"Resource": "*"
|
23
|
+
}
|
24
|
+
]
|
25
|
+
}
|
26
|
+
```
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
#### Store credentials in settings.core.email
|
31
|
+
|
32
|
+
```
|
33
|
+
SES_ACCESS_KEY = "***"
|
34
|
+
SES_SECRET_KEY = "***"
|
35
|
+
SES_REGION = "us-east-1"
|
36
|
+
|
37
|
+
EMAIL_S3_BUCKET = "YOUR_EMAIL_BUCKET"
|
38
|
+
EMAIL_USE_TLS = True
|
39
|
+
EMAIL_HOST = 'email-smtp.us-east-1.amazonaws.com'
|
40
|
+
EMAIL_HOST_USER = '***'
|
41
|
+
EMAIL_HOST_PASSWORD = '***'
|
42
|
+
EMAIL_PORT = 587
|
43
|
+
```
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
### Goto AWS SES Admin
|
48
|
+
|
49
|
+
DNS Records needs to include
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
| Type | Name | Priority | Value |
|
54
|
+
| :--- | :------------- | -------- | :-------------------------------------------- |
|
55
|
+
| MX | mail.DOMAIN.io | 10 | **feedback**-smtp.us-east-1.amazon**ses**.com |
|
56
|
+
| MX | @ | 10 | **inbound**-smtp.us-east-1.amazon**aws**.com |
|
57
|
+
| TXT | mail.DOMAIN.io | | "v=spf1 include:amazonses.com ~all" |
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
## Setup Email Receiving
|
62
|
+
|
63
|
+
```
|
64
|
+
{
|
65
|
+
"Version": "2012-10-17",
|
66
|
+
"Statement": [
|
67
|
+
{
|
68
|
+
"Sid": "AllowSESPuts",
|
69
|
+
"Effect": "Allow",
|
70
|
+
"Principal": {
|
71
|
+
"Service": "ses.amazonaws.com"
|
72
|
+
},
|
73
|
+
"Action": "s3:PutObject",
|
74
|
+
"Resource": "arn:aws:s3:::AWSDOC-EXAMPLE-BUCKET/*",
|
75
|
+
"Condition": {
|
76
|
+
"StringEquals": {
|
77
|
+
"aws:Referer": "111122223333"
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
]
|
82
|
+
}
|
83
|
+
```
|
84
|
+
|
85
|
+
|
86
|
+
https://aws.amazon.com/premiumsupport/knowledge-center/ses-receive-inbound-emails/
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
|