refactor image services

This commit is contained in:
Aaron William Po
2023-12-13 09:49:31 -05:00
parent 685c86e0c1
commit fdbadb63dc
17 changed files with 285 additions and 99 deletions

View File

@@ -9,7 +9,7 @@
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"clear-db": "npx ts-node ./src/prisma/seed/clear/index.ts", "clear-db": "npx ts-node ./src/prisma/seed/clear/index.ts",
"format": "npx prettier . --write", "format": "npx prettier . --write; npx prisma format;",
"format-watch": "npx onchange \"**/*\" -- prettier --write --ignore-unknown {{changed}}", "format-watch": "npx onchange \"**/*\" -- prettier --write --ignore-unknown {{changed}}",
"seed": "npx --max-old-space-size=4096 ts-node ./src/prisma/seed/index.ts" "seed": "npx --max-old-space-size=4096 ts-node ./src/prisma/seed/index.ts"
}, },
@@ -22,7 +22,7 @@
"@mapbox/search-js-core": "^1.0.0-beta.17", "@mapbox/search-js-core": "^1.0.0-beta.17",
"@mapbox/search-js-react": "^1.0.0-beta.17", "@mapbox/search-js-react": "^1.0.0-beta.17",
"@next/bundle-analyzer": "^14.0.3", "@next/bundle-analyzer": "^14.0.3",
"@prisma/client": "^5.6.0", "@prisma/client": "^5.7.0",
"@react-email/components": "^0.0.11", "@react-email/components": "^0.0.11",
"@react-email/render": "^0.0.9", "@react-email/render": "^0.0.9",
"@react-email/tailwind": "^0.0.12", "@react-email/tailwind": "^0.0.12",
@@ -85,7 +85,7 @@
"prettier-plugin-jsdoc": "^1.0.2", "prettier-plugin-jsdoc": "^1.0.2",
"prettier-plugin-tailwindcss": "^0.5.7", "prettier-plugin-tailwindcss": "^0.5.7",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"prisma": "^5.6.0", "prisma": "^5.7.0",
"tailwindcss-animate": "^1.0.6", "tailwindcss-animate": "^1.0.6",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",

View File

@@ -0,0 +1,11 @@
import { cloudinary } from '..';
/**
* Deletes an image from Cloudinary.
*
* @param path - The cloudinary path of the image to be deleted.
* @returns A promise that resolves when the image is successfully deleted.
*/
const deleteImage = (path: string) => cloudinary.uploader.destroy(path);
export default deleteImage;

View File

@@ -4,6 +4,7 @@ import {
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
CLOUDINARY_KEY, CLOUDINARY_KEY,
CLOUDINARY_SECRET, CLOUDINARY_SECRET,
NODE_ENV,
} from '../env'; } from '../env';
import CloudinaryStorage from './CloudinaryStorage'; import CloudinaryStorage from './CloudinaryStorage';
@@ -14,6 +15,9 @@ cloudinary.config({
}); });
/** Cloudinary storage instance. */ /** Cloudinary storage instance. */
const storage = new CloudinaryStorage({ cloudinary, params: { folder: 'biergarten' } }); const storage = new CloudinaryStorage({
cloudinary,
params: { folder: NODE_ENV === 'production' ? 'biergarten' : 'biergarten-dev' },
});
export { cloudinary, storage }; export { cloudinary, storage };

View File

@@ -1,9 +1,12 @@
import ServerError from '@/config/util/ServerError'; import ServerError from '@/config/util/ServerError';
import addBeerImageToDB from '@/services/images/beer-image/addBeerImageToDB'; import {
addBeerImagesToDB,
deleteBeerImageFromDBAndStorage,
} from '@/services/images/beer-image';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { z } from 'zod'; import { z } from 'zod';
import { UploadImagesRequest } from '../types'; import { DeleteImageRequest, UploadImagesRequest } from '../types';
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export const processBeerImageData = async ( export const processBeerImageData = async (
@@ -16,11 +19,10 @@ export const processBeerImageData = async (
throw new ServerError('No images uploaded', 400); throw new ServerError('No images uploaded', 400);
} }
const beerImages = await addBeerImageToDB({ const beerImages = await addBeerImagesToDB({
alt: body.alt,
caption: body.caption,
beerPostId: req.query.id, beerPostId: req.query.id,
userId: user!.id, userId: user!.id,
body,
files, files,
}); });
@@ -32,3 +34,18 @@ export const processBeerImageData = async (
statusCode: 200, statusCode: 200,
}); });
}; };
export const deleteBeerImageData = async (
req: DeleteImageRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { id } = req.query;
await deleteBeerImageFromDBAndStorage({ beerImageId: id });
res.status(200).json({
success: true,
message: `Successfully deleted image with id ${id}`,
statusCode: 200,
});
};

View File

@@ -1,8 +1,9 @@
import ServerError from '@/config/util/ServerError'; import ServerError from '@/config/util/ServerError';
import addBreweryImageToDB from '@/services/images/brewery-image/addBreweryImageToDB';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { z } from 'zod'; import { z } from 'zod';
import { addBreweryImagesToDB } from '@/services/images/brewery-image';
import { UploadImagesRequest } from '../types'; import { UploadImagesRequest } from '../types';
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
@@ -16,11 +17,10 @@ export const processBreweryImageData = async (
throw new ServerError('No images uploaded', 400); throw new ServerError('No images uploaded', 400);
} }
const breweryImages = await addBreweryImageToDB({ const breweryImages = await addBreweryImagesToDB({
alt: body.alt,
caption: body.caption,
breweryPostId: req.query.id, breweryPostId: req.query.id,
userId: user!.id, userId: user!.id,
body,
files, files,
}); });

View File

@@ -7,3 +7,7 @@ export interface UploadImagesRequest extends UserExtendedNextApiRequest {
query: { id: string }; query: { id: string };
body: z.infer<typeof ImageMetadataValidationSchema>; body: z.infer<typeof ImageMetadataValidationSchema>;
} }
export interface DeleteImageRequest extends UserExtendedNextApiRequest {
query: { id: string };
}

View File

@@ -4,12 +4,12 @@ import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse, NextApiRequest } from 'next'; import { NextApiResponse, NextApiRequest } from 'next';
import { z } from 'zod'; import { z } from 'zod';
import { LikeRequest } from '../types';
import createBeerPostLike from '@/services/likes/beer-post-like/createBeerPostLike'; import createBeerPostLike from '@/services/likes/beer-post-like/createBeerPostLike';
import findBeerPostLikeById from '@/services/likes/beer-post-like/findBeerPostLikeById'; import findBeerPostLikeById from '@/services/likes/beer-post-like/findBeerPostLikeById';
import getBeerPostLikeCountByBeerPostId from '@/services/likes/beer-post-like/getBeerPostLikeCount'; import getBeerPostLikeCountByBeerPostId from '@/services/likes/beer-post-like/getBeerPostLikeCount';
import removeBeerPostLikeById from '@/services/likes/beer-post-like/removeBeerPostLikeById'; import removeBeerPostLikeById from '@/services/likes/beer-post-like/removeBeerPostLikeById';
import { getBeerPostById } from '@/services/posts/beer-post'; import { getBeerPostById } from '@/services/posts/beer-post';
import { LikeRequest } from '../types';
export const sendBeerPostLikeRequest = async ( export const sendBeerPostLikeRequest = async (
req: LikeRequest, req: LikeRequest,
@@ -20,7 +20,7 @@ export const sendBeerPostLikeRequest = async (
const beer = await getBeerPostById({ beerPostId: id }); const beer = await getBeerPostById({ beerPostId: id });
if (!beer) { if (!beer) {
throw new ServerError('Could not find a beer post with that id', 404); throw new ServerError('Could not find a beer post with that id.', 404);
} }
const alreadyLiked = await findBeerPostLikeById({ const alreadyLiked = await findBeerPostLikeById({

View File

@@ -10,9 +10,9 @@ import getAllBeerStyles from '@/services/posts/beer-style-post/getAllBeerStyles'
import ServerError from '@/config/util/ServerError'; import ServerError from '@/config/util/ServerError';
import { getBeerPostsByBeerStyleIdService } from '@/services/posts/beer-post';
import { CreateBeerStyleRequest, GetBeerStyleByIdRequest } from './types'; import { CreateBeerStyleRequest, GetBeerStyleByIdRequest } from './types';
import { GetAllPostsByConnectedPostId, GetAllPostsRequest } from '../types'; import { GetAllPostsByConnectedPostId, GetAllPostsRequest } from '../types';
import { getBeerPostsByBeerStyleIdService } from '@/services/posts/beer-post';
export const getBeerStyle = async ( export const getBeerStyle = async (
req: GetBeerStyleByIdRequest, req: GetBeerStyleByIdRequest,

View File

@@ -0,0 +1,7 @@
import { cloudinary } from '../../../config/cloudinary';
const clearCloudinaryStorage = async () => {
await cloudinary.api.delete_resources_by_prefix('biergarten-dev/');
};
export default clearCloudinaryStorage;

View File

@@ -1,7 +1,16 @@
import logger from '../../../config/pino/logger'; import logger from '../../../config/pino/logger';
import clearCloudinaryStorage from './clearCloudinaryStorage';
import clearDatabase from './clearDatabase'; import clearDatabase from './clearDatabase';
clearDatabase().then(() => { (async () => {
logger.info('Database cleared'); await clearDatabase();
await clearCloudinaryStorage();
})()
.then(() => {
logger.info('Successfully cleared database and cloudinary storage.');
process.exit(0); process.exit(0);
})
.catch((err) => {
logger.error(err);
process.exit(1);
}); });

View File

@@ -20,6 +20,7 @@ import createNewBeerStyleComments from './create/createNewBeerStyleComments';
import createNewBeerStyleLikes from './create/createNewBeerStyleLikes'; import createNewBeerStyleLikes from './create/createNewBeerStyleLikes';
import createNewUserAvatars from './create/createNewUserAvatars'; import createNewUserAvatars from './create/createNewUserAvatars';
import createNewUserFollows from './create/createNewUserFollows'; import createNewUserFollows from './create/createNewUserFollows';
import clearCloudinaryStorage from './clear/clearCloudinaryStorage';
(async () => { (async () => {
try { try {
@@ -27,6 +28,7 @@ import createNewUserFollows from './create/createNewUserFollows';
logger.info('Clearing database.'); logger.info('Clearing database.');
await clearDatabase(); await clearDatabase();
await clearCloudinaryStorage();
logger.info('Database cleared successfully, preparing to seed.'); logger.info('Database cleared successfully, preparing to seed.');
await createAdminUser(); await createAdminUser();

View File

@@ -1,40 +0,0 @@
import DBClient from '@/prisma/DBClient';
import { BeerImage } from '@prisma/client';
import { z } from 'zod';
import ImageMetadataValidationSchema from '../../schema/ImageSchema/ImageMetadataValidationSchema';
interface ProcessImageDataArgs {
files: Express.Multer.File[];
alt: z.infer<typeof ImageMetadataValidationSchema>['alt'];
caption: z.infer<typeof ImageMetadataValidationSchema>['caption'];
beerPostId: string;
userId: string;
}
const addBeerImageToDB = ({
alt,
caption,
files,
beerPostId,
userId,
}: ProcessImageDataArgs) => {
const beerImagePromises: Promise<BeerImage>[] = [];
files.forEach((file) => {
beerImagePromises.push(
DBClient.instance.beerImage.create({
data: {
alt,
caption,
postedBy: { connect: { id: userId } },
beerPost: { connect: { id: beerPostId } },
path: file.path,
},
}),
);
});
return Promise.all(beerImagePromises);
};
export default addBeerImageToDB;

View File

@@ -0,0 +1,87 @@
import DBClient from '@/prisma/DBClient';
import { BeerImage } from '@prisma/client';
import { cloudinary } from '@/config/cloudinary';
import {
AddBeerImagesToDB,
DeleteBeerImageFromDBAndStorage,
UpdateBeerImageMetadata,
} from './types';
/**
* Adds beer images to the database.
*
* @param options - The options for adding beer images.
* @param options.body - The body of the request.
* @param options.body.alt - The alt text for the beer image.
* @param options.body.caption - The caption for the beer image.
* @param options.files - The array of files to be uploaded as beer images.
* @param options.beerPostId - The ID of the beer post.
* @param options.userId - The ID of the user.
* @returns A promise that resolves to an array of created beer images.
*/
export const addBeerImagesToDB: AddBeerImagesToDB = ({
body,
files,
beerPostId,
userId,
}) => {
const beerImagePromises: Promise<BeerImage>[] = [];
const { alt, caption } = body;
files.forEach((file) => {
beerImagePromises.push(
DBClient.instance.beerImage.create({
data: {
alt,
caption,
postedBy: { connect: { id: userId } },
beerPost: { connect: { id: beerPostId } },
path: file.path,
},
}),
);
});
return Promise.all(beerImagePromises);
};
/**
* Deletes a beer image from the database and storage.
*
* @param options - The options for deleting a beer image.
* @param options.beerImageId - The ID of the beer image.
*/
export const deleteBeerImageFromDBAndStorage: DeleteBeerImageFromDBAndStorage = async ({
beerImageId,
}) => {
const deleted = await DBClient.instance.beerImage.delete({
where: { id: beerImageId },
select: { path: true, id: true },
});
const { path } = deleted;
await cloudinary.uploader.destroy(path);
};
/**
* Updates the beer image metadata in the database.
*
* @param options - The options for updating the beer image metadata.
* @param options.beerImageId - The ID of the beer image.
* @param options.body - The body of the request containing the alt and caption.
* @param options.body.alt - The alt text for the beer image.
* @param options.body.caption - The caption for the beer image.
* @returns A promise that resolves to the updated beer image.
*/
export const updateBeerImageMetadata: UpdateBeerImageMetadata = async ({
beerImageId,
body,
}) => {
const { alt, caption } = body;
const updated = await DBClient.instance.beerImage.update({
where: { id: beerImageId },
data: { alt, caption },
});
return updated;
};

View File

@@ -0,0 +1,19 @@
import ImageMetadataValidationSchema from '@/services/schema/ImageSchema/ImageMetadataValidationSchema';
import { BeerImage } from '@prisma/client';
import { z } from 'zod';
export type AddBeerImagesToDB = (args: {
files: Express.Multer.File[];
body: z.infer<typeof ImageMetadataValidationSchema>;
beerPostId: string;
userId: string;
}) => Promise<BeerImage[]>;
export type DeleteBeerImageFromDBAndStorage = (args: {
beerImageId: string;
}) => Promise<void>;
export type UpdateBeerImageMetadata = (args: {
beerImageId: string;
body: z.infer<typeof ImageMetadataValidationSchema>;
}) => Promise<BeerImage>;

View File

@@ -1,39 +0,0 @@
import DBClient from '@/prisma/DBClient';
import { BreweryImage } from '@prisma/client';
import { z } from 'zod';
import ImageMetadataValidationSchema from '../../schema/ImageSchema/ImageMetadataValidationSchema';
interface ProcessImageDataArgs {
files: Express.Multer.File[];
alt: z.infer<typeof ImageMetadataValidationSchema>['alt'];
caption: z.infer<typeof ImageMetadataValidationSchema>['caption'];
breweryPostId: string;
userId: string;
}
const addBreweryImageToDB = ({
alt,
caption,
files,
breweryPostId,
userId,
}: ProcessImageDataArgs) => {
const breweryImagePromises: Promise<BreweryImage>[] = [];
files.forEach((file) => {
breweryImagePromises.push(
DBClient.instance.breweryImage.create({
data: {
alt,
caption,
postedBy: { connect: { id: userId } },
breweryPost: { connect: { id: breweryPostId } },
path: file.path,
},
}),
);
});
return Promise.all(breweryImagePromises);
};
export default addBreweryImageToDB;

View File

@@ -0,0 +1,86 @@
import DBClient from '@/prisma/DBClient';
import { BreweryImage } from '@prisma/client';
import { cloudinary } from '@/config/cloudinary';
import {
AddBreweryImagesToDB,
DeleteBreweryImageFromDBAndStorage,
UpdateBreweryImageMetadata,
} from './types';
/**
* Adds brewery images to the database.
*
* @param options - The options for adding brewery images.
* @param options.body - The body of the request containing the alt and caption.
* @param options.body.alt - The alt text for the brewery image.
* @param options.body.caption - The caption for the brewery image.
* @param options.files - The array of files to be uploaded as brewery images.
* @param options.breweryPostId - The ID of the brewery post.
* @param options.userId - The ID of the user adding the images.
* @returns A promise that resolves to an array of created brewery images.
*/
export const addBreweryImagesToDB: AddBreweryImagesToDB = ({
body,
files,
breweryPostId,
userId,
}) => {
const breweryImagePromises: Promise<BreweryImage>[] = [];
const { alt, caption } = body;
files.forEach((file) => {
breweryImagePromises.push(
DBClient.instance.breweryImage.create({
data: {
alt,
caption,
postedBy: { connect: { id: userId } },
breweryPost: { connect: { id: breweryPostId } },
path: file.path,
},
}),
);
});
return Promise.all(breweryImagePromises);
};
/**
* Deletes a brewery image from the database and storage.
*
* @param options - The options for deleting a brewery image.
* @param options.breweryImageId - The ID of the brewery image.
*/
export const deleteBreweryImageFromDBAndStorage: DeleteBreweryImageFromDBAndStorage =
async ({ breweryImageId }) => {
const deleted = await DBClient.instance.breweryImage.delete({
where: { id: breweryImageId },
select: { path: true, id: true },
});
const { path } = deleted;
await cloudinary.uploader.destroy(path);
};
/**
* Updates the brewery image metadata in the database.
*
* @param options - The options for updating the brewery image metadata.
* @param options.breweryImageId - The ID of the brewery image.
* @param options.body - The body of the request containing the alt and caption.
* @param options.body.alt - The alt text for the brewery image.
* @param options.body.caption - The caption for the brewery image.
* @returns A promise that resolves to the updated brewery image.
*/
export const updateBreweryImageMetadata: UpdateBreweryImageMetadata = async ({
breweryImageId,
body,
}) => {
const { alt, caption } = body;
const updated = await DBClient.instance.breweryImage.update({
where: { id: breweryImageId },
data: { alt, caption },
});
return updated;
};

View File

@@ -0,0 +1,19 @@
import ImageMetadataValidationSchema from '@/services/schema/ImageSchema/ImageMetadataValidationSchema';
import { BreweryImage } from '@prisma/client';
import { z } from 'zod';
export type AddBreweryImagesToDB = (args: {
files: Express.Multer.File[];
body: z.infer<typeof ImageMetadataValidationSchema>;
breweryPostId: string;
userId: string;
}) => Promise<BreweryImage[]>;
export type DeleteBreweryImageFromDBAndStorage = (args: {
breweryImageId: string;
}) => Promise<void>;
export type UpdateBreweryImageMetadata = (args: {
breweryImageId: string;
body: z.infer<typeof ImageMetadataValidationSchema>;
}) => Promise<BreweryImage>;