Skip to content

Commit

Permalink
feat(core): integrate cloudflare r2 as a file upload (#4592)
Browse files Browse the repository at this point in the history
Co-authored-by: soyombo <e11iot.soko@gmail.com>
  • Loading branch information
orgilshdeee and soyombo-baterdene committed Sep 1, 2023
1 parent 7f28d23 commit c921114
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 1 deletion.
Expand Up @@ -404,6 +404,13 @@ class GeneralSettings extends React.Component<Props, State> {
</FormGroup>
</CollapseContent>

<CollapseContent title={__('Cloudflare R2')}>
{this.renderItem('CLOUDFLARE_ACCESS_KEY_ID')}
{this.renderItem('CLOUDFLARE_SECRET_ACCESS_KEY')}
{this.renderItem('CLOUDFLARE_ACCOUNT_ID')}
{this.renderItem('CLOUDFLARE_BUCKET_NAME')}
</CollapseContent>

<CollapseContent title="AWS S3">
<Info>
<a
Expand Down
159 changes: 159 additions & 0 deletions packages/core/src/data/utils.ts
Expand Up @@ -427,6 +427,95 @@ const createGCS = async (models?: IModels) => {
});
};

/*
* Create Google Cloud Storage instance
*/
const createCFR2 = async (models?: IModels) => {
const CLOUDFLARE_ACCOUNT_ID = await getConfig(
'CLOUDFLARE_ACCOUNT_ID',
'',
models
);
const CLOUDFLARE_ACCESS_KEY_ID = await getConfig(
'CLOUDFLARE_ACCESS_KEY_ID',
'',
models
);
const CLOUDFLARE_SECRET_ACCESS_KEY = await getConfig(
'CLOUDFLARE_SECRET_ACCESS_KEY',
'',
models
);
const CLOUDFLARE_ENDPOINT = `https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`;

if (!CLOUDFLARE_ACCESS_KEY_ID || !CLOUDFLARE_SECRET_ACCESS_KEY) {
throw new Error('Cloudflare R2 Credentials are not configured');
}

const options: {
endpoint?: string;
accessKeyId: string;
secretAccessKey: string;
signatureVersion: 'v4';
} = {
endpoint: CLOUDFLARE_ENDPOINT,
accessKeyId: CLOUDFLARE_ACCESS_KEY_ID,
secretAccessKey: CLOUDFLARE_SECRET_ACCESS_KEY,
signatureVersion: 'v4'
};

return new AWS.S3(options);
};

/*
* Save file to Cloudflare r2
*/

export const uploadFileCloudflare = async (
file: { name: string; path: string; type: string },
forcePrivate: boolean = false,
models?: IModels
): Promise<string> => {
const CLOUDFLARE_BUCKET = await getConfig(
'CLOUDFLARE_BUCKET_NAME',
'',
models
);
const IS_PUBLIC = forcePrivate
? false
: await getConfig('FILE_SYSTEM_PUBLIC', 'true', models);

// initialize r2
const r2 = await createCFR2(models);

// generate unique name
const fileName = `${Math.random()}${file.name.replace(/ /g, '')}`;

// read file
const buffer = await fs.readFileSync(file.path);

// upload to r2
const response: any = await new Promise((resolve, reject) => {
r2.upload(
{
ContentType: file.type,
Bucket: CLOUDFLARE_BUCKET,
Key: fileName,
Body: buffer,
ACL: IS_PUBLIC === 'true' ? 'public-read' : undefined
},
(err, res) => {
if (err) {
return reject(err);
}

return resolve(res);
}
);
});
return IS_PUBLIC === 'true' ? response.Location : fileName;
};

/*
* Save binary data to amazon s3
*/
Expand Down Expand Up @@ -477,6 +566,34 @@ export const uploadFileAWS = async (
return IS_PUBLIC === 'true' ? response.Location : fileName;
};

/*
* Delete file from Cloudflare r2
*/
export const deleteFileCloudflare = async (
fileName: string,
models?: IModels
) => {
const CLOUDFLARE_BUCKET = await getConfig(
'CLOUDFLARE_BUCKET_NAME',
'',
models
);

const params = { Bucket: CLOUDFLARE_BUCKET, Key: fileName };

// initialize r2
const r2 = await createCFR2(models);

return new Promise((resolve, reject) => {
r2.deleteObject(params, err => {
if (err) {
return reject(err);
}
return resolve('ok');
});
});
};

/*
* Delete file from amazon s3
*/
Expand Down Expand Up @@ -697,6 +814,40 @@ export const readFileRequest = async ({
});
}

if (UPLOAD_SERVICE_TYPE === 'CLOUDFLARE') {
const CLOUDFLARE_R2_BUCKET = await getConfig(
'CLOUDFLARE_BUCKET_NAME',
'',
models
);
const r2 = await createCFR2(models);

return new Promise((resolve, reject) => {
r2.getObject(
{
Bucket: CLOUDFLARE_R2_BUCKET,
Key: key
},
(error, response) => {
if (error) {
if (
error.code === 'NoSuchKey' &&
error.message.includes('key does not exist')
) {
debugBase(
`Error occurred when fetching r2 file with key: "${key}"`
);
}

return reject(error);
}

return resolve(response.Body);
}
);
});
}

if (UPLOAD_SERVICE_TYPE === 'local') {
return new Promise((resolve, reject) => {
fs.readFile(`${uploadsFolderPath}/${key}`, (error, response) => {
Expand Down Expand Up @@ -736,6 +887,10 @@ export const uploadFile = async (
nameOrLink = await uploadFileGCS(file, models);
}

if (UPLOAD_SERVICE_TYPE === 'CLOUDFLARE') {
nameOrLink = await uploadFileCloudflare(file, false, models);
}

if (UPLOAD_SERVICE_TYPE === 'local') {
nameOrLink = await uploadFileLocal(file);
}
Expand Down Expand Up @@ -771,6 +926,10 @@ export const deleteFile = async (
return deleteFileGCS(fileName, models);
}

if (UPLOAD_SERVICE_TYPE === 'CLOUDFLARE') {
return deleteFileCloudflare(fileName, models);
}

if (UPLOAD_SERVICE_TYPE === 'local') {
return deleteFileLocal(fileName);
}
Expand Down
7 changes: 6 additions & 1 deletion packages/ui-settings/src/general/constants.ts
Expand Up @@ -40,7 +40,8 @@ export const LANGUAGES = [
export const SERVICE_TYPES = [
{ label: __('Local'), value: 'local' },
{ label: __('Amazon Web Service'), value: 'AWS' },
{ label: __('Google Cloud Service'), value: 'GCS' }
{ label: __('Google Cloud Service'), value: 'GCS' },
{ label: __('Cloudflare R2'), value: 'CLOUDFLARE' }
];

export const FILE_SYSTEM_TYPES = [
Expand Down Expand Up @@ -73,6 +74,10 @@ export const KEY_LABELS = {
WIDGETS_UPLOAD_FILE_TYPES: 'Upload File Types of Widget',
UPLOAD_SERVICE_TYPE: 'Upload Service Type',
FILE_SYSTEM_PUBLIC: 'Bucket file system type',
CLOUDFLARE_ACCESS_KEY_ID: 'Cloudflare Access Key id',
CLOUDFLARE_SECRET_ACCESS_KEY: 'Cloudflare Secret Access Key',
CLOUDFLARE_BUCKET_NAME: 'Cloudflare R2 Bucket Name',
CLOUDFLARE_ACCOUNT_ID: 'Cloudflare Account id',
AWS_ACCESS_KEY_ID: 'AWS Access Key Id',
AWS_SECRET_ACCESS_KEY: 'AWS Secret Access Key',
AWS_BUCKET: 'AWS Bucket',
Expand Down

0 comments on commit c921114

Please sign in to comment.