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,319 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Dict, Any, Optional, Tuple, List
|
3
|
+
from datetime import datetime, timedelta
|
4
|
+
import os
|
5
|
+
|
6
|
+
|
7
|
+
class StorageBackend(ABC):
|
8
|
+
"""
|
9
|
+
Abstract base class for all storage backends
|
10
|
+
"""
|
11
|
+
|
12
|
+
def __init__(self, file_manager, **kwargs):
|
13
|
+
"""
|
14
|
+
Initialize the storage backend with a FileManager instance
|
15
|
+
|
16
|
+
Args:
|
17
|
+
file_manager: FileManager instance with configuration
|
18
|
+
**kwargs: Additional backend-specific configuration
|
19
|
+
"""
|
20
|
+
self.file_manager = file_manager
|
21
|
+
self.settings = file_manager.settings
|
22
|
+
self.backend_url = file_manager.backend_url
|
23
|
+
self.config = kwargs
|
24
|
+
|
25
|
+
@abstractmethod
|
26
|
+
def save(self, file_obj, filename: str, **kwargs) -> str:
|
27
|
+
"""
|
28
|
+
Save a file to the storage backend
|
29
|
+
|
30
|
+
Args:
|
31
|
+
file_obj: File-like object to save
|
32
|
+
filename: Name to save the file as
|
33
|
+
**kwargs: Additional save options
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
str: Full path to the saved file
|
37
|
+
"""
|
38
|
+
pass
|
39
|
+
|
40
|
+
@abstractmethod
|
41
|
+
def delete(self, file_path: str) -> bool:
|
42
|
+
"""
|
43
|
+
Delete a file from the storage backend
|
44
|
+
|
45
|
+
Args:
|
46
|
+
file_path: Path to the file to delete
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
bool: True if deletion was successful
|
50
|
+
"""
|
51
|
+
pass
|
52
|
+
|
53
|
+
@abstractmethod
|
54
|
+
def exists(self, file_path: str) -> bool:
|
55
|
+
"""
|
56
|
+
Check if a file exists in the storage backend
|
57
|
+
|
58
|
+
Args:
|
59
|
+
file_path: Path to check
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
bool: True if file exists
|
63
|
+
"""
|
64
|
+
pass
|
65
|
+
|
66
|
+
@abstractmethod
|
67
|
+
def get_file_size(self, file_path: str) -> Optional[int]:
|
68
|
+
"""
|
69
|
+
Get the size of a file in bytes
|
70
|
+
|
71
|
+
Args:
|
72
|
+
file_path: Path to the file
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
Optional[int]: File size in bytes, None if file doesn't exist
|
76
|
+
"""
|
77
|
+
pass
|
78
|
+
|
79
|
+
@abstractmethod
|
80
|
+
def get_url(self, file_path: str, expires_in: Optional[int] = None) -> str:
|
81
|
+
"""
|
82
|
+
Get a URL to access the file
|
83
|
+
|
84
|
+
Args:
|
85
|
+
file_path: Path to the file
|
86
|
+
expires_in: Optional expiration time in seconds
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
str: URL to access the file
|
90
|
+
"""
|
91
|
+
pass
|
92
|
+
|
93
|
+
@abstractmethod
|
94
|
+
def generate_upload_url(self, file_path: str, content_type: str,
|
95
|
+
file_size: Optional[int] = None,
|
96
|
+
expires_in: int = 3600) -> Dict[str, Any]:
|
97
|
+
"""
|
98
|
+
Generate a pre-signed URL for direct upload
|
99
|
+
|
100
|
+
Args:
|
101
|
+
file_path: Path where the file will be stored
|
102
|
+
content_type: MIME type of the file
|
103
|
+
file_size: Expected file size in bytes
|
104
|
+
expires_in: URL expiration time in seconds
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
Dict containing:
|
108
|
+
- upload_url: Pre-signed upload URL
|
109
|
+
- method: HTTP method to use (POST, PUT, etc.)
|
110
|
+
- fields: Additional form fields (if any)
|
111
|
+
- headers: Required headers (if any)
|
112
|
+
"""
|
113
|
+
pass
|
114
|
+
|
115
|
+
def supports_direct_upload(self) -> bool:
|
116
|
+
"""
|
117
|
+
Check if this backend supports direct uploads
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
bool: True if direct uploads are supported
|
121
|
+
"""
|
122
|
+
return self.file_manager.supports_direct_upload
|
123
|
+
|
124
|
+
def validate_upload(self, file_path: str, upload_token: str,
|
125
|
+
expected_size: Optional[int] = None,
|
126
|
+
expected_checksum: Optional[str] = None) -> Tuple[bool, str]:
|
127
|
+
"""
|
128
|
+
Validate that an uploaded file matches expectations
|
129
|
+
|
130
|
+
Args:
|
131
|
+
file_path: Path to the uploaded file
|
132
|
+
upload_token: Token used for the upload
|
133
|
+
expected_size: Expected file size
|
134
|
+
expected_checksum: Expected file checksum
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
Tuple[bool, str]: (is_valid, error_message)
|
138
|
+
"""
|
139
|
+
if not self.exists(file_path):
|
140
|
+
return False, "File does not exist after upload"
|
141
|
+
|
142
|
+
if expected_size:
|
143
|
+
actual_size = self.get_file_size(file_path)
|
144
|
+
if actual_size != expected_size:
|
145
|
+
return False, f"File size mismatch: expected {expected_size}, got {actual_size}"
|
146
|
+
|
147
|
+
if expected_checksum:
|
148
|
+
actual_checksum = self.get_file_checksum(file_path)
|
149
|
+
if actual_checksum != expected_checksum:
|
150
|
+
return False, f"Checksum mismatch: expected {expected_checksum}, got {actual_checksum}"
|
151
|
+
|
152
|
+
return True, "Upload validation successful"
|
153
|
+
|
154
|
+
def get_file_checksum(self, file_path: str, algorithm: str = 'md5') -> Optional[str]:
|
155
|
+
"""
|
156
|
+
Calculate checksum of a file
|
157
|
+
|
158
|
+
Args:
|
159
|
+
file_path: Path to the file
|
160
|
+
algorithm: Hash algorithm to use (md5, sha256, etc.)
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
Optional[str]: File checksum, None if calculation fails
|
164
|
+
"""
|
165
|
+
# Default implementation - backends can override for efficiency
|
166
|
+
try:
|
167
|
+
import hashlib
|
168
|
+
hash_obj = hashlib.new(algorithm)
|
169
|
+
|
170
|
+
with self.open(file_path, 'rb') as f:
|
171
|
+
for chunk in iter(lambda: f.read(4096), b""):
|
172
|
+
hash_obj.update(chunk)
|
173
|
+
|
174
|
+
return hash_obj.hexdigest()
|
175
|
+
except Exception:
|
176
|
+
return None
|
177
|
+
|
178
|
+
def open(self, file_path: str, mode: str = 'rb'):
|
179
|
+
"""
|
180
|
+
Open a file from the storage backend
|
181
|
+
|
182
|
+
Args:
|
183
|
+
file_path: Path to the file
|
184
|
+
mode: File open mode
|
185
|
+
|
186
|
+
Returns:
|
187
|
+
File-like object
|
188
|
+
"""
|
189
|
+
raise NotImplementedError("Backend does not support file opening")
|
190
|
+
|
191
|
+
def generate_file_path(self, filename: str, group_id: Optional[int] = None) -> str:
|
192
|
+
"""
|
193
|
+
Generate a storage path for a file
|
194
|
+
|
195
|
+
Args:
|
196
|
+
filename: Original filename
|
197
|
+
group_id: Optional group ID for organization
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
str: Generated file path
|
201
|
+
"""
|
202
|
+
# Default implementation - backends can override
|
203
|
+
parts = []
|
204
|
+
|
205
|
+
if group_id:
|
206
|
+
parts.append(f"group_{group_id}")
|
207
|
+
|
208
|
+
# Add date-based organization
|
209
|
+
now = datetime.now()
|
210
|
+
parts.extend([
|
211
|
+
str(now.year),
|
212
|
+
f"{now.month:02d}",
|
213
|
+
f"{now.day:02d}"
|
214
|
+
])
|
215
|
+
|
216
|
+
parts.append(filename)
|
217
|
+
return "/".join(parts)
|
218
|
+
|
219
|
+
def get_available_space(self) -> Optional[int]:
|
220
|
+
"""
|
221
|
+
Get available storage space in bytes
|
222
|
+
|
223
|
+
Returns:
|
224
|
+
Optional[int]: Available space in bytes, None if unlimited/unknown
|
225
|
+
"""
|
226
|
+
return None
|
227
|
+
|
228
|
+
def cleanup_expired_uploads(self, before_date: Optional[datetime] = None):
|
229
|
+
"""
|
230
|
+
Clean up expired upload URLs and temporary files
|
231
|
+
|
232
|
+
Args:
|
233
|
+
before_date: Clean up uploads before this date (default: now)
|
234
|
+
"""
|
235
|
+
# Default implementation does nothing - backends can override
|
236
|
+
pass
|
237
|
+
|
238
|
+
def get_file_metadata(self, file_path: str) -> Dict[str, Any]:
|
239
|
+
"""
|
240
|
+
Get metadata for a file
|
241
|
+
|
242
|
+
Args:
|
243
|
+
file_path: Path to the file
|
244
|
+
|
245
|
+
Returns:
|
246
|
+
Dict containing file metadata
|
247
|
+
"""
|
248
|
+
metadata = {}
|
249
|
+
|
250
|
+
if self.exists(file_path):
|
251
|
+
metadata['exists'] = True
|
252
|
+
metadata['size'] = self.get_file_size(file_path)
|
253
|
+
metadata['path'] = file_path
|
254
|
+
else:
|
255
|
+
metadata['exists'] = False
|
256
|
+
|
257
|
+
return metadata
|
258
|
+
|
259
|
+
def copy_file(self, source_path: str, dest_path: str) -> bool:
|
260
|
+
"""
|
261
|
+
Copy a file within the storage backend
|
262
|
+
|
263
|
+
Args:
|
264
|
+
source_path: Source file path
|
265
|
+
dest_path: Destination file path
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
bool: True if copy was successful
|
269
|
+
"""
|
270
|
+
try:
|
271
|
+
with self.open(source_path, 'rb') as source:
|
272
|
+
return self.save(source, dest_path) is not None
|
273
|
+
except Exception:
|
274
|
+
return False
|
275
|
+
|
276
|
+
def move_file(self, source_path: str, dest_path: str) -> bool:
|
277
|
+
"""
|
278
|
+
Move a file within the storage backend
|
279
|
+
|
280
|
+
Args:
|
281
|
+
source_path: Source file path
|
282
|
+
dest_path: Destination file path
|
283
|
+
|
284
|
+
Returns:
|
285
|
+
bool: True if move was successful
|
286
|
+
"""
|
287
|
+
if self.copy_file(source_path, dest_path):
|
288
|
+
return self.delete(source_path)
|
289
|
+
return False
|
290
|
+
|
291
|
+
def list_files(self, path_prefix: str = "", limit: int = 1000) -> List[str]:
|
292
|
+
"""
|
293
|
+
List files with optional path prefix
|
294
|
+
|
295
|
+
Args:
|
296
|
+
path_prefix: Optional path prefix to filter by
|
297
|
+
limit: Maximum number of files to return
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
List[str]: List of file paths
|
301
|
+
"""
|
302
|
+
# Default implementation returns empty list - backends should override
|
303
|
+
return []
|
304
|
+
|
305
|
+
def get_setting(self, key: str, default: Any = None) -> Any:
|
306
|
+
"""
|
307
|
+
Get a setting value from the file manager configuration
|
308
|
+
|
309
|
+
Args:
|
310
|
+
key: Setting key
|
311
|
+
default: Default value if key not found
|
312
|
+
|
313
|
+
Returns:
|
314
|
+
Setting value
|
315
|
+
"""
|
316
|
+
return self.settings.get(key, default)
|
317
|
+
|
318
|
+
def __str__(self):
|
319
|
+
return f"{self.__class__.__name__}({self.backend_url})"
|