Last active
March 7, 2023 19:06
-
-
Save Avi-E-Koenig/bfe9385a5399056697a06db9e9d495ac to your computer and use it in GitHub Desktop.
Next.js Image Handler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {COLLECTIONS} from '@/const' | |
import {NextApiRequest, NextApiResponse} from 'next' | |
import fs from 'fs/promises' | |
import formidable, {Fields, File, Files} from 'formidable' | |
import path from 'path' | |
import checkAuthMiddleware from '../../../backend/middleware/checkIsAdmin' | |
import {v4 as uuid} from 'uuid' | |
const IMAGE_DIRECTORY = './public/assets/images/' | |
const ACCEPTED_FILE_TYPES = ['jpg', 'png'] | |
interface Image { | |
id: string | |
title: string | |
url: string | |
} | |
const parseForm = async ( | |
req: NextApiRequest | |
): Promise<[formidable.Fields, formidable.Files]> => | |
new Promise((resolve, reject) => | |
new formidable.IncomingForm().parse(req, (err, fields, files) => | |
err ? reject(err) : resolve([fields, files]) | |
) | |
) | |
const createImage = async ( | |
req: NextApiRequest, | |
res: NextApiResponse<any | Image | {error: string; statusCode?: number}> | |
) => { | |
try { | |
const [fields, files] = await parseForm(req) | |
const {title, collection} = fields | |
const {imageFile} = files as any | |
if (!Object.values(COLLECTIONS).includes(collection as string)) { | |
return res.status(400).json({error: 'Invalid collection name'}) | |
} | |
if (!title || !imageFile || !collection) { | |
return res.status(400).json({error: 'Invalid image'}) | |
} | |
const id = uuid() | |
const {originalFilename} = imageFile | |
if (!originalFilename) { | |
throw new Error('failed processing file name') | |
} | |
const extension = originalFilename.split('.').pop() | |
if (!extension || !ACCEPTED_FILE_TYPES.includes(extension)) { | |
return res.status(400).json({ | |
error: 'Invalid file type', | |
extension, | |
imageFile, | |
originalFilename, | |
}) | |
} | |
const filename = `${id}.${extension}` | |
const filePath = path.join(IMAGE_DIRECTORY, collection as string, filename) | |
const imageBuffer = await fs.readFile(imageFile.filepath) | |
await fs.writeFile(filePath, imageBuffer) | |
const newImage: Image = { | |
id, | |
title: filename, | |
url: filename, | |
} | |
return res.status(201).json(newImage) | |
} catch (error) { | |
console.error('Error creating new image', error) | |
return res | |
.status(500) | |
.json({error: `Internal server error: ${(error as Error).message}`}) | |
} | |
} | |
const getImages = async ( | |
req: NextApiRequest, | |
res: NextApiResponse<{images: Image[]} | {error: string}> | |
) => { | |
try { | |
const dirFiles = await fs.readdir(IMAGE_DIRECTORY) | |
const images: Image[] = [] | |
for (const filename of dirFiles) { | |
const id = filename.split('.')[0] | |
const title = id // in this example, the title is the same as the image ID | |
const url = filename | |
images.push({id, title, url}) | |
} | |
return res.status(200).json({images}) | |
} catch (error) { | |
console.error('Error getting images', error) | |
return res | |
.status(500) | |
.json({error: `Internal server error: ${(error as Error).message}`}) | |
} | |
} | |
const updateImage = async (req: NextApiRequest, res: NextApiResponse) => { | |
try { | |
const {id} = req.query | |
const [fields, files] = await parseForm(req) | |
const {title, collection} = fields | |
const {imageFile} = files as any | |
if (!Object.values(COLLECTIONS).includes(collection as string)) { | |
console.log('๐ ~ createImage ~ file: images.ts:23 ~ collection:', { | |
o: Object.values(COLLECTIONS), | |
collection, | |
}) | |
return res.status(400).json({error: 'Invalid collection name'}) | |
} | |
if (!title || !imageFile || !collection) { | |
return res.status(400).json({error: 'Invalid image'}) | |
} | |
const {originalFilename} = imageFile | |
if (!originalFilename) { | |
throw new Error('failed processing file name') | |
} | |
const extension = originalFilename.split('.').pop() | |
if (!extension || !ACCEPTED_FILE_TYPES.includes(extension)) { | |
return res.status(400).json({ | |
error: 'Invalid file type', | |
extension, | |
imageFile, | |
originalFilename, | |
}) | |
} | |
const filename = `${id}.` | |
const storedFiles = await fs.readdir(IMAGE_DIRECTORY) | |
const fileToUpdate = storedFiles.find((file) => file.includes(filename)) | |
if (!fileToUpdate) { | |
return res.status(400).json({ | |
error: 'Original file not found', | |
extension, | |
imageFile, | |
originalFilename, | |
}) | |
} | |
const newPath = path.join(IMAGE_DIRECTORY, fileToUpdate) | |
const newFilename = `${id}.${fileToUpdate.split('.').pop()}` | |
const imageBuffer = await fs.readFile(imageFile.filepath) | |
await fs.rename(newPath, imageBuffer) | |
const updatedImage: Image = { | |
id: id as string, | |
title: id as string, | |
url: newFilename, | |
} | |
return res.status(200).json(updatedImage) | |
} catch (error) { | |
console.error(`Error updating image with ID ${req.query.id}`, error) | |
return res | |
.status(500) | |
.json({error: `Internal server error: ${(error as Error).message}`}) | |
} | |
} | |
const deleteImage = async ( | |
req: NextApiRequest, | |
res: NextApiResponse<{message: string} | {error: string; statusCode?: number}> | |
) => { | |
try { | |
const {id, collection} = req.query | |
if (!Object.values(COLLECTIONS).includes(collection as string)) { | |
console.log('๐ ~ deleteImage ~ file: images.ts:134 ~ collection:', { | |
o: Object.values(COLLECTIONS), | |
collection, | |
}) | |
return res.status(400).json({error: 'Invalid collectionName'}) | |
} | |
const filename = `${id}.` | |
const files = await fs.readdir(IMAGE_DIRECTORY) | |
const fileToDelete = files.find((file) => file.includes(filename)) | |
if (!fileToDelete) { | |
return res.status(404).json({error: 'Image not found'}) | |
} | |
await fs.unlink(path.join(IMAGE_DIRECTORY, fileToDelete)) | |
return res | |
.status(200) | |
.json({message: `Image with ID ${id} deleted successfully`}) | |
} catch (error) { | |
console.error(`Error deleting image with ID ${req.query.id}`, error) | |
return res | |
.status(500) | |
.json({error: `Internal server error: ${(error as Error).message}`}) | |
} | |
} | |
// see: https://gist.github.com/Darkmift/29acc0c9a212486f8f44de4b58184838 | |
const checkedCreateImage = checkAuthMiddleware(createImage) | |
const checkedDeleteImage = checkAuthMiddleware(deleteImage) | |
const checkedUpdateImage = checkAuthMiddleware(updateImage) | |
export default function handler( | |
req: NextApiRequest, | |
res: NextApiResponse<any> | |
) { | |
const {method} = req | |
switch (method) { | |
case 'POST': | |
return checkedCreateImage(req, res) | |
case 'GET': | |
return getImages(req, res) | |
case 'PUT': | |
return checkedUpdateImage(req, res) | |
case 'DELETE': | |
return checkedDeleteImage(req, res) | |
default: | |
res.setHeader('Allow', ['POST', 'GET', 'DELETE']) | |
return res.status(405).json({ | |
error: `Method ${method} Not Allowed`, | |
}) | |
} | |
} | |
// VV conflicts with formidable parser VV | |
export const config = { | |
api: { | |
bodyParser: false, | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment