Compare commits

..

12 Commits

Author SHA1 Message Date
c59f14f7d3 remove crap 2025-12-11 06:38:03 -05:00
biondizzle
1880548e16 Merge pull request #9 from biondizzle/codex/refactor-route-handling-to-use-config/routes
Refactor API controllers to use explicit routes
2025-06-05 21:07:12 -04:00
biondizzle
a6d0143d19 refactor routes 2025-06-05 21:06:46 -04:00
biondizzle
9190a159b5 Merge pull request #8 from biondizzle/codex/build-file-uploader-for-management-console
Add admin object uploader
2025-06-05 19:55:06 -04:00
biondizzle
9684406295 Add object upload feature to console 2025-06-05 19:54:51 -04:00
biondizzle
fab814e179 Merge pull request #7 from biondizzle/codex/build-presigned-url-upload-api
Add presigned upload support
2025-06-05 19:44:22 -04:00
biondizzle
d625879dd2 Merge branch 'master' into codex/build-presigned-url-upload-api 2025-06-05 19:44:02 -04:00
biondizzle
ad69872828 Update ConsoleApiController.php 2025-06-05 19:42:31 -04:00
biondizzle
96716653a2 Update ConsoleApiController.php 2025-06-05 19:41:32 -04:00
biondizzle
a6bee5be15 Add endpoint for presigned uploads 2025-06-05 19:39:58 -04:00
biondizzle
fdaa7232a1 Merge pull request #6 from biondizzle/codex/fix-authentication-check-logic
Refactor auth checks
2025-06-05 19:39:25 -04:00
biondizzle
409c9950de Refactor auth checks to avoid assignments in conditions 2025-06-05 19:39:09 -04:00
8 changed files with 677 additions and 327 deletions

2
.env
View File

@@ -27,7 +27,7 @@ APP_SECRET=6aa2ea989e29de27bc42a77db9849b87
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
DATABASE_URL="mysql://vultradmin:AVNS_jn444_0nHCHAvnZkTFN@vultr-prod-a6de266e-e9c6-477c-abf3-7ec2e7a7bfc8-vultr-prod-3195.vultrdb.com:18140/defaultdb?serverVersion=8.0.32&charset=utf8mb4"
DATABASE_URL=""
###< doctrine/doctrine-bundle ###
# Console login credentials

View File

@@ -1,39 +1,100 @@
# Console API Routes
console_api_credentials:
console_api_credentials_list:
path: /api/credentials
controller: App\Controller\ConsoleApiController::credentials
methods: [GET, POST]
controller: App\Controller\ConsoleApiController::listCredentials
methods: [GET]
console_api_credential_detail:
console_api_credentials_create:
path: /api/credentials
controller: App\Controller\ConsoleApiController::createCredential
methods: [POST]
console_api_credential_get:
path: /api/credentials/{id}
controller: App\Controller\ConsoleApiController::credentialDetail
methods: [GET, PUT, DELETE]
controller: App\Controller\ConsoleApiController::getCredential
methods: [GET]
requirements:
id: '\d+'
console_api_buckets:
path: /api/buckets
controller: App\Controller\ConsoleApiController::buckets
methods: [GET, POST]
console_api_credential_update:
path: /api/credentials/{id}
controller: App\Controller\ConsoleApiController::updateCredential
methods: [PUT]
requirements:
id: '\d+'
console_api_bucket_detail:
console_api_credential_delete:
path: /api/credentials/{id}
controller: App\Controller\ConsoleApiController::deleteCredential
methods: [DELETE]
requirements:
id: '\d+'
console_api_buckets_list:
path: /api/buckets
controller: App\Controller\ConsoleApiController::listBuckets
methods: [GET]
console_api_buckets_create:
path: /api/buckets
controller: App\Controller\ConsoleApiController::createBucket
methods: [POST]
console_api_bucket_get:
path: /api/buckets/{name}
controller: App\Controller\ConsoleApiController::bucketDetail
methods: [GET, DELETE]
controller: App\Controller\ConsoleApiController::getBucket
methods: [GET]
requirements:
name: '[a-z0-9\-\.]+'
console_api_objects:
console_api_bucket_delete:
path: /api/buckets/{name}
controller: App\Controller\ConsoleApiController::deleteBucket
methods: [DELETE]
requirements:
name: '[a-z0-9\-\.]+'
console_api_objects_list:
path: /api/buckets/{bucketName}/objects
controller: App\Controller\ConsoleApiController::objects
methods: [GET, POST, DELETE]
controller: App\Controller\ConsoleApiController::listObjects
methods: [GET]
requirements:
bucketName: '[a-z0-9\-\.]+'
console_api_object_detail:
console_api_create_object:
path: /api/buckets/{bucketName}/objects
controller: App\Controller\ConsoleApiController::createObject
methods: [POST]
requirements:
bucketName: '[a-z0-9\-\.]+'
console_api_delete_objects:
path: /api/buckets/{bucketName}/objects
controller: App\Controller\ConsoleApiController::deleteObjects
methods: [DELETE]
requirements:
bucketName: '[a-z0-9\-\.]+'
console_api_object_get:
path: /api/buckets/{bucketName}/objects/{objectKey}
controller: App\Controller\ConsoleApiController::objectDetail
methods: [GET, DELETE]
controller: App\Controller\ConsoleApiController::getObject
methods: [GET]
requirements:
bucketName: '[a-z0-9\-\.]+'
objectKey: '.+'
console_api_object_delete:
path: /api/buckets/{bucketName}/objects/{objectKey}
controller: App\Controller\ConsoleApiController::deleteObject
methods: [DELETE]
requirements:
bucketName: '[a-z0-9\-\.]+'
objectKey: '.+'
console_api_presigned_upload:
path: /api/buckets/{bucketName}/objects/{objectKey}/presigned-upload
controller: App\Controller\ConsoleApiController::createPresignedUpload
methods: [POST]
requirements:
bucketName: '[a-z0-9\-\.]+'
objectKey: '.+'
@@ -45,10 +106,15 @@ console_api_multipart_uploads:
requirements:
bucketName: '[a-z0-9\-\.]+'
console_api_presigned_urls:
console_api_presigned_urls_list:
path: /api/presigned-urls
controller: App\Controller\ConsoleApiController::presignedUrls
methods: [GET, POST]
controller: App\Controller\ConsoleApiController::listPresignedUrls
methods: [GET]
console_api_presigned_urls_create:
path: /api/presigned-urls
controller: App\Controller\ConsoleApiController::createPresignedUrl
methods: [POST]
console_api_stats:
path: /api/stats
@@ -56,10 +122,15 @@ console_api_stats:
methods: [GET]
# Console Authentication Routes
console_login:
console_login_form:
path: /console/login
controller: App\Controller\ConsoleController::login
methods: [GET, POST]
controller: App\Controller\ConsoleController::loginForm
methods: [GET]
console_login_submit:
path: /console/login
controller: App\Controller\ConsoleController::loginSubmit
methods: [POST]
console_logout:
path: /console/logout

View File

@@ -28,12 +28,13 @@ class ConsoleApiController extends AbstractController
}
// Credentials Management
public function credentials(Request $request): JsonResponse
public function listCredentials(Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
$resp = $this->checkAuth($request);
if ($resp !== null) {
return $resp;
}
if ($request->getMethod() === 'GET') {
$credentials = $this->entityManager->getRepository(S3Credential::class)->findAll();
return new JsonResponse([
@@ -50,7 +51,13 @@ class ConsoleApiController extends AbstractController
]);
}
if ($request->getMethod() === 'POST') {
public function createCredential(Request $request): JsonResponse
{
$resp = $this->checkAuth($request);
if ($resp !== null) {
return $resp;
}
$data = json_decode($request->getContent(), true);
$accessKey = $data['access_key'] ?? 'AKIA' . strtoupper(bin2hex(random_bytes(10)));
@@ -69,13 +76,11 @@ class ConsoleApiController extends AbstractController
], 201);
}
return new JsonResponse(['error' => 'Method not allowed'], 405);
}
public function credentialDetail(int $id, Request $request): JsonResponse
public function getCredential(int $id, Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
return $resp;
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$credential = $this->entityManager->getRepository(S3Credential::class)->find($id);
@@ -83,7 +88,6 @@ class ConsoleApiController extends AbstractController
return new JsonResponse(['error' => 'Credential not found'], 404);
}
if ($request->getMethod() === 'GET') {
return new JsonResponse([
'id' => $credential->getId(),
'access_key' => $credential->getAccessKey(),
@@ -101,7 +105,18 @@ class ConsoleApiController extends AbstractController
]);
}
if ($request->getMethod() === 'PUT') {
public function updateCredential(int $id, Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$credential = $this->entityManager->getRepository(S3Credential::class)->find($id);
if (!$credential) {
return new JsonResponse(['error' => 'Credential not found'], 404);
}
$data = json_decode($request->getContent(), true);
if (isset($data['user_name'])) {
@@ -116,23 +131,32 @@ class ConsoleApiController extends AbstractController
return new JsonResponse(['message' => 'Credential updated']);
}
if ($request->getMethod() === 'DELETE') {
public function deleteCredential(int $id, Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$credential = $this->entityManager->getRepository(S3Credential::class)->find($id);
if (!$credential) {
return new JsonResponse(['error' => 'Credential not found'], 404);
}
$this->entityManager->remove($credential);
$this->entityManager->flush();
return new JsonResponse(['message' => 'Credential deleted']);
}
return new JsonResponse(['error' => 'Method not allowed'], 405);
// Buckets Management
public function listBuckets(Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
// Buckets Management
public function buckets(Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
return $resp;
}
if ($request->getMethod() === 'GET') {
$buckets = $this->entityManager->getRepository(S3Bucket::class)->findAll();
return new JsonResponse([
@@ -159,7 +183,13 @@ class ConsoleApiController extends AbstractController
]);
}
if ($request->getMethod() === 'POST') {
public function createBucket(Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$data = json_decode($request->getContent(), true);
$bucketName = $data['name'] ?? null;
@@ -189,13 +219,11 @@ class ConsoleApiController extends AbstractController
}
}
return new JsonResponse(['error' => 'Method not allowed'], 405);
}
public function bucketDetail(string $name, Request $request): JsonResponse
public function getBucket(string $name, Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
return $resp;
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$bucket = $this->s3Service->findBucketByName($name);
@@ -203,7 +231,6 @@ class ConsoleApiController extends AbstractController
return new JsonResponse(['error' => 'Bucket not found'], 404);
}
if ($request->getMethod() === 'GET') {
$objects = $this->s3Service->listObjects($bucket);
$totalSize = array_sum(array_map(fn($obj) => $obj->getSize(), $objects));
@@ -229,7 +256,18 @@ class ConsoleApiController extends AbstractController
]);
}
if ($request->getMethod() === 'DELETE') {
public function deleteBucket(string $name, Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$bucket = $this->s3Service->findBucketByName($name);
if (!$bucket) {
return new JsonResponse(['error' => 'Bucket not found'], 404);
}
try {
$this->s3Service->deleteBucket($bucket);
return new JsonResponse(['message' => 'Bucket deleted']);
@@ -238,14 +276,12 @@ class ConsoleApiController extends AbstractController
}
}
return new JsonResponse(['error' => 'Method not allowed'], 405);
}
// Objects Management
public function objects(string $bucketName, Request $request): JsonResponse
public function listObjects(string $bucketName, Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
return $resp;
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$bucket = $this->s3Service->findBucketByName($bucketName);
@@ -253,7 +289,6 @@ class ConsoleApiController extends AbstractController
return new JsonResponse(['error' => 'Bucket not found'], 404);
}
if ($request->getMethod() === 'GET') {
$prefix = $request->query->get('prefix', '');
$objects = $this->s3Service->listObjects($bucket, $prefix);
@@ -272,7 +307,57 @@ class ConsoleApiController extends AbstractController
]);
}
if ($request->getMethod() === 'DELETE') {
public function createObject(string $bucketName, Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$bucket = $this->s3Service->findBucketByName($bucketName);
if (!$bucket) {
return new JsonResponse(['error' => 'Bucket not found'], 404);
}
$objectKey = $request->headers->get('X-Object-Key')
?? $request->query->get('key')
?? $request->request->get('object_key');
if (empty($objectKey)) {
return new JsonResponse(['error' => 'Missing object key'], 400);
}
$contentType = $request->headers->get('Content-Type', 'application/octet-stream');
$file = $request->files->get('file');
if ($file instanceof \Symfony\Component\HttpFoundation\File\UploadedFile) {
$contentType = $file->getMimeType() ?: $contentType;
$content = file_get_contents($file->getPathname());
} else {
$content = $request->getContent();
}
$object = $this->s3Service->putObject($bucket, $objectKey, $content, $contentType);
return new JsonResponse([
'key' => $object->getObjectKey(),
'etag' => $object->getEtag(),
'size' => $object->getSize(),
'content_type' => $object->getContentType()
], 201);
}
public function deleteObjects(string $bucketName, Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$bucket = $this->s3Service->findBucketByName($bucketName);
if (!$bucket) {
return new JsonResponse(['error' => 'Bucket not found'], 404);
}
$data = json_decode($request->getContent(), true);
$keys = $data['keys'] ?? [];
@@ -288,13 +373,11 @@ class ConsoleApiController extends AbstractController
return new JsonResponse(['deleted' => $deleted]);
}
return new JsonResponse(['error' => 'Method not allowed'], 405);
}
public function objectDetail(string $bucketName, string $objectKey, Request $request): JsonResponse
public function getObject(string $bucketName, string $objectKey, Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
return $resp;
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$bucket = $this->s3Service->findBucketByName($bucketName);
@@ -308,7 +391,6 @@ class ConsoleApiController extends AbstractController
return new JsonResponse(['error' => 'Object not found'], 404);
}
if ($request->getMethod() === 'GET') {
return new JsonResponse([
'key' => $object->getObjectKey(),
'size' => $object->getSize(),
@@ -324,19 +406,68 @@ class ConsoleApiController extends AbstractController
]);
}
if ($request->getMethod() === 'DELETE') {
public function deleteObject(string $bucketName, string $objectKey, Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$bucket = $this->s3Service->findBucketByName($bucketName);
if (!$bucket) {
return new JsonResponse(['error' => 'Bucket not found'], 404);
}
$object = $this->s3Service->findObject($bucket, $objectKey);
if (!$object) {
return new JsonResponse(['error' => 'Object not found'], 404);
}
$this->s3Service->deleteObject($object);
return new JsonResponse(['message' => 'Object deleted']);
}
return new JsonResponse(['error' => 'Method not allowed'], 405);
public function createPresignedUpload(string $bucketName, string $objectKey, Request $request): JsonResponse
{
$resp = $this->checkAuth($request);
if (!empty($resp)) {
return $resp;
}
$bucket = $this->s3Service->findBucketByName($bucketName);
if (!$bucket) {
return new JsonResponse(['error' => 'Bucket not found'], 404);
}
$data = json_decode($request->getContent(), true);
$accessKey = $data['access_key'] ?? null;
$expiresIn = $data['expires_in'] ?? 3600;
$contentType = $data['content_type'] ?? 'application/octet-stream';
if (!$accessKey) {
return new JsonResponse(['error' => 'Missing access key'], 400);
}
$credential = $this->s3Service->findCredentialByAccessKey($accessKey);
if (!$credential) {
return new JsonResponse(['error' => 'Invalid access key'], 404);
}
// Create placeholder object
$this->s3Service->putObject($bucket, $objectKey, '', $contentType);
$url = $this->s3Service->generatePresignedUrl($bucketName, $objectKey, $credential, 'PUT', $expiresIn);
return new JsonResponse(['url' => $url], 201);
}
// Multipart Uploads
public function multipartUploads(string $bucketName, Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
return $resp;
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$bucket = $this->s3Service->findBucketByName($bucketName);
@@ -366,12 +497,13 @@ class ConsoleApiController extends AbstractController
}
// Presigned URLs
public function presignedUrls(Request $request): JsonResponse
public function listPresignedUrls(Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
return $resp;
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
if ($request->getMethod() === 'GET') {
$urls = $this->entityManager->getRepository(\App\Entity\S3PresignedUrl::class)
->createQueryBuilder('p')
->where('p.expiresAt > :now')
@@ -396,7 +528,13 @@ class ConsoleApiController extends AbstractController
]);
}
if ($request->getMethod() === 'POST') {
public function createPresignedUrl(Request $request): JsonResponse
{
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$data = json_decode($request->getContent(), true);
$bucketName = $data['bucket_name'] ?? null;
@@ -419,14 +557,12 @@ class ConsoleApiController extends AbstractController
return new JsonResponse(['url' => $url], 201);
}
return new JsonResponse(['error' => 'Method not allowed'], 405);
}
// Statistics
public function stats(Request $request): JsonResponse
{
if ($resp = $this->checkAuth($request)) {
return $resp;
$authResp = $this->checkAuth($request);
if ($authResp !== null) {
return $authResp;
}
$credentialCount = $this->entityManager->getRepository(S3Credential::class)->count([]);
$bucketCount = $this->entityManager->getRepository(S3Bucket::class)->count([]);

View File

@@ -18,14 +18,21 @@ class ConsoleController extends AbstractController
return $this->render('console/index.html.twig');
}
public function login(Request $request): Response
public function loginForm(Request $request): Response
{
if ($request->getSession()->get('console_logged_in')) {
return new RedirectResponse('/console');
}
return $this->render('console/login.html.twig', ['error' => null]);
}
public function loginSubmit(Request $request): Response
{
if ($request->getSession()->get('console_logged_in')) {
return new RedirectResponse('/console');
}
$error = null;
if ($request->isMethod('POST')) {
$user = $request->request->get('username');
$pass = $request->request->get('password');
$envUser = $_ENV['CONSOLE_USER'] ?? 'admin';
@@ -36,10 +43,7 @@ class ConsoleController extends AbstractController
return new RedirectResponse('/console');
}
$error = 'Invalid credentials';
}
return $this->render('console/login.html.twig', ['error' => $error]);
return $this->render('console/login.html.twig', ['error' => 'Invalid credentials']);
}
public function logout(Request $request): Response

View File

@@ -529,9 +529,12 @@
<div x-show="selectedBucket" class="card">
<div class="card-header">
Objects
<div>
<div style="display: flex; gap: 0.5rem;">
<input x-model="objectSearch" @input="filterObjects()"
placeholder="Search objects..." class="search-input">
<button @click="showUploadModal = true" class="btn btn-primary btn-sm">
<i class="fas fa-upload"></i> Upload
</button>
</div>
</div>
<div class="card-body">
@@ -630,6 +633,27 @@
</div>
</div>
<!-- Upload Object Modal -->
<div x-show="showUploadModal" x-cloak class="modal" @click.self="showUploadModal = false">
<div class="modal-content">
<h3 style="margin-bottom: 1rem;">Upload Object</h3>
<form @submit.prevent="uploadObject()">
<div class="form-group">
<label class="form-label">Object Key</label>
<input x-model="uploadKey" type="text" class="form-input" placeholder="path/to/file.txt" required>
</div>
<div class="form-group">
<label class="form-label">Select File</label>
<input type="file" @change="uploadFile = $event.target.files[0]" class="form-input" required>
</div>
<div style="display: flex; gap: 1rem; justify-content: flex-end;">
<button type="button" @click="showUploadModal = false" class="btn btn-secondary">Cancel</button>
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</form>
</div>
</div>
<!-- Create Credential Modal -->
<div x-show="showCreateCredentialModal" x-cloak class="modal" @click.self="showCreateCredentialModal = false">
<div class="modal-content">
@@ -775,6 +799,7 @@
showCreateCredentialModal: false,
showCreateBucketModal: false,
showCreatePresignedModal: false,
showUploadModal: false,
// Forms
newCredential: {
@@ -794,6 +819,8 @@
expires_in: 3600,
access_key: ''
},
uploadFile: null,
uploadKey: '',
init() {
this.loadStats();
@@ -1003,6 +1030,29 @@
}
},
async uploadObject() {
if (!this.uploadFile || !this.uploadKey) return;
try {
await fetch(`/api/buckets/${this.selectedBucket.name}/objects?key=${encodeURIComponent(this.uploadKey)}`, {
method: 'POST',
headers: {
'Content-Type': this.uploadFile.type || 'application/octet-stream',
'X-Object-Key': this.uploadKey
},
body: this.uploadFile
});
this.showMessage('Object uploaded successfully!');
this.showUploadModal = false;
this.uploadFile = null;
this.uploadKey = '';
this.viewBucket(this.selectedBucket.name);
} catch (error) {
console.error('Failed to upload object:', error);
this.showMessage('Upload failed', 'error');
}
},
async viewCredential(credential) {
// Could open a modal with full credential details
alert(`Access Key: ${credential.access_key}\nUser: ${credential.user_name || 'N/A'}\nStatus: ${credential.is_active ? 'Active' : 'Inactive'}`);

View File

@@ -589,6 +589,32 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/CreatePresignedUrlResponse'
/api/buckets/{bucketName}/objects/{objectKey}/presigned-upload:
parameters:
- $ref: '#/components/parameters/BucketName'
- $ref: '#/components/parameters/ObjectKey'
post:
operationId: createPresignedUpload
tags:
- Management - Presigned URLs
summary: Create presigned upload URL
description: Creates an empty object and returns a presigned URL for uploading content
security:
- ApiKey: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreatePresignedUploadRequest'
responses:
'201':
description: Presigned URL created
content:
application/json:
schema:
$ref: '#/components/schemas/CreatePresignedUrlResponse'
components:
schemas:
CreatePresignedUrlResponse:
@@ -597,6 +623,24 @@ components:
url:
type: string
example: /my-bucket/file.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20230115%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230115T103000Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=abc123&hash=def456
CreatePresignedUploadRequest:
type: object
required:
- access_key
properties:
access_key:
type: string
example: "AKIAIOSFODNN7EXAMPLE"
expires_in:
type: integer
minimum: 1
maximum: 604800
default: 3600
example: 3600
content_type:
type: string
default: "application/octet-stream"
example: "image/jpeg"
ObjectSummary:
type: object
properties:

View File

@@ -580,3 +580,29 @@
application/json:
schema:
$ref: './schemas.yaml#/CreatePresignedUrlResponse'
/api/buckets/{bucketName}/objects/{objectKey}/presigned-upload:
parameters:
- $ref: './parameters.yaml#/BucketName'
- $ref: './parameters.yaml#/ObjectKey'
post:
operationId: createPresignedUpload
tags:
- Management - Presigned URLs
summary: Create presigned upload URL
description: Creates an empty object and returns a presigned URL for uploading content
security:
- ApiKey: []
requestBody:
required: true
content:
application/json:
schema:
$ref: './schemas.yaml#/CreatePresignedUploadRequest'
responses:
'201':
description: Presigned URL created
content:
application/json:
schema:
$ref: './schemas.yaml#/CreatePresignedUrlResponse'

View File

@@ -628,6 +628,25 @@ CreatePresignedUrlRequest:
type: string
example: "AKIAIOSFODNN7EXAMPLE"
CreatePresignedUploadRequest:
type: object
required:
- access_key
properties:
access_key:
type: string
example: "AKIAIOSFODNN7EXAMPLE"
expires_in:
type: integer
minimum: 1
maximum: 604800
default: 3600
example: 3600
content_type:
type: string
default: "application/octet-stream"
example: "image/jpeg"
CreatePresignedUrlResponse:
type: object
properties: