diff --git a/config/routes/console_api.yaml b/config/routes/console_api.yaml index dc8f7da..e1c04eb 100644 --- a/config/routes/console_api.yaml +++ b/config/routes/console_api.yaml @@ -38,6 +38,14 @@ console_api_object_detail: 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: '.+' + console_api_multipart_uploads: path: /api/buckets/{bucketName}/multipart-uploads controller: App\Controller\ConsoleApiController::multipartUploads diff --git a/src/Controller/ConsoleApiController.php b/src/Controller/ConsoleApiController.php index 28c423a..e3bdc6c 100644 --- a/src/Controller/ConsoleApiController.php +++ b/src/Controller/ConsoleApiController.php @@ -30,9 +30,9 @@ class ConsoleApiController extends AbstractController // Credentials Management public function credentials(Request $request): JsonResponse { - $authResp = $this->checkAuth($request); - if ($authResp !== null) { - return $authResp; + $resp = $this->checkAuth($request); + if (!empty($resp)) { + return $resp; } if ($request->getMethod() === 'GET') { $credentials = $this->entityManager->getRepository(S3Credential::class)->findAll(); @@ -338,6 +338,40 @@ class ConsoleApiController extends AbstractController 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 { @@ -476,4 +510,4 @@ class ConsoleApiController extends AbstractController return round($bytes / pow(1024, $i), 2) . ' ' . $units[$i]; } -} \ No newline at end of file +} diff --git a/templates/openapi/openapi.yaml b/templates/openapi/openapi.yaml index 502c284..4bf3a80 100644 --- a/templates/openapi/openapi.yaml +++ b/templates/openapi/openapi.yaml @@ -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: diff --git a/templates/openapi/paths.yaml b/templates/openapi/paths.yaml index 7922ac9..5deeb46 100644 --- a/templates/openapi/paths.yaml +++ b/templates/openapi/paths.yaml @@ -573,6 +573,32 @@ application/json: schema: $ref: './schemas.yaml#/CreatePresignedUrlRequest' + responses: + '201': + description: Presigned URL created + content: + 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 diff --git a/templates/openapi/schemas.yaml b/templates/openapi/schemas.yaml index ea49e1e..3b6472c 100644 --- a/templates/openapi/schemas.yaml +++ b/templates/openapi/schemas.yaml @@ -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: