Skip to content

Commit

Permalink
fix(datatrakWeb): RN-1405: Fix file uploads from web not being saved …
Browse files Browse the repository at this point in the history
…correctly (#5822)

* WIP

* Handle uploads of files from web

* Tidy ups

* Undo testing change

* Fix build

* Update S3Client.ts

* Update S3Client.ts

---------

Co-authored-by: Andrew <vanbeekandrew@gmail.com>
Co-authored-by: Tom Caiger <caigertom@gmail.com>
  • Loading branch information
3 people authored Dec 16, 2024
1 parent f453e86 commit 9cbb126
Showing 1 changed file with 62 additions and 16 deletions.
78 changes: 62 additions & 16 deletions packages/server-utils/src/s3/S3Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,77 @@ export class S3Client {
return response.Body;
}

private async uploadPublicImage(fileName: string, buffer: Buffer, fileType: string) {
private async uploadPublicImage(fileName: string, buffer: Buffer, contentType: string) {
return this.upload(fileName, {
Body: buffer,
ACL: 'public-read',
ContentType: `image/${fileType}`,
ContentType: contentType,
ContentEncoding: 'base64',
});
}

private async uploadPrivateFile(fileName: string, readable: Buffer | string) {
private async uploadPrivateFile(
fileName: string,
readable: Buffer | string,
contentType?: string,
contentEncoding?: string,
) {
return this.upload(fileName, {
Body: readable,
ACL: 'bucket-owner-full-control',
ContentType: contentType,
ContentEncoding: contentEncoding,
});
}

private convertEncodedFileToBuffer(encodedFile: string) {
// remove the base64 prefix from the image. This handles svg and other image types
const encodedFileString = encodedFile.replace(new RegExp('(data:)(.*)(;base64,)'), '');

return Buffer.from(encodedFileString, 'base64');
}

private getContentTypeFromBase64(base64String: string) {
let fileType =
base64String.includes('data:') && base64String.includes(';base64')
? base64String.substring('data:'.length, base64String.indexOf(';base64'))
: 'image/png';

if (fileType === 'image/jpeg') {
fileType = 'image/jpg';
}

return fileType;
}

public async uploadFile(fileName: string, readable: Buffer | string) {
const s3FilePath = `${getS3UploadFilePath()}${fileName}`;
const s3UploadFolder = getS3UploadFilePath();
const s3FilePath = `${s3UploadFolder}${fileName}`;

const alreadyExists = await this.checkIfFileExists(s3FilePath);

// If the file already exists, throw an error
if (alreadyExists) {
throw new Error(`File ${s3FilePath} already exists on S3, overwrite is not allowed`);
}

return this.uploadPrivateFile(s3FilePath, readable);
// If the file is a url string, ignore it because it's not a file. This shouldn't happen but it's a safety check
if (typeof readable === 'string' && readable.includes(s3UploadFolder)) {
return;
}

let buffer = readable;
let contentType = undefined; // in cases where the file is directly loaded as a buffer, we don't have a content type and it will work without it
let contentEncoding = undefined;

// If the file is a base64 string, convert it to a buffer and get the file type. If we don't do this, the file will be uploaded as a binary file and just the text value will be saved and won't be able to be opened
if (typeof readable === 'string') {
buffer = this.convertEncodedFileToBuffer(readable);
contentType = this.getContentTypeFromBase64(readable);
contentEncoding = 'base64';
}

return this.uploadPrivateFile(s3FilePath, buffer, contentType, contentEncoding);
}

public async deleteFile(filePath: string) {
Expand All @@ -93,18 +139,14 @@ export class S3Client {

public async uploadImage(base64EncodedImage = '', fileId: string, allowOverwrite = false) {
const imageTypes = ['png', 'jpeg', 'jpg', 'gif', 'svg+xml'];
const encodedImageString = base64EncodedImage.replace(
new RegExp('(data:image)(.*)(;base64,)'),
'',
);
// remove the base64 prefix from the image. This handles svg and other image types
const buffer = Buffer.from(encodedImageString, 'base64');

// convert the base64 encoded image to a buffer
const buffer = this.convertEncodedFileToBuffer(base64EncodedImage);

const contentType = this.getContentTypeFromBase64(base64EncodedImage);

// use the file type from the image if it's available, otherwise default to png
let fileType =
base64EncodedImage.includes('data:image') && base64EncodedImage.includes(';base64')
? base64EncodedImage.substring('data:image/'.length, base64EncodedImage.indexOf(';base64'))
: 'png';
let fileType = contentType.split('/')[1] || 'png';

// If is not an image file type, e.g. a pdf, throw an error
if (!imageTypes.includes(fileType)) throw new Error(`File type ${fileType} is not supported`);
Expand All @@ -114,15 +156,19 @@ export class S3Client {
const fileExtension = fileType.replace('+xml', '');

const filePath = getS3ImageFilePath();

// If a fileId is provided, use it as the file name, otherwise generate a unique file name
const fileName = fileId
? `${filePath}${fileId}.${fileExtension}`
: `${filePath}${getUniqueFileName()}.${fileExtension}`;

// In some cases we want to allow overwriting of existing files
if (!allowOverwrite) {
if (await this.checkIfFileExists(fileName))
throw new Error(`File ${fileName} already exists on S3, overwrite is not allowed`);
}
return this.uploadPublicImage(fileName, buffer, fileType);

return this.uploadPublicImage(fileName, buffer, contentType);
}

public async downloadFile(fileName: string) {
Expand Down

0 comments on commit 9cbb126

Please sign in to comment.