postiz/libraries/nestjs-libraries/src/upload/r2.uploader.ts

219 lines
5.7 KiB
TypeScript

import {
UploadPartCommand, S3Client, ListPartsCommand, CreateMultipartUploadCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, PutObjectCommand
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { Request, Response } from 'express';
import crypto from 'crypto';
import path from 'path';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
const { CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_ACCESS_KEY, CLOUDFLARE_SECRET_ACCESS_KEY, CLOUDFLARE_BUCKETNAME, CLOUDFLARE_BUCKET_URL } =
process.env;
const R2 = new S3Client({
region: 'auto',
endpoint: `https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: CLOUDFLARE_ACCESS_KEY!,
secretAccessKey: CLOUDFLARE_SECRET_ACCESS_KEY!,
},
});
// Function to generate a random string
function generateRandomString() {
return makeId(20);
}
export default async function handleR2Upload(
endpoint: string,
req: Request,
res: Response
) {
switch (endpoint) {
case 'create-multipart-upload':
return createMultipartUpload(req, res);
case 'prepare-upload-parts':
return prepareUploadParts(req, res);
case 'complete-multipart-upload':
return completeMultipartUpload(req, res);
case 'list-parts':
return listParts(req, res);
case 'abort-multipart-upload':
return abortMultipartUpload(req, res);
case 'sign-part':
return signPart(req, res);
}
return res.status(404).end();
}
export async function simpleUpload(data: Buffer, originalFilename: string, contentType: string) {
const fileExtension = path.extname(originalFilename); // Extract extension
const randomFilename = generateRandomString() + fileExtension; // Append extension
const params = {
Bucket: CLOUDFLARE_BUCKETNAME,
Key: randomFilename,
Body: data,
ContentType: contentType,
};
const command = new PutObjectCommand({ ...params });
await R2.send(command);
return CLOUDFLARE_BUCKET_URL + '/' + randomFilename;
}
export async function createMultipartUpload(
req: Request,
res: Response
) {
const { file, fileHash, contentType } = req.body;
const fileExtension = path.extname(file.name); // Extract extension
const randomFilename = generateRandomString() + fileExtension; // Append extension
try {
const params = {
Bucket: CLOUDFLARE_BUCKETNAME,
Key: `${randomFilename}`,
ContentType: contentType,
Metadata: {
'x-amz-meta-file-hash': fileHash,
},
};
const command = new CreateMultipartUploadCommand({ ...params });
const response = await R2.send(command);
return res.status(200).json({
uploadId: response.UploadId,
key: response.Key,
});
} catch (err) {
console.log('Error', err);
return res.status(500).json({ source: { status: 500 } });
}
}
export async function prepareUploadParts(
req: Request,
res: Response
) {
const { partData } = req.body;
const parts = partData.parts;
const response = {
presignedUrls: {},
};
for (const part of parts) {
try {
const params = {
Bucket: CLOUDFLARE_BUCKETNAME,
Key: partData.key,
PartNumber: part.number,
UploadId: partData.uploadId,
};
const command = new UploadPartCommand({ ...params });
const url = await getSignedUrl(R2, command, { expiresIn: 3600 });
// @ts-ignore
response.presignedUrls[part.number] = url;
} catch (err) {
console.log('Error', err);
return res.status(500).json(err);
}
}
return res.status(200).json(response);
}
export async function listParts(req: Request, res: Response) {
const { key, uploadId } = req.body;
try {
const params = {
Bucket: CLOUDFLARE_BUCKETNAME,
Key: key,
UploadId: uploadId,
};
const command = new ListPartsCommand({ ...params });
const response = await R2.send(command);
return res.status(200).json(response['Parts']);
} catch (err) {
console.log('Error', err);
return res.status(500).json(err);
}
}
export async function completeMultipartUpload(
req: Request,
res: Response
) {
const { key, uploadId, parts } = req.body;
try {
const params = {
Bucket: CLOUDFLARE_BUCKETNAME,
Key: key,
UploadId: uploadId,
MultipartUpload: { Parts: parts },
};
const command = new CompleteMultipartUploadCommand({
Bucket: CLOUDFLARE_BUCKETNAME,
Key: key,
UploadId: uploadId,
MultipartUpload: { Parts: parts },
});
const response = await R2.send(command);
response.Location = process.env.CLOUDFLARE_BUCKET_URL + '/' + response?.Location?.split('/').at(-1);
return response;
} catch (err) {
console.log('Error', err);
return res.status(500).json(err);
}
}
export async function abortMultipartUpload(
req: Request,
res: Response
) {
const { key, uploadId } = req.body;
try {
const params = {
Bucket: CLOUDFLARE_BUCKETNAME,
Key: key,
UploadId: uploadId,
};
const command = new AbortMultipartUploadCommand({ ...params });
const response = await R2.send(command);
return res.status(200).json(response);
} catch (err) {
console.log('Error', err);
return res.status(500).json(err);
}
}
export async function signPart(req: Request, res: Response) {
const { key, uploadId } = req.body;
const partNumber = parseInt(req.body.partNumber);
const params = {
Bucket: CLOUDFLARE_BUCKETNAME,
Key: key,
PartNumber: partNumber,
UploadId: uploadId,
Expires: 3600
};
const command = new UploadPartCommand({ ...params });
const url = await getSignedUrl(R2, command, { expiresIn: 3600 });
return res.status(200).json({
url: url,
});
}