feat(medusa-file-s3,medusa-file-minio): Upgrade to TypeScript (#3740)

This commit is contained in:
Derek Wene authored and GitHub committed 2023-04-23 12:31:16 +02:00
1 parent ba45a316a7
commit 8b6464180a
11 files changed
+661 -420

No files matched your search

+10 -17
View File
@@ -2,7 +2,10 @@
"name": "medusa-file-minio",
"version": "1.1.6",
"description": "MinIO server file connector for Medusa",
"main": "index.js",
"main": "dist/index.js",
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
@@ -11,32 +14,22 @@
"author": "Edin Skeja",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.16.0",
"@babel/core": "^7.16.0",
"@babel/node": "^7.16.0",
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@babel/plugin-transform-instanceof": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.16.4",
"@babel/preset-env": "^7.16.4",
"@babel/register": "^7.16.0",
"@babel/runtime": "^7.16.3",
"client-sessions": "^0.8.0",
"@medusajs/medusa": "1.8.0-rc.6",
"cross-env": "^5.2.1",
"jest": "^25.5.4",
"medusa-interfaces": "^1.3.7"
"typescript": "^4.9.5"
},
"scripts": {
"build": "babel src --out-dir dist/ --ignore '**/__tests__','**/__mocks__'",
"prepare": "cross-env NODE_ENV=production yarn run build",
"watch": "babel -w src --out-dir dist/ --ignore '**/__tests__','**/__mocks__'",
"test": "jest --passWithNoTests src"
"test": "jest --passWithNoTests src",
"build": "tsc",
"watch": "tsc --watch"
},
"peerDependencies": {
"medusa-interfaces": "1.3.7"
},
"dependencies": {
"@babel/plugin-transform-classes": "^7.16.0",
"aws-sdk": "^2.1043.0",
"aws-sdk": "^2.983.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.2.0",
@@ -1,182 +0,0 @@
import stream from "stream"
import aws from "aws-sdk"
import { parse } from "path"
import fs from "fs"
import { AbstractFileService } from "@medusajs/medusa"
import { MedusaError } from "medusa-core-utils"
class MinioService extends AbstractFileService {
constructor({}, options) {
super({}, options)
this.bucket_ = options.bucket
this.accessKeyId_ = options.access_key_id
this.secretAccessKey_ = options.secret_access_key
this.private_bucket_ = options.private_bucket
this.private_access_key_id_ =
options.private_access_key_id ?? this.accessKeyId_
this.private_secret_access_key_ =
options.private_secret_access_key ?? this.secretAccessKey_
this.endpoint_ = options.endpoint
this.s3ForcePathStyle_ = true
this.signatureVersion_ = "v4"
this.downloadUrlDuration = options.download_url_duration ?? 60 // 60 seconds
}
upload(file) {
this.updateAwsConfig_()
return this.uploadFile(file)
}
uploadProtected(file) {
this.validatePrivateBucketConfiguration_(true)
this.updateAwsConfig_(true)
return this.uploadFile(file, { isProtected: true })
}
uploadFile(file, options = { isProtected: false }) {
const parsedFilename = parse(file.originalname)
const fileKey = `${parsedFilename.name}-${Date.now()}${parsedFilename.ext}`
const s3 = new aws.S3()
const params = {
ACL: options.isProtected ? "private" : "public-read",
Bucket: options.isProtected ? this.private_bucket_ : this.bucket_,
Body: fs.createReadStream(file.path),
Key: fileKey,
}
return new Promise((resolve, reject) => {
s3.upload(params, (err, data) => {
if (err) {
reject(err)
return
}
resolve({ url: data.Location, key: data.Key })
})
})
}
async delete(file) {
this.updateAwsConfig_()
const s3 = new aws.S3()
const params = {
Bucket: this.bucket_,
Key: `${file.fileKey}`,
}
return await Promise.all([
new Promise((resolve, reject) =>
s3.deleteObject({ ...params, Bucket: this.bucket_ }, (err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
})
),
new Promise((resolve, reject) =>
s3.deleteObject(
{ ...params, Bucket: this.private_bucket_ },
(err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
}
)
),
])
}
async getUploadStreamDescriptor({ usePrivateBucket = true, ...fileData }) {
this.validatePrivateBucketConfiguration_(usePrivateBucket)
this.updateAwsConfig_(usePrivateBucket)
const pass = new stream.PassThrough()
const fileKey = `${fileData.name}.${fileData.ext}`
const params = {
Bucket: usePrivateBucket ? this.private_bucket_ : this.bucket_,
Body: pass,
Key: fileKey,
}
const s3 = new aws.S3()
return {
writeStream: pass,
promise: s3.upload(params).promise(),
url: `${this.spacesUrl_}/${fileKey}`,
fileKey,
}
}
async getDownloadStream({ usePrivateBucket = true, ...fileData }) {
this.validatePrivateBucketConfiguration_(usePrivateBucket)
this.updateAwsConfig_(usePrivateBucket)
const s3 = new aws.S3()
const params = {
Bucket: usePrivateBucket ? this.private_bucket_ : this.bucket_,
Key: `${fileData.fileKey}`,
}
return s3.getObject(params).createReadStream()
}
async getPresignedDownloadUrl({ usePrivateBucket = true, ...fileData }) {
this.validatePrivateBucketConfiguration_(usePrivateBucket)
this.updateAwsConfig_(usePrivateBucket, {
signatureVersion: "v4",
})
const s3 = new aws.S3()
const params = {
Bucket: usePrivateBucket ? this.private_bucket_ : this.bucket_,
Key: `${fileData.fileKey}`,
Expires: this.downloadUrlDuration,
}
return await s3.getSignedUrlPromise("getObject", params)
}
validatePrivateBucketConfiguration_(usePrivateBucket) {
if (
usePrivateBucket &&
(!this.private_access_key_id_ || !this.private_bucket_)
) {
throw new MedusaError(
MedusaError.Types.UNEXPECTED_STATE,
"Private bucket is not configured"
)
}
}
updateAwsConfig_(usePrivateBucket = false, additionalConfiguration = {}) {
aws.config.setPromisesDependency(null)
aws.config.update(
{
accessKeyId: usePrivateBucket
? this.private_access_key_id_
: this.accessKeyId_,
secretAccessKey: usePrivateBucket
? this.private_secret_access_key_
: this.secretAccessKey_,
endpoint: this.endpoint_,
s3ForcePathStyle: this.s3ForcePathStyle_,
signatureVersion: this.signatureVersion_,
...additionalConfiguration,
},
true
)
}
}
export default MinioService
@@ -0,0 +1,212 @@
import stream from "stream"
import aws from "aws-sdk"
import { parse } from "path"
import fs from "fs"
import {
AbstractFileService,
DeleteFileType,
FileServiceUploadResult,
GetUploadedFileType,
IFileService,
UploadStreamDescriptorType,
} from "@medusajs/medusa"
import { MedusaError } from "medusa-core-utils"
import { ClientConfiguration, PutObjectRequest } from "aws-sdk/clients/s3"
class MinioService extends AbstractFileService implements IFileService {
protected bucket_: string
protected accessKeyId_: string
protected secretAccessKey_: string
protected private_bucket_: string
protected private_access_key_id_: string
protected private_secret_access_key_: string
protected endpoint_: string
protected s3ForcePathStyle_: boolean
protected signatureVersion_: string
protected downloadUrlDuration: string | number
constructor({}, options) {
super({}, options)
this.bucket_ = options.bucket
this.accessKeyId_ = options.access_key_id
this.secretAccessKey_ = options.secret_access_key
this.private_bucket_ = options.private_bucket
this.private_access_key_id_ =
options.private_access_key_id ?? this.accessKeyId_
this.private_secret_access_key_ =
options.private_secret_access_key ?? this.secretAccessKey_
this.endpoint_ = options.endpoint
this.s3ForcePathStyle_ = true
this.signatureVersion_ = "v4"
this.downloadUrlDuration = options.download_url_duration ?? 60 // 60 seconds
}
protected buildUrl(bucket: string, key: string) {
return `${this.endpoint_}/${bucket}/${key}`
}
async upload(file: Express.Multer.File): Promise<FileServiceUploadResult> {
return await this.uploadFile(file)
}
async uploadProtected(
file: Express.Multer.File
): Promise<FileServiceUploadResult> {
this.validatePrivateBucketConfiguration_(true)
return await this.uploadFile(file, { isProtected: true })
}
protected async uploadFile(
file: Express.Multer.File,
options: { isProtected: boolean } = { isProtected: false }
) {
const parsedFilename = parse(file.originalname)
const fileKey = `${parsedFilename.name}-${Date.now()}${parsedFilename.ext}`
const client = this.getClient(options.isProtected)
const params = {
ACL: options.isProtected ? "private" : "public-read",
Bucket: options.isProtected ? this.private_bucket_ : this.bucket_,
Body: fs.createReadStream(file.path),
Key: fileKey,
ContentType: file.mimetype,
}
const result = await client.upload(params).promise()
return { url: result.Location, key: result.Key }
}
async delete(file: DeleteFileType): Promise<void> {
const privateClient = this.getClient(false)
const publicClient = this.getClient(true)
const params = {
Bucket: this.bucket_,
Key: `${file.fileKey}`,
}
await Promise.all([
new Promise((resolve, reject) =>
publicClient.deleteObject(
{ ...params, Bucket: this.bucket_ },
(err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
}
)
),
new Promise((resolve, reject) =>
privateClient.deleteObject(
{ ...params, Bucket: this.private_bucket_ },
(err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
}
)
),
])
}
async getUploadStreamDescriptor(
fileData: UploadStreamDescriptorType & {
usePrivateBucket?: boolean
contentType?: string
}
) {
const usePrivateBucket = !!fileData.usePrivateBucket
this.validatePrivateBucketConfiguration_(usePrivateBucket)
const client = this.getClient(usePrivateBucket)
const pass = new stream.PassThrough()
const fileKey = `${fileData.name}.${fileData.ext}`
const params: PutObjectRequest = {
Bucket: usePrivateBucket ? this.private_bucket_ : this.bucket_,
Body: pass,
Key: fileKey,
ContentType: fileData.contentType,
}
return {
writeStream: pass,
promise: client.upload(params).promise(),
url: this.buildUrl(params.Bucket, fileKey),
fileKey,
}
}
async getDownloadStream(
fileData: GetUploadedFileType & { usePrivateBucket?: boolean }
) {
const usePrivateBucket = !!fileData.usePrivateBucket
this.validatePrivateBucketConfiguration_(usePrivateBucket)
const client = this.getClient(usePrivateBucket)
const params = {
Bucket: usePrivateBucket ? this.private_bucket_ : this.bucket_,
Key: `${fileData.fileKey}`,
}
return client.getObject(params).createReadStream()
}
async getPresignedDownloadUrl({ usePrivateBucket = true, ...fileData }) {
this.validatePrivateBucketConfiguration_(usePrivateBucket)
const client = this.getClient(usePrivateBucket, {
signatureVersion: "v4",
})
const params = {
Bucket: usePrivateBucket ? this.private_bucket_ : this.bucket_,
Key: `${fileData.fileKey}`,
Expires: this.downloadUrlDuration,
}
return await client.getSignedUrlPromise("getObject", params)
}
validatePrivateBucketConfiguration_(usePrivateBucket) {
if (
usePrivateBucket &&
(!this.private_access_key_id_ || !this.private_bucket_)
) {
throw new MedusaError(
MedusaError.Types.UNEXPECTED_STATE,
"Private bucket is not configured"
)
}
}
protected getClient(
usePrivateBucket = false,
additionalConfiguration: Partial<ClientConfiguration> = {}
) {
return new aws.S3({
accessKeyId: usePrivateBucket
? this.private_access_key_id_
: this.accessKeyId_,
secretAccessKey: usePrivateBucket
? this.private_secret_access_key_
: this.secretAccessKey_,
endpoint: this.endpoint_,
s3ForcePathStyle: this.s3ForcePathStyle_,
signatureVersion: this.signatureVersion_,
...additionalConfiguration,
})
}
}
export default MinioService
+30
View File
@@ -0,0 +1,30 @@
{
"compilerOptions": {
"lib": ["es5", "es6", "es2019"],
"target": "es5",
"outDir": "./dist",
"rootDir": "src",
"esModuleInterop": true,
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitReturns": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"allowJs": true,
"skipLibCheck": true,
"downlevelIteration": true // to use ES5 specific tooling
},
"include": ["src"],
"exclude": [
"dist",
"src/**/__tests__",
"src/**/__mocks__",
"src/**/__fixtures__",
"node_modules"
]
}