169 lines
4.6 KiB
TypeScript
Raw Normal View History

2023-04-13 08:45:44 +02:00
import type { ReadStream } from 'node:fs';
import { getOr } from 'lodash/fp';
import AWS from 'aws-sdk';
import { getBucketFromUrl } from './utils';
interface File {
name: string;
alternativeText?: string;
caption?: string;
width?: number;
height?: number;
formats?: Record<string, unknown>;
hash: string;
ext?: string;
mime: string;
size: number;
url: string;
previewUrl?: string;
path?: string;
provider?: string;
provider_metadata?: Record<string, unknown>;
stream?: ReadStream;
buffer?: Buffer;
}
2018-02-20 15:57:34 +01:00
2023-04-06 16:19:38 +02:00
// TODO V5: Migrate to aws-sdk v3
2023-04-13 08:45:44 +02:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('aws-sdk/lib/maintenance_mode_message').suppress = true;
2018-02-20 15:57:34 +01:00
2023-04-21 12:25:39 +02:00
function hasUrlProtocol(url: string) {
// Regex to test protocol like "http://", "https://"
return /^\w*:\/\//.test(url);
}
2023-04-13 08:45:44 +02:00
interface InitOptions extends Partial<AWS.S3.ClientConfiguration> {
baseUrl?: string;
rootPath?: string;
s3Options: AWS.S3.ClientConfiguration & {
params: {
Bucket: string; // making it required
ACL?: string;
signedUrlExpires?: string;
};
};
}
export = {
init({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions) {
if (Object.keys(legacyS3Options).length > 0) {
process.emitWarning(
2022-10-25 14:52:17 +02:00
"S3 configuration options passed at root level of the plugin's providerOptions is deprecated and will be removed in a future release. Please wrap them inside the 's3Options:{}' property."
);
2023-03-20 12:27:33 +01:00
}
const config = { ...s3Options, ...legacyS3Options };
2018-02-21 14:06:57 +01:00
const S3 = new AWS.S3({
apiVersion: '2006-03-01',
...config,
2018-02-21 14:06:57 +01:00
});
const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, '')}/` : '';
2023-04-13 08:45:44 +02:00
const getFileKey = (file: File) => {
const path = file.path ? `${file.path}/` : '';
return `${filePrefix}${path}${file.hash}${file.ext}`;
2023-03-20 12:27:33 +01:00
};
2023-03-08 10:44:21 +01:00
const ACL = getOr('public-read', ['params', 'ACL'], config);
2023-02-14 19:02:00 +01:00
2023-04-13 08:45:44 +02:00
const upload = (file: File, customParams = {}): Promise<void> =>
2022-01-05 19:02:04 +01:00
new Promise((resolve, reject) => {
const fileKey = getFileKey(file);
2018-02-21 14:06:57 +01:00
2023-04-13 08:45:44 +02:00
if (!file.stream && !file.buffer) {
reject(new Error('Missing file stream or buffer'));
return;
}
const params = {
Key: fileKey,
Bucket: config.params.Bucket,
Body: file.stream || file.buffer,
ACL,
ContentType: file.mime,
...customParams,
};
const onUploaded = (err: Error, data: AWS.S3.ManagedUpload.SendData) => {
if (err) {
return reject(err);
}
// set the bucket file url
2023-04-21 12:25:39 +02:00
if (baseUrl) {
// Construct the url with the baseUrl
file.url = `${baseUrl}/${fileKey}`;
2023-04-13 08:45:44 +02:00
} else {
2023-04-21 12:25:39 +02:00
// Add the protocol if it is missing
// Some providers like DigitalOcean Spaces return the url without the protocol
file.url = hasUrlProtocol(data.Location) ? data.Location : `https://${data.Location}`;
2022-01-05 19:02:04 +01:00
}
2023-04-13 08:45:44 +02:00
resolve();
};
S3.upload(params, onUploaded);
2022-01-05 19:02:04 +01:00
});
return {
2023-02-09 15:02:15 +01:00
isPrivate() {
2023-02-14 19:02:00 +01:00
return ACL === 'private';
2023-02-09 15:02:15 +01:00
},
2023-04-13 08:45:44 +02:00
async getSignedUrl(file: File): Promise<{ url: string }> {
// Do not sign the url if it does not come from the same bucket.
const { bucket } = getBucketFromUrl(file.url);
if (bucket !== config.params.Bucket) {
return { url: file.url };
}
2023-02-09 15:02:15 +01:00
return new Promise((resolve, reject) => {
2023-03-21 15:27:05 +01:00
const fileKey = getFileKey(file);
2023-02-14 19:02:00 +01:00
2023-02-09 15:02:15 +01:00
S3.getSignedUrl(
'getObject',
{
Bucket: config.params.Bucket,
Key: fileKey,
2023-03-24 09:35:22 +01:00
Expires: getOr(15 * 60, ['params', 'signedUrlExpires'], config), // 15 minutes
2023-02-09 15:02:15 +01:00
},
(err, url) => {
if (err) {
return reject(err);
}
resolve({ url });
}
);
});
},
2023-04-13 08:45:44 +02:00
uploadStream(file: File, customParams = {}) {
2022-01-05 19:02:04 +01:00
return upload(file, customParams);
},
2023-04-13 08:45:44 +02:00
upload(file: File, customParams = {}) {
2022-01-05 19:02:04 +01:00
return upload(file, customParams);
2018-02-20 15:57:34 +01:00
},
2023-04-13 08:45:44 +02:00
delete(file: File, customParams = {}): Promise<void> {
2018-02-22 14:43:10 +01:00
return new Promise((resolve, reject) => {
// delete file on S3 bucket
const fileKey = getFileKey(file);
2019-07-18 19:28:52 +02:00
S3.deleteObject(
{
Key: fileKey,
2023-04-13 08:45:44 +02:00
Bucket: config.params.Bucket,
...customParams,
2019-07-18 19:28:52 +02:00
},
(err) => {
2019-07-18 19:28:52 +02:00
if (err) {
return reject(err);
}
2018-02-20 15:57:34 +01:00
2019-07-18 19:28:52 +02:00
resolve();
}
);
2018-02-22 14:43:10 +01:00
});
2019-07-18 19:28:52 +02:00
},
2018-02-20 15:57:34 +01:00
};
2019-07-18 19:28:52 +02:00
},
2018-02-20 15:57:34 +01:00
};