Skip to content

Instantly share code, notes, and snippets.

@richardvanbergen
Created March 12, 2021 01:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save richardvanbergen/a21c701632a2f24a013d8e17495f3e2c to your computer and use it in GitHub Desktop.
Save richardvanbergen/a21c701632a2f24a013d8e17495f3e2c to your computer and use it in GitHub Desktop.
import AWS from 'aws-sdk'
import { UploadedFile } from 'express-fileupload'
import {
BeforeOperationHook,
BeforeValidateHook,
BeforeChangeHook,
AfterChangeHook,
BeforeReadHook,
AfterReadHook,
BeforeDeleteHook,
AfterDeleteHook,
AfterErrorHook,
BeforeLoginHook,
AfterLoginHook,
AfterForgotPasswordHook,
} from 'payload/dist/collections/config/types'
import { APIError } from 'payload/errors'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isUploadedFile(object: any): object is UploadedFile {
return 'data' in object;
}
let instance: AWS.S3 = null
const getCurrentS3Instance = (): AWS.S3 => {
if (instance === null) {
throw new APIError("S3 has not been initialized. Ensure you're calling `init()` with your S3 credentials before using these hooks.")
}
return instance
}
const options: FileOptions = {
bucket: null,
acl: 'private'
}
const getBucketName = (): string => {
if (options.bucket === null) {
throw new APIError("Bucket name has not been initialized. Ensure you're calling `init()` with your S3 credentials and file options before using these hooks.")
}
return options.bucket
}
export const uploadToS3: AfterChangeHook = async ({ doc, req }) => {
let uploadedFile: UploadedFile;
if (isUploadedFile(req.files.file)) {
uploadedFile = req.files.file
} else {
uploadedFile = req.files.file[0]
}
const s3 = getCurrentS3Instance()
const bucket = getBucketName()
await s3.putObject({
Bucket: bucket,
Key: String(doc.filename),
Body: uploadedFile.data,
ACL: 'public-read'
}).promise()
return doc
}
export const getS3Url: AfterReadHook = async ({ doc }) => {
return {
...doc,
filename: `https://${process.env.SPACES_NAME}.${process.env.SPACES_REGION}.cdn.digitaloceanspaces.com/${doc.filename}`
}
}
export const deleteFromS3: AfterDeleteHook = async ({ doc }) => {
const s3 = getCurrentS3Instance()
await s3.deleteObject({
Bucket: process.env.SPACES_NAME,
Key: String(doc.filename),
}).promise()
}
type PayloadCollectionHooks = {
beforeOperation?: BeforeOperationHook[];
beforeValidate?: BeforeValidateHook[];
beforeChange?: BeforeChangeHook[];
afterChange?: AfterChangeHook[];
beforeRead?: BeforeReadHook[];
afterRead?: AfterReadHook[];
beforeDelete?: BeforeDeleteHook[];
afterDelete?: AfterDeleteHook[];
afterError?: AfterErrorHook;
beforeLogin?: BeforeLoginHook[];
afterLogin?: AfterLoginHook[];
afterForgotPassword?: AfterForgotPasswordHook[];
}
type FileOptions = {
bucket: string;
acl?: 'private' | 'public-read';
}
export function init(s3Configuration: AWS.S3.ClientConfiguration, fileOptions: FileOptions): void {
instance = new AWS.S3(s3Configuration)
options.bucket = fileOptions.bucket
if (fileOptions.acl) {
options.acl = fileOptions.acl
}
}
export function withS3Storage(
s3Configuration: AWS.S3.ClientConfiguration,
fileOptions: FileOptions,
hooks?: PayloadCollectionHooks,
): PayloadCollectionHooks {
init(s3Configuration, fileOptions)
const {
afterChange = [],
afterRead = [],
afterDelete = [],
...rest
} = hooks || {}
return {
afterChange: [
uploadToS3,
...afterChange
],
afterRead: [
getS3Url,
...afterRead
],
afterDelete: [
deleteFromS3,
...afterDelete
],
...rest
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment