feat: Add support for uploading a file directly to the file provider from the client (#12224)

* feat: Add support for uploading a file directly to the file provider from the client

* fix: Add missing types and add a couple of module tests

* fix: Allow nested routes, add test for it
This commit is contained in:
Stevche Radevski
2025-04-18 10:22:00 +02:00
committed by GitHub
parent 6b1d8cd3d4
commit c4a0b63778
11 changed files with 314 additions and 5 deletions
@@ -1,6 +1,7 @@
import {
DeleteObjectCommand,
GetObjectCommand,
ObjectCannedACL,
PutObjectCommand,
S3Client,
S3ClientConfigType,
@@ -36,6 +37,8 @@ interface S3FileServiceConfig {
additionalClientConfig?: Record<string, any>
}
const DEFAULT_UPLOAD_EXPIRATION_DURATION_SECONDS = 60 * 60
export class S3FileService extends AbstractFileProviderService {
static identifier = "s3"
protected config_: S3FileServiceConfig
@@ -175,4 +178,41 @@ export class S3FileService extends AbstractFileProviderService {
expiresIn: this.config_.downloadFileDuration,
})
}
// Note: Some providers (eg. AWS S3) allows IAM policies to further restrict what can be uploaded.
async getPresignedUploadUrl(
fileData: FileTypes.ProviderGetPresignedUploadUrlDTO
): Promise<FileTypes.ProviderFileResultDTO> {
if (!fileData?.filename) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`No filename provided`
)
}
const fileKey = `${this.config_.prefix}${fileData.filename}`
let acl: ObjectCannedACL | undefined
if (fileData.access) {
acl = fileData.access === "public" ? "public-read" : "private"
}
// Using content-type, acl, etc. doesn't work with all providers, and some simply ignore it.
const command = new PutObjectCommand({
Bucket: this.config_.bucket,
ContentType: fileData.mimeType,
ACL: acl,
Key: fileKey,
})
const signedUrl = await getSignedUrl(this.client_, command, {
expiresIn:
fileData.expiresIn ?? DEFAULT_UPLOAD_EXPIRATION_DURATION_SECONDS,
})
return {
url: signedUrl,
key: fileKey,
}
}
}