mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
refactor image services
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"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}}",
|
||||
"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-react": "^1.0.0-beta.17",
|
||||
"@next/bundle-analyzer": "^14.0.3",
|
||||
"@prisma/client": "^5.6.0",
|
||||
"@prisma/client": "^5.7.0",
|
||||
"@react-email/components": "^0.0.11",
|
||||
"@react-email/render": "^0.0.9",
|
||||
"@react-email/tailwind": "^0.0.12",
|
||||
@@ -85,7 +85,7 @@
|
||||
"prettier-plugin-jsdoc": "^1.0.2",
|
||||
"prettier-plugin-tailwindcss": "^0.5.7",
|
||||
"prettier": "^3.0.0",
|
||||
"prisma": "^5.6.0",
|
||||
"prisma": "^5.7.0",
|
||||
"tailwindcss-animate": "^1.0.6",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"ts-node": "^10.9.1",
|
||||
|
||||
11
src/config/cloudinary/helpers/deleteImage.ts
Normal file
11
src/config/cloudinary/helpers/deleteImage.ts
Normal 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;
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
|
||||
CLOUDINARY_KEY,
|
||||
CLOUDINARY_SECRET,
|
||||
NODE_ENV,
|
||||
} from '../env';
|
||||
import CloudinaryStorage from './CloudinaryStorage';
|
||||
|
||||
@@ -14,6 +15,9 @@ cloudinary.config({
|
||||
});
|
||||
|
||||
/** 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 };
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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 { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import { UploadImagesRequest } from '../types';
|
||||
import { DeleteImageRequest, UploadImagesRequest } from '../types';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const processBeerImageData = async (
|
||||
@@ -16,11 +19,10 @@ export const processBeerImageData = async (
|
||||
throw new ServerError('No images uploaded', 400);
|
||||
}
|
||||
|
||||
const beerImages = await addBeerImageToDB({
|
||||
alt: body.alt,
|
||||
caption: body.caption,
|
||||
const beerImages = await addBeerImagesToDB({
|
||||
beerPostId: req.query.id,
|
||||
userId: user!.id,
|
||||
body,
|
||||
files,
|
||||
});
|
||||
|
||||
@@ -32,3 +34,18 @@ export const processBeerImageData = async (
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import addBreweryImageToDB from '@/services/images/brewery-image/addBreweryImageToDB';
|
||||
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import { addBreweryImagesToDB } from '@/services/images/brewery-image';
|
||||
import { UploadImagesRequest } from '../types';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
@@ -16,11 +17,10 @@ export const processBreweryImageData = async (
|
||||
throw new ServerError('No images uploaded', 400);
|
||||
}
|
||||
|
||||
const breweryImages = await addBreweryImageToDB({
|
||||
alt: body.alt,
|
||||
caption: body.caption,
|
||||
const breweryImages = await addBreweryImagesToDB({
|
||||
breweryPostId: req.query.id,
|
||||
userId: user!.id,
|
||||
body,
|
||||
files,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,3 +7,7 @@ export interface UploadImagesRequest extends UserExtendedNextApiRequest {
|
||||
query: { id: string };
|
||||
body: z.infer<typeof ImageMetadataValidationSchema>;
|
||||
}
|
||||
|
||||
export interface DeleteImageRequest extends UserExtendedNextApiRequest {
|
||||
query: { id: string };
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import ServerError from '@/config/util/ServerError';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiResponse, NextApiRequest } from 'next';
|
||||
import { z } from 'zod';
|
||||
import { LikeRequest } from '../types';
|
||||
import createBeerPostLike from '@/services/likes/beer-post-like/createBeerPostLike';
|
||||
import findBeerPostLikeById from '@/services/likes/beer-post-like/findBeerPostLikeById';
|
||||
import getBeerPostLikeCountByBeerPostId from '@/services/likes/beer-post-like/getBeerPostLikeCount';
|
||||
import removeBeerPostLikeById from '@/services/likes/beer-post-like/removeBeerPostLikeById';
|
||||
import { getBeerPostById } from '@/services/posts/beer-post';
|
||||
import { LikeRequest } from '../types';
|
||||
|
||||
export const sendBeerPostLikeRequest = async (
|
||||
req: LikeRequest,
|
||||
@@ -20,7 +20,7 @@ export const sendBeerPostLikeRequest = async (
|
||||
|
||||
const beer = await getBeerPostById({ beerPostId: id });
|
||||
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({
|
||||
|
||||
@@ -10,9 +10,9 @@ import getAllBeerStyles from '@/services/posts/beer-style-post/getAllBeerStyles'
|
||||
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
|
||||
import { getBeerPostsByBeerStyleIdService } from '@/services/posts/beer-post';
|
||||
import { CreateBeerStyleRequest, GetBeerStyleByIdRequest } from './types';
|
||||
import { GetAllPostsByConnectedPostId, GetAllPostsRequest } from '../types';
|
||||
import { getBeerPostsByBeerStyleIdService } from '@/services/posts/beer-post';
|
||||
|
||||
export const getBeerStyle = async (
|
||||
req: GetBeerStyleByIdRequest,
|
||||
|
||||
7
src/prisma/seed/clear/clearCloudinaryStorage.ts
Normal file
7
src/prisma/seed/clear/clearCloudinaryStorage.ts
Normal 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;
|
||||
@@ -1,7 +1,16 @@
|
||||
import logger from '../../../config/pino/logger';
|
||||
import clearCloudinaryStorage from './clearCloudinaryStorage';
|
||||
import clearDatabase from './clearDatabase';
|
||||
|
||||
clearDatabase().then(() => {
|
||||
logger.info('Database cleared');
|
||||
(async () => {
|
||||
await clearDatabase();
|
||||
await clearCloudinaryStorage();
|
||||
})()
|
||||
.then(() => {
|
||||
logger.info('Successfully cleared database and cloudinary storage.');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import createNewBeerStyleComments from './create/createNewBeerStyleComments';
|
||||
import createNewBeerStyleLikes from './create/createNewBeerStyleLikes';
|
||||
import createNewUserAvatars from './create/createNewUserAvatars';
|
||||
import createNewUserFollows from './create/createNewUserFollows';
|
||||
import clearCloudinaryStorage from './clear/clearCloudinaryStorage';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
@@ -27,6 +28,7 @@ import createNewUserFollows from './create/createNewUserFollows';
|
||||
|
||||
logger.info('Clearing database.');
|
||||
await clearDatabase();
|
||||
await clearCloudinaryStorage();
|
||||
logger.info('Database cleared successfully, preparing to seed.');
|
||||
|
||||
await createAdminUser();
|
||||
|
||||
@@ -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;
|
||||
87
src/services/images/beer-image/index.ts
Normal file
87
src/services/images/beer-image/index.ts
Normal 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;
|
||||
};
|
||||
19
src/services/images/beer-image/types/index.ts
Normal file
19
src/services/images/beer-image/types/index.ts
Normal 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>;
|
||||
@@ -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;
|
||||
86
src/services/images/brewery-image/index.ts
Normal file
86
src/services/images/brewery-image/index.ts
Normal 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;
|
||||
};
|
||||
19
src/services/images/brewery-image/types/index.ts
Normal file
19
src/services/images/brewery-image/types/index.ts
Normal 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>;
|
||||
Reference in New Issue
Block a user