From b261c8c4a9a85843a295cc184a6ffb1ba44d8e5f Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 20 Mar 2024 00:59:06 +0200 Subject: [PATCH] Extract API endpoints for images --- public/scripts/extensions/gallery/index.js | 4 +- public/scripts/utils.js | 2 +- server.js | 93 ++------------------- src/endpoints/images.js | 94 ++++++++++++++++++++++ 4 files changed, 104 insertions(+), 89 deletions(-) create mode 100644 src/endpoints/images.js diff --git a/public/scripts/extensions/gallery/index.js b/public/scripts/extensions/gallery/index.js index 815170897..06d62d0a4 100644 --- a/public/scripts/extensions/gallery/index.js +++ b/public/scripts/extensions/gallery/index.js @@ -29,7 +29,7 @@ let galleryMaxRows = 3; * @returns {Promise} - Resolves with an array of gallery item objects, rejects on error. */ async function getGalleryItems(url) { - const response = await fetch(`/listimgfiles/${url}`, { + const response = await fetch(`/api/images/list/${url}`, { method: 'POST', headers: getRequestHeaders(), }); @@ -201,7 +201,7 @@ async function uploadFile(file, url) { 'Content-Type': 'application/json', }); - const response = await fetch('/uploadimage', { + const response = await fetch('/api/images/upload', { method: 'POST', headers: headers, body: JSON.stringify(payload), diff --git a/public/scripts/utils.js b/public/scripts/utils.js index c7d001761..4f825d4b2 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -996,7 +996,7 @@ export async function saveBase64AsFile(base64Data, characterName, filename = '', }; // Send the data URL to your backend using fetch - const response = await fetch('/uploadimage', { + const response = await fetch('/api/images/upload', { method: 'POST', body: JSON.stringify(requestBody), headers: { diff --git a/server.js b/server.js index a9d425baf..2a6bfc713 100644 --- a/server.js +++ b/server.js @@ -42,9 +42,6 @@ const { getVersion, getConfigValue, color, - clientRelativePath, - removeFileExtension, - getImages, forwardFetchResponse, } = require('./src/util'); const { ensureThumbnailCache } = require('./src/endpoints/thumbnails'); @@ -244,89 +241,6 @@ app.post('/savemovingui', jsonParser, (request, response) => { return response.sendStatus(200); }); -/** - * Ensure the directory for the provided file path exists. - * If not, it will recursively create the directory. - * - * @param {string} filePath - The full path of the file for which the directory should be ensured. - */ -function ensureDirectoryExistence(filePath) { - const dirname = path.dirname(filePath); - if (fs.existsSync(dirname)) { - return true; - } - ensureDirectoryExistence(dirname); - fs.mkdirSync(dirname); -} - -/** - * Endpoint to handle image uploads. - * The image should be provided in the request body in base64 format. - * Optionally, a character name can be provided to save the image in a sub-folder. - * - * @route POST /uploadimage - * @param {Object} request.body - The request payload. - * @param {string} request.body.image - The base64 encoded image data. - * @param {string} [request.body.ch_name] - Optional character name to determine the sub-directory. - * @returns {Object} response - The response object containing the path where the image was saved. - */ -app.post('/uploadimage', jsonParser, async (request, response) => { - // Check for image data - if (!request.body || !request.body.image) { - return response.status(400).send({ error: 'No image data provided' }); - } - - try { - // Extracting the base64 data and the image format - const splitParts = request.body.image.split(','); - const format = splitParts[0].split(';')[0].split('/')[1]; - const base64Data = splitParts[1]; - const validFormat = ['png', 'jpg', 'webp', 'jpeg', 'gif'].includes(format); - if (!validFormat) { - return response.status(400).send({ error: 'Invalid image format' }); - } - - // Constructing filename and path - let filename; - if (request.body.filename) { - filename = `${removeFileExtension(request.body.filename)}.${format}`; - } else { - filename = `${Date.now()}.${format}`; - } - - // if character is defined, save to a sub folder for that character - let pathToNewFile = path.join(DIRECTORIES.userImages, sanitize(filename)); - if (request.body.ch_name) { - pathToNewFile = path.join(DIRECTORIES.userImages, sanitize(request.body.ch_name), sanitize(filename)); - } - - ensureDirectoryExistence(pathToNewFile); - const imageBuffer = Buffer.from(base64Data, 'base64'); - await fs.promises.writeFile(pathToNewFile, imageBuffer); - response.send({ path: clientRelativePath(pathToNewFile) }); - } catch (error) { - console.log(error); - response.status(500).send({ error: 'Failed to save the image' }); - } -}); - -app.post('/listimgfiles/:folder', (req, res) => { - const directoryPath = path.join(process.cwd(), 'public/user/images/', sanitize(req.params.folder)); - - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath, { recursive: true }); - } - - try { - const images = getImages(directoryPath); - return res.send(images); - } catch (error) { - console.error(error); - return res.status(500).send({ error: 'Unable to retrieve files' }); - } -}); - - function cleanUploads() { try { if (fs.existsSync(UPLOADS_PATH)) { @@ -422,6 +336,13 @@ redirect('/uploaduseravatar', '/api/avatars/upload'); redirect('/deletequickreply', '/api/quick-replies/delete'); redirect('/savequickreply', '/api/quick-replies/save'); +// Redirect deprecated image endpoints +redirect('/uploadimage', '/api/images/upload'); +redirect('/listimgfiles/:folder', '/api/images/list/:folder'); + +// Image management +app.use('/api/images', require('./src/endpoints/images').router); + // Quick reply management app.use('/api/quick-replies', require('./src/endpoints/quick-replies').router); diff --git a/src/endpoints/images.js b/src/endpoints/images.js new file mode 100644 index 000000000..e0f458c35 --- /dev/null +++ b/src/endpoints/images.js @@ -0,0 +1,94 @@ +const fs = require('fs'); +const path = require('path'); +const express = require('express'); +const sanitize = require('sanitize-filename'); + +const { jsonParser } = require('../express-common'); +const { DIRECTORIES } = require('../constants'); +const { clientRelativePath, removeFileExtension, getImages } = require('../util'); + +/** + * Ensure the directory for the provided file path exists. + * If not, it will recursively create the directory. + * + * @param {string} filePath - The full path of the file for which the directory should be ensured. + */ +function ensureDirectoryExistence(filePath) { + const dirname = path.dirname(filePath); + if (fs.existsSync(dirname)) { + return true; + } + ensureDirectoryExistence(dirname); + fs.mkdirSync(dirname); +} + +const router = express.Router(); + +/** + * Endpoint to handle image uploads. + * The image should be provided in the request body in base64 format. + * Optionally, a character name can be provided to save the image in a sub-folder. + * + * @route POST /api/images/upload + * @param {Object} request.body - The request payload. + * @param {string} request.body.image - The base64 encoded image data. + * @param {string} [request.body.ch_name] - Optional character name to determine the sub-directory. + * @returns {Object} response - The response object containing the path where the image was saved. + */ +router.post('/upload', jsonParser, async (request, response) => { + // Check for image data + if (!request.body || !request.body.image) { + return response.status(400).send({ error: 'No image data provided' }); + } + + try { + // Extracting the base64 data and the image format + const splitParts = request.body.image.split(','); + const format = splitParts[0].split(';')[0].split('/')[1]; + const base64Data = splitParts[1]; + const validFormat = ['png', 'jpg', 'webp', 'jpeg', 'gif'].includes(format); + if (!validFormat) { + return response.status(400).send({ error: 'Invalid image format' }); + } + + // Constructing filename and path + let filename; + if (request.body.filename) { + filename = `${removeFileExtension(request.body.filename)}.${format}`; + } else { + filename = `${Date.now()}.${format}`; + } + + // if character is defined, save to a sub folder for that character + let pathToNewFile = path.join(DIRECTORIES.userImages, sanitize(filename)); + if (request.body.ch_name) { + pathToNewFile = path.join(DIRECTORIES.userImages, sanitize(request.body.ch_name), sanitize(filename)); + } + + ensureDirectoryExistence(pathToNewFile); + const imageBuffer = Buffer.from(base64Data, 'base64'); + await fs.promises.writeFile(pathToNewFile, imageBuffer); + response.send({ path: clientRelativePath(pathToNewFile) }); + } catch (error) { + console.log(error); + response.status(500).send({ error: 'Failed to save the image' }); + } +}); + +router.post('/list/:folder', (req, res) => { + const directoryPath = path.join(process.cwd(), DIRECTORIES.userImages, sanitize(req.params.folder)); + + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath, { recursive: true }); + } + + try { + const images = getImages(directoryPath); + return res.send(images); + } catch (error) { + console.error(error); + return res.status(500).send({ error: 'Unable to retrieve files' }); + } +}); + +module.exports = { router };