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,616 @@
|
|
1
|
+
from django.http import JsonResponse, HttpResponseRedirect
|
2
|
+
from django.shortcuts import get_object_or_404
|
3
|
+
from django.conf import settings
|
4
|
+
import json
|
5
|
+
from datetime import datetime, timedelta
|
6
|
+
from typing import List, Dict, Any, Optional, Tuple, Union
|
7
|
+
import hashlib
|
8
|
+
import uuid
|
9
|
+
|
10
|
+
from ..models import FileManager, File
|
11
|
+
from ..backends import get_backend
|
12
|
+
|
13
|
+
|
14
|
+
def get_file_manager(request, file_manager_id=None, group=None):
|
15
|
+
"""
|
16
|
+
Get FileManager instance based on ID, group, or default
|
17
|
+
|
18
|
+
Args:
|
19
|
+
request: The HTTP request
|
20
|
+
file_manager_id: Optional ID of specific file manager
|
21
|
+
group: Optional group to find file manager for
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
FileManager instance
|
25
|
+
|
26
|
+
Raises:
|
27
|
+
ValueError: If no active file manager is available
|
28
|
+
"""
|
29
|
+
if file_manager_id:
|
30
|
+
return get_object_or_404(
|
31
|
+
FileManager.objects.filter(is_active=True),
|
32
|
+
id=file_manager_id
|
33
|
+
)
|
34
|
+
|
35
|
+
# Get user's group or use provided group
|
36
|
+
user_group = group or getattr(request.user, 'group', None)
|
37
|
+
|
38
|
+
# Try to get default file manager for the group
|
39
|
+
if user_group:
|
40
|
+
file_manager = FileManager.objects.filter(
|
41
|
+
group=user_group,
|
42
|
+
is_default=True,
|
43
|
+
is_active=True
|
44
|
+
).first()
|
45
|
+
|
46
|
+
if file_manager:
|
47
|
+
return file_manager
|
48
|
+
|
49
|
+
# Fall back to global default
|
50
|
+
file_manager = FileManager.objects.filter(
|
51
|
+
group__isnull=True,
|
52
|
+
is_default=True,
|
53
|
+
is_active=True
|
54
|
+
).first()
|
55
|
+
|
56
|
+
if not file_manager:
|
57
|
+
# If no default, get any active file manager
|
58
|
+
file_manager = FileManager.objects.filter(is_active=True).first()
|
59
|
+
|
60
|
+
if not file_manager:
|
61
|
+
raise ValueError("No active file manager available")
|
62
|
+
|
63
|
+
return file_manager
|
64
|
+
|
65
|
+
|
66
|
+
def validate_file_request(file_manager, filename, content_type, file_size=None) -> Tuple[bool, str]:
|
67
|
+
"""
|
68
|
+
Validate a file upload request
|
69
|
+
|
70
|
+
Args:
|
71
|
+
file_manager: FileManager instance
|
72
|
+
filename: Name of the file
|
73
|
+
content_type: MIME type of the file
|
74
|
+
file_size: Optional size of the file in bytes
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
Tuple of (is_valid, error_message)
|
78
|
+
"""
|
79
|
+
# Check file manager restrictions
|
80
|
+
can_upload, error_msg = file_manager.can_upload_file(filename, file_size)
|
81
|
+
if not can_upload:
|
82
|
+
return False, error_msg
|
83
|
+
|
84
|
+
# Check MIME type
|
85
|
+
if not file_manager.can_upload_mime_type(content_type):
|
86
|
+
return False, f"MIME type '{content_type}' is not allowed"
|
87
|
+
|
88
|
+
return True, "File upload request is valid"
|
89
|
+
|
90
|
+
|
91
|
+
def initiate_upload(request, data) -> Dict[str, Any]:
|
92
|
+
"""
|
93
|
+
Initiate file upload(s) and return upload URLs and metadata
|
94
|
+
|
95
|
+
Args:
|
96
|
+
request: The HTTP request
|
97
|
+
data: JSON data containing file information
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Dictionary with response data
|
101
|
+
|
102
|
+
Example request data:
|
103
|
+
{
|
104
|
+
"files": [
|
105
|
+
{
|
106
|
+
"filename": "document.pdf",
|
107
|
+
"content_type": "application/pdf",
|
108
|
+
"size": 1024000
|
109
|
+
}
|
110
|
+
],
|
111
|
+
"file_manager_id": 123, // optional
|
112
|
+
"group_id": 456, // optional
|
113
|
+
"metadata": { // optional global metadata
|
114
|
+
"source": "web_upload",
|
115
|
+
"category": "documents"
|
116
|
+
}
|
117
|
+
}
|
118
|
+
"""
|
119
|
+
# Parse request data
|
120
|
+
files_data = data.get('files', [])
|
121
|
+
file_manager_id = data.get('file_manager_id')
|
122
|
+
group_id = data.get('group_id')
|
123
|
+
global_metadata = data.get('metadata', {})
|
124
|
+
|
125
|
+
if not files_data:
|
126
|
+
return {
|
127
|
+
'success': False,
|
128
|
+
'error': 'No files specified',
|
129
|
+
'status_code': 400
|
130
|
+
}
|
131
|
+
|
132
|
+
# Get file manager
|
133
|
+
try:
|
134
|
+
file_manager = get_file_manager(
|
135
|
+
request,
|
136
|
+
file_manager_id=file_manager_id,
|
137
|
+
group=getattr(request.user, 'groups', None).filter(id=group_id).first() if group_id else None
|
138
|
+
)
|
139
|
+
except Exception as e:
|
140
|
+
return {
|
141
|
+
'success': False,
|
142
|
+
'error': f'File manager error: {str(e)}',
|
143
|
+
'status_code': 400
|
144
|
+
}
|
145
|
+
|
146
|
+
# Get storage backend
|
147
|
+
try:
|
148
|
+
backend = get_backend(file_manager)
|
149
|
+
except Exception as e:
|
150
|
+
return {
|
151
|
+
'success': False,
|
152
|
+
'error': f'Storage backend error: {str(e)}',
|
153
|
+
'status_code': 500
|
154
|
+
}
|
155
|
+
|
156
|
+
# Process each file
|
157
|
+
upload_files = []
|
158
|
+
errors = []
|
159
|
+
|
160
|
+
for file_data in files_data:
|
161
|
+
filename = file_data.get('filename')
|
162
|
+
content_type = file_data.get('content_type')
|
163
|
+
file_size = file_data.get('size')
|
164
|
+
file_metadata = file_data.get('metadata', {})
|
165
|
+
|
166
|
+
if not filename or not content_type:
|
167
|
+
errors.append({
|
168
|
+
'filename': filename,
|
169
|
+
'error': 'Filename and content_type are required'
|
170
|
+
})
|
171
|
+
continue
|
172
|
+
|
173
|
+
# Validate file request
|
174
|
+
is_valid, error_msg = validate_file_request(
|
175
|
+
file_manager, filename, content_type, file_size
|
176
|
+
)
|
177
|
+
|
178
|
+
if not is_valid:
|
179
|
+
errors.append({
|
180
|
+
'filename': filename,
|
181
|
+
'error': error_msg
|
182
|
+
})
|
183
|
+
continue
|
184
|
+
|
185
|
+
try:
|
186
|
+
# Create File record
|
187
|
+
file_obj = File(
|
188
|
+
group=file_manager.group,
|
189
|
+
uploaded_by=request.user,
|
190
|
+
file_manager=file_manager,
|
191
|
+
original_filename=filename,
|
192
|
+
content_type=content_type,
|
193
|
+
file_size=file_size,
|
194
|
+
upload_status=File.PENDING
|
195
|
+
)
|
196
|
+
|
197
|
+
# Combine metadata
|
198
|
+
combined_metadata = {**global_metadata, **file_metadata}
|
199
|
+
if combined_metadata:
|
200
|
+
file_obj.metadata = combined_metadata
|
201
|
+
|
202
|
+
# Generate file path
|
203
|
+
file_obj.file_path = backend.generate_file_path(
|
204
|
+
file_obj.generate_unique_filename(),
|
205
|
+
group_id=file_manager.group.id if file_manager.group else None
|
206
|
+
)
|
207
|
+
|
208
|
+
# Save to generate upload token
|
209
|
+
file_obj.save()
|
210
|
+
|
211
|
+
# Generate upload URL if backend supports it
|
212
|
+
upload_info = None
|
213
|
+
if backend.supports_direct_upload():
|
214
|
+
try:
|
215
|
+
upload_info = backend.generate_upload_url(
|
216
|
+
file_obj.file_path,
|
217
|
+
content_type,
|
218
|
+
file_size,
|
219
|
+
expires_in=file_manager.get_setting('upload_expires_in', 3600)
|
220
|
+
)
|
221
|
+
|
222
|
+
# Update file record with upload URL and expiration
|
223
|
+
file_obj.upload_url = upload_info['upload_url']
|
224
|
+
file_obj.upload_expires_at = datetime.now() + timedelta(
|
225
|
+
seconds=file_manager.get_setting('upload_expires_in', 3600)
|
226
|
+
)
|
227
|
+
file_obj.save()
|
228
|
+
|
229
|
+
except Exception as e:
|
230
|
+
errors.append({
|
231
|
+
'filename': filename,
|
232
|
+
'error': f'Failed to generate upload URL: {str(e)}'
|
233
|
+
})
|
234
|
+
continue
|
235
|
+
|
236
|
+
# Prepare response data
|
237
|
+
file_response = {
|
238
|
+
'id': file_obj.id,
|
239
|
+
'filename': file_obj.filename,
|
240
|
+
'original_filename': file_obj.original_filename,
|
241
|
+
'upload_token': file_obj.upload_token,
|
242
|
+
'file_path': file_obj.file_path,
|
243
|
+
'content_type': file_obj.content_type,
|
244
|
+
'upload_status': file_obj.upload_status
|
245
|
+
}
|
246
|
+
|
247
|
+
if upload_info:
|
248
|
+
file_response.update({
|
249
|
+
'upload_url': upload_info['upload_url'],
|
250
|
+
'method': upload_info['method'],
|
251
|
+
'fields': upload_info.get('fields', {}),
|
252
|
+
'headers': upload_info.get('headers', {}),
|
253
|
+
'expires_at': file_obj.upload_expires_at.isoformat() if file_obj.upload_expires_at else None
|
254
|
+
})
|
255
|
+
else:
|
256
|
+
# For backends that don't support direct upload
|
257
|
+
file_response.update({
|
258
|
+
'upload_url': f'/fileman/upload/{file_obj.upload_token}/',
|
259
|
+
'method': 'POST',
|
260
|
+
'fields': {},
|
261
|
+
'headers': {}
|
262
|
+
})
|
263
|
+
|
264
|
+
upload_files.append(file_response)
|
265
|
+
|
266
|
+
except Exception as e:
|
267
|
+
errors.append({
|
268
|
+
'filename': filename,
|
269
|
+
'error': f'Failed to initiate upload: {str(e)}'
|
270
|
+
})
|
271
|
+
|
272
|
+
# Prepare response
|
273
|
+
response_data = {
|
274
|
+
'success': len(upload_files) > 0,
|
275
|
+
'files': upload_files,
|
276
|
+
'file_manager': {
|
277
|
+
'id': file_manager.id,
|
278
|
+
'name': file_manager.name,
|
279
|
+
'backend_type': file_manager.backend_type,
|
280
|
+
'supports_direct_upload': file_manager.supports_direct_upload
|
281
|
+
}
|
282
|
+
}
|
283
|
+
|
284
|
+
if errors:
|
285
|
+
response_data['errors'] = errors
|
286
|
+
|
287
|
+
status_code = 200 if upload_files else 400
|
288
|
+
response_data['status_code'] = status_code
|
289
|
+
|
290
|
+
return response_data
|
291
|
+
|
292
|
+
|
293
|
+
def finalize_upload(request, data) -> Dict[str, Any]:
|
294
|
+
"""
|
295
|
+
Finalize file upload and confirm successful upload
|
296
|
+
|
297
|
+
Args:
|
298
|
+
request: The HTTP request
|
299
|
+
data: JSON data containing upload information
|
300
|
+
|
301
|
+
Returns:
|
302
|
+
Dictionary with response data
|
303
|
+
|
304
|
+
Example request data:
|
305
|
+
{
|
306
|
+
"upload_token": "abc123...",
|
307
|
+
"file_size": 1024000, // optional
|
308
|
+
"checksum": "md5:abcdef...", // optional
|
309
|
+
"metadata": { // optional additional metadata
|
310
|
+
"processing_complete": true
|
311
|
+
}
|
312
|
+
}
|
313
|
+
"""
|
314
|
+
# Parse request data
|
315
|
+
upload_token = data.get('upload_token')
|
316
|
+
reported_size = data.get('file_size')
|
317
|
+
reported_checksum = data.get('checksum')
|
318
|
+
additional_metadata = data.get('metadata', {})
|
319
|
+
|
320
|
+
if not upload_token:
|
321
|
+
return {
|
322
|
+
'success': False,
|
323
|
+
'error': 'Upload token is required',
|
324
|
+
'status_code': 400
|
325
|
+
}
|
326
|
+
|
327
|
+
# Get file record
|
328
|
+
try:
|
329
|
+
file_obj = File.objects.get(
|
330
|
+
upload_token=upload_token,
|
331
|
+
is_active=True
|
332
|
+
)
|
333
|
+
except File.DoesNotExist:
|
334
|
+
return {
|
335
|
+
'success': False,
|
336
|
+
'error': 'Invalid upload token',
|
337
|
+
'status_code': 404
|
338
|
+
}
|
339
|
+
|
340
|
+
# Check if upload has expired
|
341
|
+
if file_obj.is_upload_expired:
|
342
|
+
file_obj.mark_as_expired()
|
343
|
+
return {
|
344
|
+
'success': False,
|
345
|
+
'error': 'Upload has expired',
|
346
|
+
'status_code': 410
|
347
|
+
}
|
348
|
+
|
349
|
+
# Check permissions
|
350
|
+
if file_obj.uploaded_by != request.user:
|
351
|
+
return {
|
352
|
+
'success': False,
|
353
|
+
'error': 'Permission denied',
|
354
|
+
'status_code': 403
|
355
|
+
}
|
356
|
+
|
357
|
+
# Get storage backend
|
358
|
+
try:
|
359
|
+
backend = get_backend(file_obj.file_manager)
|
360
|
+
except Exception as e:
|
361
|
+
return {
|
362
|
+
'success': False,
|
363
|
+
'error': f'Storage backend error: {str(e)}',
|
364
|
+
'status_code': 500
|
365
|
+
}
|
366
|
+
|
367
|
+
try:
|
368
|
+
# Mark as uploading
|
369
|
+
file_obj.mark_as_uploading()
|
370
|
+
|
371
|
+
# Validate upload
|
372
|
+
is_valid, error_msg = backend.validate_upload(
|
373
|
+
file_obj.file_path,
|
374
|
+
upload_token,
|
375
|
+
expected_size=reported_size,
|
376
|
+
expected_checksum=reported_checksum
|
377
|
+
)
|
378
|
+
|
379
|
+
if not is_valid:
|
380
|
+
file_obj.mark_as_failed(error_msg)
|
381
|
+
return {
|
382
|
+
'success': False,
|
383
|
+
'error': f'Upload validation failed: {error_msg}',
|
384
|
+
'status_code': 400
|
385
|
+
}
|
386
|
+
|
387
|
+
# Get actual file metadata from backend
|
388
|
+
file_metadata = backend.get_file_metadata(file_obj.file_path)
|
389
|
+
actual_size = file_metadata.get('size')
|
390
|
+
|
391
|
+
# Update file record
|
392
|
+
file_obj.file_size = actual_size or reported_size
|
393
|
+
|
394
|
+
# Calculate checksum if not provided
|
395
|
+
if not reported_checksum and backend.exists(file_obj.file_path):
|
396
|
+
try:
|
397
|
+
calculated_checksum = backend.get_file_checksum(file_obj.file_path)
|
398
|
+
if calculated_checksum:
|
399
|
+
file_obj.checksum = f"md5:{calculated_checksum}"
|
400
|
+
except Exception:
|
401
|
+
pass # Checksum calculation is optional
|
402
|
+
else:
|
403
|
+
file_obj.checksum = reported_checksum or ""
|
404
|
+
|
405
|
+
# Add additional metadata
|
406
|
+
if additional_metadata:
|
407
|
+
file_obj.metadata.update(additional_metadata)
|
408
|
+
|
409
|
+
# Mark as completed
|
410
|
+
file_obj.mark_as_completed()
|
411
|
+
|
412
|
+
# Generate download URL
|
413
|
+
try:
|
414
|
+
download_url = backend.get_url(file_obj.file_path)
|
415
|
+
except Exception:
|
416
|
+
download_url = f'/fileman/download/{upload_token}/'
|
417
|
+
|
418
|
+
# Prepare response
|
419
|
+
response_data = {
|
420
|
+
'success': True,
|
421
|
+
'file': {
|
422
|
+
'id': file_obj.id,
|
423
|
+
'filename': file_obj.filename,
|
424
|
+
'original_filename': file_obj.original_filename,
|
425
|
+
'file_path': file_obj.file_path,
|
426
|
+
'file_size': file_obj.file_size,
|
427
|
+
'content_type': file_obj.content_type,
|
428
|
+
'upload_status': file_obj.upload_status,
|
429
|
+
'upload_token': file_obj.upload_token,
|
430
|
+
'checksum': file_obj.checksum,
|
431
|
+
'download_url': download_url,
|
432
|
+
'metadata': file_obj.metadata,
|
433
|
+
'created': file_obj.created.isoformat(),
|
434
|
+
'modified': file_obj.modified.isoformat()
|
435
|
+
},
|
436
|
+
'status_code': 200
|
437
|
+
}
|
438
|
+
|
439
|
+
return response_data
|
440
|
+
|
441
|
+
except Exception as e:
|
442
|
+
file_obj.mark_as_failed(str(e))
|
443
|
+
return {
|
444
|
+
'success': False,
|
445
|
+
'error': f'Failed to finalize upload: {str(e)}',
|
446
|
+
'status_code': 500
|
447
|
+
}
|
448
|
+
|
449
|
+
|
450
|
+
def direct_upload(request, upload_token, file_data) -> Dict[str, Any]:
|
451
|
+
"""
|
452
|
+
Handle direct file uploads for backends that don't support pre-signed URLs
|
453
|
+
|
454
|
+
Args:
|
455
|
+
request: The HTTP request
|
456
|
+
upload_token: The upload token
|
457
|
+
file_data: The uploaded file data
|
458
|
+
|
459
|
+
Returns:
|
460
|
+
Dictionary with response data
|
461
|
+
"""
|
462
|
+
# Get file record
|
463
|
+
try:
|
464
|
+
file_obj = File.objects.get(
|
465
|
+
upload_token=upload_token,
|
466
|
+
is_active=True
|
467
|
+
)
|
468
|
+
except File.DoesNotExist:
|
469
|
+
return {
|
470
|
+
'success': False,
|
471
|
+
'error': 'Invalid upload token',
|
472
|
+
'status_code': 404
|
473
|
+
}
|
474
|
+
|
475
|
+
# Check if upload has expired
|
476
|
+
if file_obj.is_upload_expired:
|
477
|
+
file_obj.mark_as_expired()
|
478
|
+
return {
|
479
|
+
'success': False,
|
480
|
+
'error': 'Upload has expired',
|
481
|
+
'status_code': 410
|
482
|
+
}
|
483
|
+
|
484
|
+
# Get uploaded file
|
485
|
+
uploaded_file = file_data
|
486
|
+
if not uploaded_file:
|
487
|
+
return {
|
488
|
+
'success': False,
|
489
|
+
'error': 'No file uploaded',
|
490
|
+
'status_code': 400
|
491
|
+
}
|
492
|
+
|
493
|
+
# Get storage backend
|
494
|
+
try:
|
495
|
+
backend = get_backend(file_obj.file_manager)
|
496
|
+
except Exception as e:
|
497
|
+
return {
|
498
|
+
'success': False,
|
499
|
+
'error': f'Storage backend error: {str(e)}',
|
500
|
+
'status_code': 500
|
501
|
+
}
|
502
|
+
|
503
|
+
try:
|
504
|
+
# Mark as uploading
|
505
|
+
file_obj.mark_as_uploading()
|
506
|
+
|
507
|
+
# Save file using backend
|
508
|
+
file_path = backend.save(
|
509
|
+
uploaded_file,
|
510
|
+
file_obj.filename,
|
511
|
+
content_type=file_obj.content_type,
|
512
|
+
group_id=file_obj.group.id if file_obj.group else None,
|
513
|
+
metadata=file_obj.metadata
|
514
|
+
)
|
515
|
+
|
516
|
+
# Update file record with actual path
|
517
|
+
file_obj.file_path = file_path
|
518
|
+
file_obj.file_size = uploaded_file.size
|
519
|
+
|
520
|
+
# Calculate checksum
|
521
|
+
try:
|
522
|
+
uploaded_file.seek(0) # Reset file pointer
|
523
|
+
md5_hash = hashlib.md5()
|
524
|
+
for chunk in uploaded_file.chunks():
|
525
|
+
md5_hash.update(chunk)
|
526
|
+
file_obj.checksum = f"md5:{md5_hash.hexdigest()}"
|
527
|
+
except Exception:
|
528
|
+
pass # Checksum calculation is optional
|
529
|
+
|
530
|
+
# Mark as completed
|
531
|
+
file_obj.mark_as_completed()
|
532
|
+
|
533
|
+
return {
|
534
|
+
'success': True,
|
535
|
+
'message': 'File uploaded successfully',
|
536
|
+
'upload_token': upload_token,
|
537
|
+
'status_code': 200
|
538
|
+
}
|
539
|
+
|
540
|
+
except Exception as e:
|
541
|
+
file_obj.mark_as_failed(str(e))
|
542
|
+
return {
|
543
|
+
'success': False,
|
544
|
+
'error': f'Failed to upload file: {str(e)}',
|
545
|
+
'status_code': 500
|
546
|
+
}
|
547
|
+
|
548
|
+
|
549
|
+
def get_download_url(request, upload_token) -> Dict[str, Any]:
|
550
|
+
"""
|
551
|
+
Generate download URL for a file
|
552
|
+
|
553
|
+
Args:
|
554
|
+
request: The HTTP request
|
555
|
+
upload_token: The upload token
|
556
|
+
|
557
|
+
Returns:
|
558
|
+
Dictionary with download URL or error information
|
559
|
+
"""
|
560
|
+
# Get file record
|
561
|
+
try:
|
562
|
+
file_obj = File.objects.get(
|
563
|
+
upload_token=upload_token,
|
564
|
+
is_active=True,
|
565
|
+
upload_status=File.COMPLETED
|
566
|
+
)
|
567
|
+
except File.DoesNotExist:
|
568
|
+
return {
|
569
|
+
'success': False,
|
570
|
+
'error': 'File not found',
|
571
|
+
'status_code': 404
|
572
|
+
}
|
573
|
+
|
574
|
+
# Check permissions
|
575
|
+
if not file_obj.can_be_accessed_by(request.user, getattr(request.user, 'group', None)):
|
576
|
+
return {
|
577
|
+
'success': False,
|
578
|
+
'error': 'Permission denied',
|
579
|
+
'status_code': 403
|
580
|
+
}
|
581
|
+
|
582
|
+
# Get storage backend
|
583
|
+
try:
|
584
|
+
backend = get_backend(file_obj.file_manager)
|
585
|
+
except Exception as e:
|
586
|
+
return {
|
587
|
+
'success': False,
|
588
|
+
'error': f'Storage backend error: {str(e)}',
|
589
|
+
'status_code': 500
|
590
|
+
}
|
591
|
+
|
592
|
+
# Generate download URL
|
593
|
+
try:
|
594
|
+
download_url = backend.get_url(
|
595
|
+
file_obj.file_path,
|
596
|
+
expires_in=3600 # 1 hour
|
597
|
+
)
|
598
|
+
|
599
|
+
return {
|
600
|
+
'success': True,
|
601
|
+
'download_url': download_url,
|
602
|
+
'file': {
|
603
|
+
'id': file_obj.id,
|
604
|
+
'filename': file_obj.filename,
|
605
|
+
'original_filename': file_obj.original_filename,
|
606
|
+
'content_type': file_obj.content_type
|
607
|
+
},
|
608
|
+
'status_code': 200
|
609
|
+
}
|
610
|
+
|
611
|
+
except Exception as e:
|
612
|
+
return {
|
613
|
+
'success': False,
|
614
|
+
'error': f'Failed to generate download URL: {str(e)}',
|
615
|
+
'status_code': 500
|
616
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
from .reporter import report_event
|