chore(medusa-file-s3): Add cache-control option + update to sdk v3 (#4884)
* add cache-control option, fix delete function, update to sdk v3 * Create cool-pears-trade.md * reformat throw line, remove dev comment * remove console.log debugging statement --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
1 parent
33ffdaf42d
commit
046b0dcfa5
4 files changed
+1318
-55
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"medusa-file-s3": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Chore(medusa-file-s3): Add cache-control option, fix delete function, update to sdk v3
|
||||||
@@ -32,7 +32,9 @@
|
|||||||
"@medusajs/medusa": "^1.12.0"
|
"@medusajs/medusa": "^1.12.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws-sdk": "^2.983.0",
|
"@aws-sdk/client-s3": "^3.400.0",
|
||||||
|
"@aws-sdk/lib-storage": "^3.400.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.400.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"medusa-core-utils": "^1.2.0",
|
"medusa-core-utils": "^1.2.0",
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import aws from "aws-sdk"
|
import type { S3ClientConfigType, PutObjectCommandInput, GetObjectCommandOutput } from "@aws-sdk/client-s3"
|
||||||
|
import { Upload } from "@aws-sdk/lib-storage"
|
||||||
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
||||||
|
import {
|
||||||
|
S3Client,
|
||||||
|
PutObjectCommand,
|
||||||
|
DeleteObjectCommand,
|
||||||
|
GetObjectCommand
|
||||||
|
} from "@aws-sdk/client-s3"
|
||||||
import { parse } from "path"
|
import { parse } from "path"
|
||||||
import { AbstractFileService, IFileService } from "@medusajs/medusa"
|
import { AbstractFileService, IFileService } from "@medusajs/medusa"
|
||||||
import {
|
import {
|
||||||
@@ -7,10 +15,9 @@ import {
|
|||||||
FileServiceUploadResult,
|
FileServiceUploadResult,
|
||||||
GetUploadedFileType,
|
GetUploadedFileType,
|
||||||
UploadStreamDescriptorType,
|
UploadStreamDescriptorType,
|
||||||
|
Logger
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import stream from "stream"
|
import stream from "stream"
|
||||||
import { PutObjectRequest } from "aws-sdk/clients/s3"
|
|
||||||
import { ClientConfiguration } from "aws-sdk/clients/s3"
|
|
||||||
|
|
||||||
class S3Service extends AbstractFileService implements IFileService {
|
class S3Service extends AbstractFileService implements IFileService {
|
||||||
protected bucket_: string
|
protected bucket_: string
|
||||||
@@ -18,11 +25,13 @@ class S3Service extends AbstractFileService implements IFileService {
|
|||||||
protected accessKeyId_: string
|
protected accessKeyId_: string
|
||||||
protected secretAccessKey_: string
|
protected secretAccessKey_: string
|
||||||
protected region_: string
|
protected region_: string
|
||||||
protected endpoint_: string
|
|
||||||
protected awsConfigObject_: any
|
protected awsConfigObject_: any
|
||||||
protected downloadFileDuration_: string
|
protected downloadFileDuration_: number
|
||||||
|
protected cacheControl_: string
|
||||||
|
protected logger_: Logger
|
||||||
|
protected client_: S3Client
|
||||||
|
|
||||||
constructor({}, options) {
|
constructor({ logger }, options) {
|
||||||
super({}, options)
|
super({}, options)
|
||||||
|
|
||||||
this.bucket_ = options.bucket
|
this.bucket_ = options.bucket
|
||||||
@@ -30,22 +39,26 @@ class S3Service extends AbstractFileService implements IFileService {
|
|||||||
this.accessKeyId_ = options.access_key_id
|
this.accessKeyId_ = options.access_key_id
|
||||||
this.secretAccessKey_ = options.secret_access_key
|
this.secretAccessKey_ = options.secret_access_key
|
||||||
this.region_ = options.region
|
this.region_ = options.region
|
||||||
this.endpoint_ = options.endpoint
|
|
||||||
this.downloadFileDuration_ = options.download_file_duration
|
this.downloadFileDuration_ = options.download_file_duration
|
||||||
this.awsConfigObject_ = options.aws_config_object ?? {}
|
this.awsConfigObject_ = options.aws_config_object ?? {}
|
||||||
|
this.cacheControl_ = options.cache_control ?? "max-age=31536000"
|
||||||
|
this.logger_ = logger
|
||||||
|
this.client_ = this.getClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getClient(overwriteConfig: Partial<ClientConfiguration> = {}) {
|
protected getClient(overwriteConfig: Partial<S3ClientConfigType> = {}) {
|
||||||
const config: ClientConfiguration = {
|
const config: S3ClientConfigType = {
|
||||||
accessKeyId: this.accessKeyId_,
|
credentials: {
|
||||||
secretAccessKey: this.secretAccessKey_,
|
accessKeyId: this.accessKeyId_,
|
||||||
|
secretAccessKey: this.secretAccessKey_,
|
||||||
|
},
|
||||||
region: this.region_,
|
region: this.region_,
|
||||||
endpoint: this.endpoint_,
|
|
||||||
...this.awsConfigObject_,
|
...this.awsConfigObject_,
|
||||||
|
signatureVersion: 'v4',
|
||||||
...overwriteConfig,
|
...overwriteConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
return new aws.S3(config)
|
return new S3Client(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
async upload(file: Express.Multer.File): Promise<FileServiceUploadResult> {
|
async upload(file: Express.Multer.File): Promise<FileServiceUploadResult> {
|
||||||
@@ -63,55 +76,52 @@ class S3Service extends AbstractFileService implements IFileService {
|
|||||||
acl: undefined,
|
acl: undefined,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const client = this.getClient()
|
|
||||||
|
|
||||||
const parsedFilename = parse(file.originalname)
|
const parsedFilename = parse(file.originalname)
|
||||||
|
|
||||||
const fileKey = `${parsedFilename.name}-${Date.now()}${parsedFilename.ext}`
|
const fileKey = `${parsedFilename.name}-${Date.now()}${parsedFilename.ext}`
|
||||||
|
|
||||||
const params = {
|
const command = new PutObjectCommand({
|
||||||
ACL: options.acl ?? (options.isProtected ? "private" : "public-read"),
|
ACL: options.acl ?? (options.isProtected ? "private" : "public-read"),
|
||||||
Bucket: this.bucket_,
|
Bucket: this.bucket_,
|
||||||
Body: fs.createReadStream(file.path),
|
Body: fs.createReadStream(file.path),
|
||||||
Key: fileKey,
|
Key: fileKey,
|
||||||
ContentType: file.mimetype,
|
ContentType: file.mimetype,
|
||||||
}
|
CacheControl: this.cacheControl_
|
||||||
|
})
|
||||||
|
|
||||||
const result = await client.upload(params).promise()
|
try {
|
||||||
|
await this.client_.send(command)
|
||||||
return {
|
return {
|
||||||
url: result.Location,
|
url: `${this.s3Url_}/${fileKey}`,
|
||||||
key: result.Key,
|
key: fileKey,
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger_.error(e)
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(file: DeleteFileType): Promise<void> {
|
async delete(file: DeleteFileType): Promise<void> {
|
||||||
const client = this.getClient()
|
const command = new DeleteObjectCommand({
|
||||||
|
|
||||||
const params = {
|
|
||||||
Bucket: this.bucket_,
|
Bucket: this.bucket_,
|
||||||
Key: `${file}`,
|
Key: `${file.file_key}`,
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
client.deleteObject(params, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.client_.send(command)
|
||||||
|
} catch (e) {
|
||||||
|
this.logger_.error(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUploadStreamDescriptor(fileData: UploadStreamDescriptorType) {
|
async getUploadStreamDescriptor(fileData: UploadStreamDescriptorType) {
|
||||||
const client = this.getClient()
|
|
||||||
const pass = new stream.PassThrough()
|
const pass = new stream.PassThrough()
|
||||||
|
|
||||||
const isPrivate = fileData.isPrivate ?? true // default to private
|
const isPrivate = fileData.isPrivate ?? true // default to private
|
||||||
|
|
||||||
const fileKey = `${fileData.name}.${fileData.ext}`
|
const fileKey = `${fileData.name}.${fileData.ext}`
|
||||||
const params: PutObjectRequest = {
|
const params: PutObjectCommandInput = {
|
||||||
ACL: isPrivate ? "private" : "public-read",
|
ACL: isPrivate ? "private" : "public-read",
|
||||||
Bucket: this.bucket_,
|
Bucket: this.bucket_,
|
||||||
Body: pass,
|
Body: pass,
|
||||||
@@ -119,9 +129,14 @@ class S3Service extends AbstractFileService implements IFileService {
|
|||||||
ContentType: fileData.contentType as string,
|
ContentType: fileData.contentType as string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadJob = new Upload({
|
||||||
|
client: this.client_,
|
||||||
|
params
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
writeStream: pass,
|
writeStream: pass,
|
||||||
promise: client.upload(params).promise(),
|
promise: uploadJob.done(),
|
||||||
url: `${this.s3Url_}/${fileKey}`,
|
url: `${this.s3Url_}/${fileKey}`,
|
||||||
fileKey,
|
fileKey,
|
||||||
}
|
}
|
||||||
@@ -130,28 +145,25 @@ class S3Service extends AbstractFileService implements IFileService {
|
|||||||
async getDownloadStream(
|
async getDownloadStream(
|
||||||
fileData: GetUploadedFileType
|
fileData: GetUploadedFileType
|
||||||
): Promise<NodeJS.ReadableStream> {
|
): Promise<NodeJS.ReadableStream> {
|
||||||
const client = this.getClient()
|
const command = new GetObjectCommand({
|
||||||
|
|
||||||
const params = {
|
|
||||||
Bucket: this.bucket_,
|
Bucket: this.bucket_,
|
||||||
Key: `${fileData.fileKey}`,
|
Key: `${fileData.fileKey}`,
|
||||||
}
|
})
|
||||||
|
|
||||||
return await client.getObject(params).createReadStream()
|
const response: GetObjectCommandOutput = await this.client_.send(command)
|
||||||
|
|
||||||
|
return response.Body as NodeJS.ReadableStream
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPresignedDownloadUrl(
|
async getPresignedDownloadUrl(
|
||||||
fileData: GetUploadedFileType
|
fileData: GetUploadedFileType
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const client = this.getClient({ signatureVersion: "v4" })
|
const command = new GetObjectCommand({
|
||||||
|
|
||||||
const params = {
|
|
||||||
Bucket: this.bucket_,
|
Bucket: this.bucket_,
|
||||||
Key: `${fileData.fileKey}`,
|
Key: `${fileData.fileKey}`,
|
||||||
Expires: this.downloadFileDuration_,
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return await client.getSignedUrlPromise("getObject", params)
|
return await getSignedUrl(this.client_, command, { expiresIn: this.downloadFileDuration_ })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user