Skip to content

Commit

Permalink
chore(medusa-file-s3): Add cache-control option + update to sdk v3 (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>
  • Loading branch information
pevey and olivermrbl authored Aug 29, 2023
1 parent 33ffdaf commit 046b0dc
Show file tree
Hide file tree
Showing 4 changed files with 1,318 additions and 55 deletions.
6 changes: 6 additions & 0 deletions .changeset/cool-pears-trade.md
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion packages/medusa-file-s3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
"@medusajs/medusa": "^1.12.0"
},
"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",
"express": "^4.17.1",
"medusa-core-utils": "^1.2.0",
Expand Down
110 changes: 61 additions & 49 deletions packages/medusa-file-s3/src/services/s3.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,64 @@
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 { AbstractFileService, IFileService } from "@medusajs/medusa"
import {
DeleteFileType,
FileServiceUploadResult,
GetUploadedFileType,
UploadStreamDescriptorType,
Logger
} from "@medusajs/types"
import stream from "stream"
import { PutObjectRequest } from "aws-sdk/clients/s3"
import { ClientConfiguration } from "aws-sdk/clients/s3"

class S3Service extends AbstractFileService implements IFileService {
protected bucket_: string
protected s3Url_: string
protected accessKeyId_: string
protected secretAccessKey_: string
protected region_: string
protected endpoint_: string
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)

this.bucket_ = options.bucket
this.s3Url_ = options.s3_url
this.accessKeyId_ = options.access_key_id
this.secretAccessKey_ = options.secret_access_key
this.region_ = options.region
this.endpoint_ = options.endpoint
this.downloadFileDuration_ = options.download_file_duration
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> = {}) {
const config: ClientConfiguration = {
accessKeyId: this.accessKeyId_,
secretAccessKey: this.secretAccessKey_,
protected getClient(overwriteConfig: Partial<S3ClientConfigType> = {}) {
const config: S3ClientConfigType = {
credentials: {
accessKeyId: this.accessKeyId_,
secretAccessKey: this.secretAccessKey_,
},
region: this.region_,
endpoint: this.endpoint_,
...this.awsConfigObject_,
signatureVersion: 'v4',
...overwriteConfig,
}

return new aws.S3(config)
return new S3Client(config)
}

async upload(file: Express.Multer.File): Promise<FileServiceUploadResult> {
Expand All @@ -63,65 +76,67 @@ class S3Service extends AbstractFileService implements IFileService {
acl: undefined,
}
) {
const client = this.getClient()

const parsedFilename = parse(file.originalname)

const fileKey = `${parsedFilename.name}-${Date.now()}${parsedFilename.ext}`

const params = {
const command = new PutObjectCommand({
ACL: options.acl ?? (options.isProtected ? "private" : "public-read"),
Bucket: this.bucket_,
Body: fs.createReadStream(file.path),
Key: fileKey,
ContentType: file.mimetype,
}

const result = await client.upload(params).promise()
CacheControl: this.cacheControl_
})

return {
url: result.Location,
key: result.Key,
try {
await this.client_.send(command)
return {
url: `${this.s3Url_}/${fileKey}`,
key: fileKey,
}
} catch (e) {
this.logger_.error(e)
throw e
}
}

async delete(file: DeleteFileType): Promise<void> {
const client = this.getClient()

const params = {
const command = new DeleteObjectCommand({
Bucket: this.bucket_,
Key: `${file}`,
}

return new Promise((resolve, reject) => {
client.deleteObject(params, (err, data) => {
if (err) {
reject(err)
return
}
resolve()
})
Key: `${file.file_key}`,
})

try {
await this.client_.send(command)
} catch (e) {
this.logger_.error(e)
}
}

async getUploadStreamDescriptor(fileData: UploadStreamDescriptorType) {
const client = this.getClient()
const pass = new stream.PassThrough()

const isPrivate = fileData.isPrivate ?? true // default to private

const fileKey = `${fileData.name}.${fileData.ext}`
const params: PutObjectRequest = {
const params: PutObjectCommandInput = {
ACL: isPrivate ? "private" : "public-read",
Bucket: this.bucket_,
Body: pass,
Key: fileKey,
ContentType: fileData.contentType as string,
}

const uploadJob = new Upload({
client: this.client_,
params
})

return {
writeStream: pass,
promise: client.upload(params).promise(),
promise: uploadJob.done(),
url: `${this.s3Url_}/${fileKey}`,
fileKey,
}
Expand All @@ -130,28 +145,25 @@ class S3Service extends AbstractFileService implements IFileService {
async getDownloadStream(
fileData: GetUploadedFileType
): Promise<NodeJS.ReadableStream> {
const client = this.getClient()

const params = {
const command = new GetObjectCommand({
Bucket: this.bucket_,
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(
fileData: GetUploadedFileType
): Promise<string> {
const client = this.getClient({ signatureVersion: "v4" })

const params = {
const command = new GetObjectCommand({
Bucket: this.bucket_,
Key: `${fileData.fileKey}`,
Expires: this.downloadFileDuration_,
}
})

return await client.getSignedUrlPromise("getObject", params)
return await getSignedUrl(this.client_, command, { expiresIn: this.downloadFileDuration_ })
}
}

Expand Down
Loading

0 comments on commit 046b0dc

Please sign in to comment.