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 0bc7b2c..9a2875c 100644 --- a/src/Controller/ConsoleApiController.php +++ b/src/Controller/ConsoleApiController.php @@ -332,6 +332,39 @@ class ConsoleApiController extends AbstractController return new JsonResponse(['error' => 'Method not allowed'], 405); } + public function createPresignedUpload(string $bucketName, string $objectKey, Request $request): JsonResponse + { + if ($resp = $this->checkAuth($request)) { + 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 { 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: