feat: implement direct upload (#12328)
* feat: implement direct upload * feat: add direct-upload endpoint * refactor: implement feedback * refactor: have a dedicated endpoint for direct uploads * refactor: convert responses to snakecase * refactor: rename method to createImport * test: add tests for the presigned-urls endpoint
This commit is contained in:
@@ -167,8 +167,8 @@ export default class FileModuleService implements FileTypes.IFileModuleService {
|
||||
* const stream = await fileModuleService.getAsStream("file_123")
|
||||
* writeable.pipe(stream)
|
||||
*/
|
||||
getAsStream(id: string): Promise<Readable> {
|
||||
return this.fileProviderService_.getAsStream({ fileKey: id })
|
||||
getDownloadStream(id: string): Promise<Readable> {
|
||||
return this.fileProviderService_.getDownloadStream({ fileKey: id })
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,8 +70,8 @@ export default class FileProviderService {
|
||||
return this.fileProvider_.getPresignedUploadUrl(fileData)
|
||||
}
|
||||
|
||||
getAsStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
return this.fileProvider_.getAsStream(fileData)
|
||||
getDownloadStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
return this.fileProvider_.getDownloadStream(fileData)
|
||||
}
|
||||
|
||||
getAsBuffer(fileData: FileTypes.ProviderGetFileDTO): Promise<Buffer> {
|
||||
|
||||
@@ -85,7 +85,9 @@ export class LocalFileService extends AbstractFileProviderService {
|
||||
return
|
||||
}
|
||||
|
||||
async getAsStream(file: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
async getDownloadStream(
|
||||
file: FileTypes.ProviderGetFileDTO
|
||||
): Promise<Readable> {
|
||||
const baseDir = file.fileKey.startsWith("private-")
|
||||
? this.privateUploadDir_
|
||||
: this.uploadDir_
|
||||
@@ -124,6 +126,47 @@ export class LocalFileService extends AbstractFileProviderService {
|
||||
return this.getUploadFileUrl(file.fileKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pre-signed URL that the client (frontend) can use to trigger
|
||||
* a file upload. In this case, the Medusa backend will implement the
|
||||
* "/upload" endpoint to perform the file upload.
|
||||
*
|
||||
* Since, we do not want the client to perform link detection on the frontend
|
||||
* and then prepare a different kind of request for cloud providers and different
|
||||
* request for the local server, we will have to make these URLs self sufficient.
|
||||
*
|
||||
* What is a self sufficient URL
|
||||
*
|
||||
* - There should be no need to specify the MIME type or filename separately in request body (cloud providers don't allow it).
|
||||
* - There should be no need to pass auth headers like cookies. Again cloud providers
|
||||
* won't allow it and will likely result in a CORS error.
|
||||
*/
|
||||
async getPresignedUploadUrl(
|
||||
fileData: FileTypes.ProviderGetPresignedUploadUrlDTO
|
||||
): Promise<FileTypes.ProviderFileResultDTO> {
|
||||
if (!fileData?.filename) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`No filename provided`
|
||||
)
|
||||
}
|
||||
|
||||
const uploadUrl = new URL(
|
||||
"upload",
|
||||
`${this.backendUrl_.replace(/\/$/, "")}/`
|
||||
)
|
||||
|
||||
uploadUrl.searchParams.set("filename", fileData.filename)
|
||||
if (fileData.mimeType) {
|
||||
uploadUrl.searchParams.set("type", fileData.mimeType)
|
||||
}
|
||||
|
||||
return {
|
||||
url: uploadUrl.toString(),
|
||||
key: fileData.filename,
|
||||
}
|
||||
}
|
||||
|
||||
private getUploadFilePath = (baseDir: string, fileKey: string) => {
|
||||
return path.join(baseDir, fileKey)
|
||||
}
|
||||
|
||||
@@ -217,15 +217,17 @@ export class S3FileService extends AbstractFileProviderService {
|
||||
}
|
||||
}
|
||||
|
||||
async getAsStream(file: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
if (!file?.filename) {
|
||||
async getDownloadStream(
|
||||
file: FileTypes.ProviderGetFileDTO
|
||||
): Promise<Readable> {
|
||||
if (!file?.fileKey) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`No filename provided`
|
||||
`No fileKey provided`
|
||||
)
|
||||
}
|
||||
|
||||
const fileKey = `${this.config_.prefix}${file.filename}`
|
||||
const fileKey = `${this.config_.prefix}${file.fileKey}`
|
||||
const response = await this.client_.send(
|
||||
new GetObjectCommand({
|
||||
Key: fileKey,
|
||||
@@ -237,14 +239,14 @@ export class S3FileService extends AbstractFileProviderService {
|
||||
}
|
||||
|
||||
async getAsBuffer(file: FileTypes.ProviderGetFileDTO): Promise<Buffer> {
|
||||
if (!file?.filename) {
|
||||
if (!file?.fileKey) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`No filename provided`
|
||||
`No fileKey provided`
|
||||
)
|
||||
}
|
||||
|
||||
const fileKey = `${this.config_.prefix}${file.filename}`
|
||||
const fileKey = `${this.config_.prefix}${file.fileKey}`
|
||||
const response = await this.client_.send(
|
||||
new GetObjectCommand({
|
||||
Key: fileKey,
|
||||
|
||||
Reference in New Issue
Block a user