Skip to content

Commit

Permalink
Merge pull request #72 from middlewarehq/persistance
Browse files Browse the repository at this point in the history
Add Card Persistence Mechanism
  • Loading branch information
samad-yar-khan authored Dec 11, 2023
2 parents 7b47b7a + 6ccc07d commit c7d98d0
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 6 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@sentry/nextjs": "^7.86.0",
"@vercel/og": "^0.5.20",
"archiver": "^6.0.1",
"aws-sdk": "^2.1515.0",
"axios": "^1.6.2",
"chalk": "^5.3.0",
"date-fns": "^2.30.0",
Expand Down
52 changes: 52 additions & 0 deletions src/api-helpers/persistance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ImageFile } from '@/types/images';
import {
deleteS3Directory,
fetchImagesFromS3Directory,
uploadImagesToS3
} from '../utils/persistence/s3';
import {
deleteLocalDirectory,
fetchImagesFromLocalDirectory,
saveImagesToLocalDirectory
} from '../utils/persistence/file-system';

const awsCredentialExits =
process.env.AWS_ACCESS_KEY_ID &&
process.env.AWS_SECRET_ACCESS_KEY &&
process.env.AWS_REGION;

const bucketName = process.env.UNWRAPPED_PERSISTENCE_BUCKET_NAME;

export const saveCards = async (
userLogin: string,
imageFiles: ImageFile[]
): Promise<void> => {
if (awsCredentialExits && bucketName) {
await uploadImagesToS3(bucketName, userLogin, imageFiles);
} else {
await saveImagesToLocalDirectory(
`${process.cwd()}/unwrapped-cards/${userLogin}/`,
imageFiles
);
}
};

export const fetchCards = async (userLogin: string): Promise<ImageFile[]> => {
if (awsCredentialExits && bucketName) {
return await fetchImagesFromS3Directory(bucketName, userLogin);
} else {
return await fetchImagesFromLocalDirectory(
`${process.cwd()}/unwrapped-cards/${userLogin}/`
);
}
};

export const deleteCards = async (userLogin: string): Promise<void> => {
if (awsCredentialExits && bucketName) {
await deleteS3Directory(bucketName, userLogin);
} else {
await deleteLocalDirectory(
`${process.cwd()}/unwrapped-cards/${userLogin}/`
);
}
};
83 changes: 83 additions & 0 deletions src/utils/persistence/file-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ImageFile } from '@/types/images';
import fs from 'fs/promises';
import path from 'path';

async function directoryExists(localDirectory: string): Promise<boolean> {
try {
const stats = await fs.stat(localDirectory);
return stats.isDirectory();
} catch (error) {
return false;
}
}

async function ensureDirectoryExists(localDirectory: string) {
try {
await fs.mkdir(localDirectory, { recursive: true });
console.log(`Directory created: ${localDirectory}`);
} catch (error: any) {
if (error.code === 'EEXIST') {
console.log(`Directory already exists: ${localDirectory}`);
} else {
console.error(`Error creating directory: ${error.message}`);
}
}
}

export const saveImagesToLocalDirectory = async (
localDirectory: string,
imageFiles: ImageFile[]
): Promise<void> => {
try {
await ensureDirectoryExists(localDirectory);

const savePromises = imageFiles.map(async (imageFile) => {
const filePath = path.join(localDirectory, imageFile.fileName);
await fs.writeFile(filePath, imageFile.data);
});

await Promise.all(savePromises);
} catch (error: any) {
throw new Error(
`Error fetching images from local directory: ${error.message}`
);
}
};

export const fetchImagesFromLocalDirectory = async (
localDirectory: string
): Promise<ImageFile[]> => {
try {
await ensureDirectoryExists(localDirectory);
const files = await fs.readdir(localDirectory);
const images: ImageFile[] = await Promise.all(
files.map(async (fileName) => {
const filePath = path.join(localDirectory, fileName);
const data = await fs.readFile(filePath);
return {
fileName,
data
};
})
);

return images;
} catch (error: any) {
throw new Error(
`Error fetching images from local directory: ${error.message}`
);
}
};

export const deleteLocalDirectory = async (
localDirectory: string
): Promise<void> => {
try {
const directory_exists = await directoryExists(localDirectory);
if (!directory_exists) return;

await fs.rmdir(localDirectory, { recursive: true });
} catch (error: any) {
throw new Error(`Error deleting local directory: ${error.message}`);
}
};
88 changes: 88 additions & 0 deletions src/utils/persistence/s3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { ImageFile } from '@/types/images';
import { S3 } from 'aws-sdk';

const s3 = new S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION
});

export const uploadImagesToS3 = async (
bucketName: string,
prefix: string,
imageFiles: ImageFile[]
): Promise<void> => {
const uploadPromises = imageFiles.map(async (imageFile) => {
const uploadParams = {
Bucket: bucketName,
Key: `${prefix}/${imageFile.fileName}`,
Body: imageFile.data,
Prefix: prefix
};

try {
await s3.upload(uploadParams).promise();
} catch (error: any) {
throw new Error(`Error uploading image to S3: ${error.message}`);
}
});

await Promise.all(uploadPromises);
};

export const fetchImagesFromS3Directory = async (
bucketName: string,
directory: string
): Promise<ImageFile[]> => {
const listObjectsParams = {
Bucket: bucketName,
Prefix: directory
};

try {
const data = await s3.listObjectsV2(listObjectsParams).promise();

const images: ImageFile[] = await Promise.all(
(data.Contents || []).map(async (item) => {
const imageBuffer = await s3
.getObject({ Bucket: bucketName, Key: item.Key || '' })
.promise();
return {
fileName: item.Key || '',
data: imageBuffer.Body as Buffer
};
})
);

return images;
} catch (error: any) {
throw new Error(`Error fetching images from S3: ${error.message}`);
}
};

export const deleteS3Directory = async (
bucketName: string,
directory: string
): Promise<void> => {
const listObjectsParams = {
Bucket: bucketName,
Prefix: directory
};

try {
const data = await s3.listObjectsV2(listObjectsParams).promise();

if (data.Contents && data.Contents.length > 0) {
const deleteObjectsParams = {
Bucket: bucketName,
Delete: {
Objects: data.Contents.map((item) => ({ Key: item.Key || '' }))
}
};

await s3.deleteObjects(deleteObjectsParams).promise();
}
} catch (error: any) {
throw new Error(`Error deleting directory from S3: ${error.message}`);
}
};
Loading

0 comments on commit c7d98d0

Please sign in to comment.