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.
Files changed (194) hide show
  1. django_nativemojo-0.1.10.dist-info/LICENSE +19 -0
  2. django_nativemojo-0.1.10.dist-info/METADATA +96 -0
  3. django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
  4. django_nativemojo-0.1.10.dist-info/RECORD +194 -0
  5. django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
  6. mojo/__init__.py +3 -0
  7. mojo/apps/account/__init__.py +1 -0
  8. mojo/apps/account/admin.py +91 -0
  9. mojo/apps/account/apps.py +16 -0
  10. mojo/apps/account/migrations/0001_initial.py +77 -0
  11. mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
  12. mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
  13. mojo/apps/account/migrations/__init__.py +0 -0
  14. mojo/apps/account/models/__init__.py +3 -0
  15. mojo/apps/account/models/group.py +98 -0
  16. mojo/apps/account/models/member.py +95 -0
  17. mojo/apps/account/models/pkey.py +18 -0
  18. mojo/apps/account/models/user.py +211 -0
  19. mojo/apps/account/rest/__init__.py +3 -0
  20. mojo/apps/account/rest/group.py +25 -0
  21. mojo/apps/account/rest/user.py +47 -0
  22. mojo/apps/account/utils/__init__.py +0 -0
  23. mojo/apps/account/utils/jwtoken.py +72 -0
  24. mojo/apps/account/utils/passkeys.py +54 -0
  25. mojo/apps/fileman/README.md +549 -0
  26. mojo/apps/fileman/__init__.py +0 -0
  27. mojo/apps/fileman/apps.py +15 -0
  28. mojo/apps/fileman/backends/__init__.py +117 -0
  29. mojo/apps/fileman/backends/base.py +319 -0
  30. mojo/apps/fileman/backends/filesystem.py +397 -0
  31. mojo/apps/fileman/backends/s3.py +398 -0
  32. mojo/apps/fileman/examples/configurations.py +378 -0
  33. mojo/apps/fileman/examples/usage_example.py +665 -0
  34. mojo/apps/fileman/management/__init__.py +1 -0
  35. mojo/apps/fileman/management/commands/__init__.py +1 -0
  36. mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
  37. mojo/apps/fileman/models/__init__.py +7 -0
  38. mojo/apps/fileman/models/file.py +292 -0
  39. mojo/apps/fileman/models/manager.py +227 -0
  40. mojo/apps/fileman/models/render.py +0 -0
  41. mojo/apps/fileman/rest/__init__ +0 -0
  42. mojo/apps/fileman/rest/__init__.py +23 -0
  43. mojo/apps/fileman/rest/fileman.py +13 -0
  44. mojo/apps/fileman/rest/upload.py +92 -0
  45. mojo/apps/fileman/utils/__init__.py +19 -0
  46. mojo/apps/fileman/utils/upload.py +616 -0
  47. mojo/apps/incident/__init__.py +1 -0
  48. mojo/apps/incident/handlers/__init__.py +3 -0
  49. mojo/apps/incident/handlers/event_handlers.py +142 -0
  50. mojo/apps/incident/migrations/0001_initial.py +83 -0
  51. mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
  52. mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
  53. mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
  54. mojo/apps/incident/migrations/__init__.py +0 -0
  55. mojo/apps/incident/models/__init__.py +3 -0
  56. mojo/apps/incident/models/event.py +135 -0
  57. mojo/apps/incident/models/incident.py +33 -0
  58. mojo/apps/incident/models/rule.py +247 -0
  59. mojo/apps/incident/parsers/__init__.py +0 -0
  60. mojo/apps/incident/parsers/ossec/__init__.py +1 -0
  61. mojo/apps/incident/parsers/ossec/core.py +82 -0
  62. mojo/apps/incident/parsers/ossec/parsed.py +23 -0
  63. mojo/apps/incident/parsers/ossec/rules.py +124 -0
  64. mojo/apps/incident/parsers/ossec/utils.py +169 -0
  65. mojo/apps/incident/reporter.py +42 -0
  66. mojo/apps/incident/rest/__init__.py +2 -0
  67. mojo/apps/incident/rest/event.py +23 -0
  68. mojo/apps/incident/rest/ossec.py +22 -0
  69. mojo/apps/logit/__init__.py +0 -0
  70. mojo/apps/logit/admin.py +37 -0
  71. mojo/apps/logit/migrations/0001_initial.py +32 -0
  72. mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
  73. mojo/apps/logit/migrations/0003_log_level.py +18 -0
  74. mojo/apps/logit/migrations/__init__.py +0 -0
  75. mojo/apps/logit/models/__init__.py +1 -0
  76. mojo/apps/logit/models/log.py +57 -0
  77. mojo/apps/logit/rest.py +9 -0
  78. mojo/apps/metrics/README.md +79 -0
  79. mojo/apps/metrics/__init__.py +12 -0
  80. mojo/apps/metrics/redis_metrics.py +331 -0
  81. mojo/apps/metrics/rest/__init__.py +1 -0
  82. mojo/apps/metrics/rest/base.py +152 -0
  83. mojo/apps/metrics/rest/db.py +0 -0
  84. mojo/apps/metrics/utils.py +227 -0
  85. mojo/apps/notify/README.md +91 -0
  86. mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
  87. mojo/apps/notify/__init__.py +0 -0
  88. mojo/apps/notify/admin.py +52 -0
  89. mojo/apps/notify/handlers/__init__.py +0 -0
  90. mojo/apps/notify/handlers/example_handlers.py +516 -0
  91. mojo/apps/notify/handlers/ses/__init__.py +25 -0
  92. mojo/apps/notify/handlers/ses/bounce.py +0 -0
  93. mojo/apps/notify/handlers/ses/complaint.py +25 -0
  94. mojo/apps/notify/handlers/ses/message.py +86 -0
  95. mojo/apps/notify/management/__init__.py +0 -0
  96. mojo/apps/notify/management/commands/__init__.py +1 -0
  97. mojo/apps/notify/management/commands/process_notifications.py +370 -0
  98. mojo/apps/notify/mod +0 -0
  99. mojo/apps/notify/models/__init__.py +12 -0
  100. mojo/apps/notify/models/account.py +128 -0
  101. mojo/apps/notify/models/attachment.py +24 -0
  102. mojo/apps/notify/models/bounce.py +68 -0
  103. mojo/apps/notify/models/complaint.py +40 -0
  104. mojo/apps/notify/models/inbox.py +113 -0
  105. mojo/apps/notify/models/inbox_message.py +173 -0
  106. mojo/apps/notify/models/outbox.py +129 -0
  107. mojo/apps/notify/models/outbox_message.py +288 -0
  108. mojo/apps/notify/models/template.py +30 -0
  109. mojo/apps/notify/providers/__init__.py +0 -0
  110. mojo/apps/notify/providers/aws.py +73 -0
  111. mojo/apps/notify/rest/__init__.py +0 -0
  112. mojo/apps/notify/rest/ses.py +0 -0
  113. mojo/apps/notify/utils/__init__.py +2 -0
  114. mojo/apps/notify/utils/notifications.py +404 -0
  115. mojo/apps/notify/utils/parsing.py +202 -0
  116. mojo/apps/notify/utils/render.py +144 -0
  117. mojo/apps/tasks/README.md +118 -0
  118. mojo/apps/tasks/__init__.py +11 -0
  119. mojo/apps/tasks/manager.py +489 -0
  120. mojo/apps/tasks/rest/__init__.py +2 -0
  121. mojo/apps/tasks/rest/hooks.py +0 -0
  122. mojo/apps/tasks/rest/tasks.py +62 -0
  123. mojo/apps/tasks/runner.py +174 -0
  124. mojo/apps/tasks/tq_handlers.py +14 -0
  125. mojo/decorators/__init__.py +3 -0
  126. mojo/decorators/auth.py +25 -0
  127. mojo/decorators/cron.py +31 -0
  128. mojo/decorators/http.py +132 -0
  129. mojo/decorators/validate.py +14 -0
  130. mojo/errors.py +88 -0
  131. mojo/helpers/__init__.py +0 -0
  132. mojo/helpers/aws/__init__.py +0 -0
  133. mojo/helpers/aws/client.py +8 -0
  134. mojo/helpers/aws/s3.py +268 -0
  135. mojo/helpers/aws/setup_email.py +0 -0
  136. mojo/helpers/cron.py +79 -0
  137. mojo/helpers/crypto/__init__.py +4 -0
  138. mojo/helpers/crypto/aes.py +60 -0
  139. mojo/helpers/crypto/hash.py +59 -0
  140. mojo/helpers/crypto/privpub/__init__.py +1 -0
  141. mojo/helpers/crypto/privpub/hybrid.py +97 -0
  142. mojo/helpers/crypto/privpub/rsa.py +104 -0
  143. mojo/helpers/crypto/sign.py +36 -0
  144. mojo/helpers/crypto/too.l.py +25 -0
  145. mojo/helpers/crypto/utils.py +26 -0
  146. mojo/helpers/daemon.py +94 -0
  147. mojo/helpers/dates.py +69 -0
  148. mojo/helpers/dns/__init__.py +0 -0
  149. mojo/helpers/dns/godaddy.py +62 -0
  150. mojo/helpers/filetypes.py +128 -0
  151. mojo/helpers/logit.py +310 -0
  152. mojo/helpers/modules.py +95 -0
  153. mojo/helpers/paths.py +63 -0
  154. mojo/helpers/redis.py +10 -0
  155. mojo/helpers/request.py +89 -0
  156. mojo/helpers/request_parser.py +269 -0
  157. mojo/helpers/response.py +14 -0
  158. mojo/helpers/settings.py +146 -0
  159. mojo/helpers/sysinfo.py +140 -0
  160. mojo/helpers/ua.py +0 -0
  161. mojo/middleware/__init__.py +0 -0
  162. mojo/middleware/auth.py +26 -0
  163. mojo/middleware/logging.py +55 -0
  164. mojo/middleware/mojo.py +21 -0
  165. mojo/migrations/0001_initial.py +32 -0
  166. mojo/migrations/__init__.py +0 -0
  167. mojo/models/__init__.py +2 -0
  168. mojo/models/meta.py +262 -0
  169. mojo/models/rest.py +538 -0
  170. mojo/models/secrets.py +59 -0
  171. mojo/rest/__init__.py +1 -0
  172. mojo/rest/info.py +26 -0
  173. mojo/serializers/__init__.py +0 -0
  174. mojo/serializers/models.py +165 -0
  175. mojo/serializers/openapi.py +188 -0
  176. mojo/urls.py +38 -0
  177. mojo/ws4redis/README.md +174 -0
  178. mojo/ws4redis/__init__.py +2 -0
  179. mojo/ws4redis/client.py +283 -0
  180. mojo/ws4redis/connection.py +327 -0
  181. mojo/ws4redis/exceptions.py +32 -0
  182. mojo/ws4redis/redis.py +183 -0
  183. mojo/ws4redis/servers/__init__.py +0 -0
  184. mojo/ws4redis/servers/base.py +86 -0
  185. mojo/ws4redis/servers/django.py +171 -0
  186. mojo/ws4redis/servers/uwsgi.py +63 -0
  187. mojo/ws4redis/settings.py +45 -0
  188. mojo/ws4redis/utf8validator.py +128 -0
  189. mojo/ws4redis/websocket.py +403 -0
  190. testit/__init__.py +0 -0
  191. testit/client.py +147 -0
  192. testit/faker.py +20 -0
  193. testit/helpers.py +198 -0
  194. testit/runner.py +262 -0
@@ -0,0 +1,227 @@
1
+ from django.db import models
2
+ from mojo.models import MojoModel, MojoSecrets
3
+
4
+
5
+ class FileManager(MojoSecrets, MojoModel):
6
+ """
7
+ File manager configuration for different storage backends and upload strategies
8
+ """
9
+
10
+ class RestMeta:
11
+ CAN_SAVE = CAN_CREATE = True
12
+ CAN_DELETE = True
13
+ DEFAULT_SORT = "-id"
14
+ VIEW_PERMS = ["view_fileman"]
15
+ SEARCH_FIELDS = ["name", "backend_type", "description"]
16
+ SEARCH_TERMS = [
17
+ "name", "backend_type", "description",
18
+ ("group", "group__name")]
19
+
20
+ GRAPHS = {
21
+ "default": {
22
+ "graphs": {
23
+ "group": "basic"
24
+ }
25
+ },
26
+ "list": {
27
+ "graphs": {
28
+ "group": "basic"
29
+ }
30
+ }
31
+ }
32
+
33
+ # Storage backend types
34
+ FILE_SYSTEM = 'file'
35
+ AWS_S3 = 's3'
36
+ AZURE_BLOB = 'azure'
37
+ GOOGLE_CLOUD = 'gcs'
38
+ CUSTOM = 'custom'
39
+
40
+ BACKEND_CHOICES = [
41
+ (FILE_SYSTEM, 'File System'),
42
+ (AWS_S3, 'AWS S3'),
43
+ (AZURE_BLOB, 'Azure Blob Storage'),
44
+ (GOOGLE_CLOUD, 'Google Cloud Storage'),
45
+ (CUSTOM, 'Custom Backend'),
46
+ ]
47
+
48
+ created = models.DateTimeField(auto_now_add=True, editable=False, db_index=True)
49
+ modified = models.DateTimeField(auto_now=True)
50
+
51
+ group = models.ForeignKey(
52
+ "account.Group",
53
+ related_name="file_managers",
54
+ null=True,
55
+ blank=True,
56
+ default=None,
57
+ on_delete=models.CASCADE,
58
+ help_text="Group that owns this file manager configuration"
59
+ )
60
+
61
+ name = models.CharField(
62
+ max_length=255,
63
+ db_index=True,
64
+ help_text="Descriptive name for this file manager configuration"
65
+ )
66
+
67
+ description = models.TextField(
68
+ blank=True,
69
+ default="",
70
+ help_text="Optional description of this file manager's purpose"
71
+ )
72
+
73
+ backend_type = models.CharField(
74
+ max_length=32,
75
+ choices=BACKEND_CHOICES,
76
+ db_index=True,
77
+ help_text="Type of storage backend (file, s3, azure, gcs, custom)"
78
+ )
79
+
80
+ backend_url = models.CharField(
81
+ max_length=500,
82
+ help_text="Base URL or connection string for the storage backend"
83
+ )
84
+
85
+ supports_direct_upload = models.BooleanField(
86
+ default=False,
87
+ help_text="Whether this backend supports direct upload (pre-signed URLs)"
88
+ )
89
+
90
+ max_file_size = models.BigIntegerField(
91
+ default=100 * 1024 * 1024, # 100MB default
92
+ help_text="Maximum file size in bytes (0 for unlimited)"
93
+ )
94
+
95
+ allowed_extensions = models.JSONField(
96
+ default=list,
97
+ blank=True,
98
+ help_text="List of allowed file extensions (empty for all)"
99
+ )
100
+
101
+ allowed_mime_types = models.JSONField(
102
+ default=list,
103
+ blank=True,
104
+ help_text="List of allowed MIME types (empty for all)"
105
+ )
106
+
107
+ is_active = models.BooleanField(
108
+ default=True,
109
+ help_text="Whether this file manager is active and can be used"
110
+ )
111
+
112
+ is_default = models.BooleanField(
113
+ default=False,
114
+ help_text="Whether this is the default file manager for the group or user"
115
+ )
116
+
117
+ class Meta:
118
+ unique_together = [
119
+ ['group', 'name'],
120
+ ]
121
+ indexes = [
122
+ models.Index(fields=['backend_type', 'is_active']),
123
+ models.Index(fields=['group', 'is_default']),
124
+ models.Index(fields=['user', 'is_default']),
125
+ models.Index(fields=['group', 'backend_type']),
126
+ ]
127
+
128
+ def __str__(self):
129
+ group_name = self.group.name if self.group else "Global"
130
+ return f"{self.name} ({self.get_backend_type_display()}) - {group_name}"
131
+
132
+ def get_setting(self, key, default=None):
133
+ """Get a specific setting value"""
134
+ return self.get_secret(key, default)
135
+
136
+ def set_setting(self, key, value):
137
+ """Set a specific setting value"""
138
+ self.set_secret(key, value)
139
+
140
+ @property
141
+ def settings(self):
142
+ return self.secrets
143
+
144
+ @property
145
+ def is_file_system(self):
146
+ return self.backend_type == self.FILE_SYSTEM
147
+
148
+ @property
149
+ def is_s3(self):
150
+ return self.backend_type == self.AWS_S3
151
+
152
+ @property
153
+ def is_azure(self):
154
+ return self.backend_type == self.AZURE_BLOB
155
+
156
+ @property
157
+ def is_gcs(self):
158
+ return self.backend_type == self.GOOGLE_CLOUD
159
+
160
+ @property
161
+ def is_custom(self):
162
+ return self.backend_type == self.CUSTOM
163
+
164
+ def can_upload_file(self, filename, file_size=None):
165
+ """Check if a file can be uploaded based on restrictions"""
166
+ if not self.is_active:
167
+ return False, "File manager is not active"
168
+
169
+ # Check file size
170
+ if file_size and self.max_file_size > 0 and file_size > self.max_file_size:
171
+ return False, f"File size exceeds maximum of {self.max_file_size} bytes"
172
+
173
+ # Check file extension
174
+ if self.allowed_extensions:
175
+ import os
176
+ _, ext = os.path.splitext(filename.lower())
177
+ if ext and ext[1:] not in [e.lower() for e in self.allowed_extensions]:
178
+ return False, f"File extension {ext} is not allowed"
179
+
180
+ return True, "File can be uploaded"
181
+
182
+ def can_upload_mime_type(self, mime_type):
183
+ """Check if a MIME type is allowed"""
184
+ if not self.allowed_mime_types:
185
+ return True
186
+ return mime_type.lower() in [mt.lower() for mt in self.allowed_mime_types]
187
+
188
+ def save(self, *args, **kwargs):
189
+ """Custom save to enforce only one default per group"""
190
+ if self.is_default:
191
+ # Set all other file managers in the same group to not default
192
+ FileManager.objects.filter(
193
+ group=self.group,
194
+ is_default=True
195
+ ).exclude(pk=self.pk).update(is_default=False)
196
+
197
+ super().save(*args, **kwargs)
198
+
199
+ @classmethod
200
+ def get_from_request(cls, request):
201
+ """Get the file manager from the request"""
202
+ if request.DATA.fileman:
203
+ return cls.objects.get(pk=request.DATA.fileman)
204
+ if request.DATA.use_groups_fileman:
205
+ return cls.get_for_user_group(group=request.group)
206
+ return cls.get_for_user_group(user=request.user, group=request.group)
207
+
208
+ @classmethod
209
+ def get_for_user_group(cls, user=None, group=None):
210
+ """Get the file manager from the user and/or group"""
211
+ file_manager = None
212
+ if not file_manager and user:
213
+ file_manager = cls.objects.filter(
214
+ user=user, group=group, is_default=True
215
+ ).first()
216
+
217
+ if not file_manager and group:
218
+ file_manager = cls.objects.filter(
219
+ group=group, is_default=True
220
+ ).first()
221
+
222
+ if not file_manager:
223
+ file_manager = cls.objects.filter(
224
+ group=None, user=None, is_default=True
225
+ ).first()
226
+
227
+ return file_manager
File without changes
File without changes
@@ -0,0 +1,23 @@
1
+ """
2
+ File Manager REST API endpoints
3
+ """
4
+
5
+ from .fileman import on_filemanager, on_file
6
+ from .upload import (
7
+ on_upload_initiate,
8
+ on_upload_finalize,
9
+ on_direct_upload,
10
+ on_download
11
+ )
12
+
13
+ __all__ = [
14
+ # File manager model endpoints
15
+ 'on_filemanager',
16
+ 'on_file',
17
+
18
+ # File upload/download endpoints
19
+ 'on_upload_initiate',
20
+ 'on_upload_finalize',
21
+ 'on_direct_upload',
22
+ 'on_download'
23
+ ]
@@ -0,0 +1,13 @@
1
+ from mojo import decorators as md
2
+ from mojo.apps.fileman.models import File, FileManager
3
+
4
+
5
+ @md.URL('manager')
6
+ @md.URL('manager/<int:pk>')
7
+ def on_filemanager(request, pk=None):
8
+ return FileManager.on_rest_request(request, pk)
9
+
10
+ @md.URL('file')
11
+ @md.URL('file/<int:pk>')
12
+ def on_file(request, pk=None):
13
+ return File.on_rest_request(request, pk)
@@ -0,0 +1,92 @@
1
+ from mojo import decorators as md
2
+ from mojo import JsonResponse
3
+ from mojo.apps.fileman.models import File, FileManager
4
+ from mojo.apps.fileman.utils.upload import (
5
+ initiate_upload,
6
+ finalize_upload,
7
+ direct_upload,
8
+ get_download_url
9
+ )
10
+
11
+
12
+ @md.POST('upload/initiate')
13
+ def on_upload_initiate(request):
14
+ """
15
+ Initiate a file upload and get upload URLs
16
+
17
+ Request body format:
18
+ {
19
+ "files": [
20
+ {
21
+ "filename": "document.pdf",
22
+ "content_type": "application/pdf",
23
+ "size": 1024000
24
+ }
25
+ ],
26
+ "file_manager_id": 123, // optional
27
+ "group_id": 456, // optional
28
+ "metadata": { // optional global metadata
29
+ "source": "web_upload",
30
+ "category": "documents"
31
+ }
32
+ }
33
+ """
34
+ response_data = initiate_upload(request, request.DATA)
35
+ status_code = response_data.pop('status_code', 200)
36
+ return JsonResponse(response_data, status=status_code)
37
+
38
+
39
+ @md.POST('upload/finalize')
40
+ def on_upload_finalize(request):
41
+ """
42
+ Finalize a file upload
43
+
44
+ Request body format:
45
+ {
46
+ "upload_token": "abc123...",
47
+ "file_size": 1024000, // optional
48
+ "checksum": "md5:abcdef...", // optional
49
+ "metadata": { // optional additional metadata
50
+ "processing_complete": true
51
+ }
52
+ }
53
+ """
54
+ response_data = finalize_upload(request, request.DATA)
55
+ status_code = response_data.pop('status_code', 200)
56
+ return JsonResponse(response_data, status=status_code)
57
+
58
+
59
+ @md.POST('upload/<str:upload_token>')
60
+ def on_direct_upload(request, upload_token):
61
+ """
62
+ Handle direct file upload for backends that don't support pre-signed URLs
63
+ """
64
+ if not request.FILES or 'file' not in request.FILES:
65
+ return JsonResponse({
66
+ 'success': False,
67
+ 'error': 'No file provided'
68
+ }, status=400)
69
+
70
+ file_data = request.FILES['file']
71
+ response_data = direct_upload(request, upload_token, file_data)
72
+ status_code = response_data.pop('status_code', 200)
73
+ return JsonResponse(response_data, status=status_code)
74
+
75
+
76
+ @md.GET('download/<str:download_token>')
77
+ def on_download(request, download_token):
78
+ """
79
+ Get a download URL for a file
80
+ """
81
+ response_data = get_download_url(request, download_token)
82
+
83
+ # If direct URL is available, redirect to it
84
+ if response_data.get('success') and 'download_url' in response_data:
85
+ return JsonResponse({
86
+ 'success': True,
87
+ 'download_url': response_data['download_url'],
88
+ 'file': response_data.get('file', {})
89
+ })
90
+
91
+ status_code = response_data.pop('status_code', 200)
92
+ return JsonResponse(response_data, status=status_code)
@@ -0,0 +1,19 @@
1
+ # File upload utility functions
2
+
3
+ from .upload import (
4
+ get_file_manager,
5
+ validate_file_request,
6
+ initiate_upload,
7
+ finalize_upload,
8
+ direct_upload,
9
+ get_download_url
10
+ )
11
+
12
+ __all__ = [
13
+ 'get_file_manager',
14
+ 'validate_file_request',
15
+ 'initiate_upload',
16
+ 'finalize_upload',
17
+ 'direct_upload',
18
+ 'get_download_url'
19
+ ]