From babe9abbe9188b8447194055827f1a884f065e5f Mon Sep 17 00:00:00 2001 From: valadaptive Date: Mon, 4 Dec 2023 12:48:29 -0500 Subject: [PATCH 1/2] Use Express router for extensions endpoint --- server.js | 2 +- src/endpoints/extensions.js | 374 ++++++++++++++++++------------------ 2 files changed, 185 insertions(+), 191 deletions(-) diff --git a/server.js b/server.js index a0b24bb5b..5a69be985 100644 --- a/server.js +++ b/server.js @@ -3594,7 +3594,7 @@ require('./src/endpoints/thumbnails').registerEndpoints(app, jsonParser); require('./src/endpoints/novelai').registerEndpoints(app, jsonParser); // Third-party extensions -require('./src/endpoints/extensions').registerEndpoints(app, jsonParser); +app.use('/api/extensions', require('./src/endpoints/extensions').router); // Asset management require('./src/endpoints/assets').registerEndpoints(app, jsonParser); diff --git a/src/endpoints/extensions.js b/src/endpoints/extensions.js index 4e3e54d39..c479b45de 100644 --- a/src/endpoints/extensions.js +++ b/src/endpoints/extensions.js @@ -1,8 +1,10 @@ const path = require('path'); const fs = require('fs'); +const express = require('express'); const { default: simpleGit } = require('simple-git'); const sanitize = require('sanitize-filename'); const { DIRECTORIES } = require('../constants'); +const { jsonParser } = require('../express-common'); /** * This function extracts the extension information from the manifest file. @@ -45,206 +47,198 @@ async function checkIfRepoIsUpToDate(extensionPath) { }; } +const router = express.Router(); + /** - * Registers the endpoints for the third-party extensions API. - * @param {import('express').Express} app - Express app - * @param {any} jsonParser - JSON parser middleware + * HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest, + * and return extension information and path. + * + * @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property. + * @param {Object} response - HTTP Response object used to respond to the HTTP request. + * + * @returns {void} */ -function registerEndpoints(app, jsonParser) { - /** - * HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest, - * and return extension information and path. - * - * @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property. - * @param {Object} response - HTTP Response object used to respond to the HTTP request. - * - * @returns {void} - */ - app.post('/api/extensions/install', jsonParser, async (request, response) => { - if (!request.body.url) { - return response.status(400).send('Bad Request: URL is required in the request body.'); - } +router.post('/install', jsonParser, async (request, response) => { + if (!request.body.url) { + return response.status(400).send('Bad Request: URL is required in the request body.'); + } - try { - const git = simpleGit(); - - // make sure the third-party directory exists - if (!fs.existsSync(path.join(DIRECTORIES.extensions, 'third-party'))) { - fs.mkdirSync(path.join(DIRECTORIES.extensions, 'third-party')); - } - - const url = request.body.url; - const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', path.basename(url, '.git')); - - if (fs.existsSync(extensionPath)) { - return response.status(409).send(`Directory already exists at ${extensionPath}`); - } - - await git.clone(url, extensionPath, { '--depth': 1 }); - console.log(`Extension has been cloned at ${extensionPath}`); - - - const { version, author, display_name } = await getManifest(extensionPath); - - - return response.send({ version, author, display_name, extensionPath }); - } catch (error) { - console.log('Importing custom content failed', error); - return response.status(500).send(`Server Error: ${error.message}`); - } - }); - - /** - * HTTP POST handler function to pull the latest updates from a git repository - * based on the extension name provided in the request body. It returns the latest commit hash, - * the path of the extension, the status of the repository (whether it's up-to-date or not), - * and the remote URL of the repository. - * - * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property. - * @param {Object} response - HTTP Response object used to respond to the HTTP request. - * - * @returns {void} - */ - app.post('/api/extensions/update', jsonParser, async (request, response) => { + try { const git = simpleGit(); - if (!request.body.extensionName) { - return response.status(400).send('Bad Request: extensionName is required in the request body.'); - } - - try { - const extensionName = request.body.extensionName; - const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName); - - if (!fs.existsSync(extensionPath)) { - return response.status(404).send(`Directory does not exist at ${extensionPath}`); - } - - const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); - const currentBranch = await git.cwd(extensionPath).branch(); - if (!isUpToDate) { - - await git.cwd(extensionPath).pull('origin', currentBranch.current); - console.log(`Extension has been updated at ${extensionPath}`); - } else { - console.log(`Extension is up to date at ${extensionPath}`); - } - await git.cwd(extensionPath).fetch('origin'); - const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); - const shortCommitHash = fullCommitHash.slice(0, 7); - - return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl }); - - } catch (error) { - console.log('Updating custom content failed', error); - return response.status(500).send(`Server Error: ${error.message}`); - } - }); - - /** - * HTTP POST handler function to get the current git commit hash and branch name for a given extension. - * It checks whether the repository is up-to-date with the remote, and returns the status along with - * the remote URL of the repository. - * - * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property. - * @param {Object} response - HTTP Response object used to respond to the HTTP request. - * - * @returns {void} - */ - app.post('/api/extensions/version', jsonParser, async (request, response) => { - const git = simpleGit(); - if (!request.body.extensionName) { - return response.status(400).send('Bad Request: extensionName is required in the request body.'); - } - - try { - const extensionName = request.body.extensionName; - const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName); - - if (!fs.existsSync(extensionPath)) { - return response.status(404).send(`Directory does not exist at ${extensionPath}`); - } - - const currentBranch = await git.cwd(extensionPath).branch(); - // get only the working branch - const currentBranchName = currentBranch.current; - await git.cwd(extensionPath).fetch('origin'); - const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); - console.log(currentBranch, currentCommitHash); - const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); - - return response.send({ currentBranchName, currentCommitHash, isUpToDate, remoteUrl }); - - } catch (error) { - console.log('Getting extension version failed', error); - return response.status(500).send(`Server Error: ${error.message}`); - } - }); - - /** - * HTTP POST handler function to delete a git repository based on the extension name provided in the request body. - * - * @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property. - * @param {Object} response - HTTP Response object used to respond to the HTTP request. - * - * @returns {void} - */ - app.post('/api/extensions/delete', jsonParser, async (request, response) => { - if (!request.body.extensionName) { - return response.status(400).send('Bad Request: extensionName is required in the request body.'); - } - - // Sanatize the extension name to prevent directory traversal - const extensionName = sanitize(request.body.extensionName); - - try { - const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName); - - if (!fs.existsSync(extensionPath)) { - return response.status(404).send(`Directory does not exist at ${extensionPath}`); - } - - await fs.promises.rmdir(extensionPath, { recursive: true }); - console.log(`Extension has been deleted at ${extensionPath}`); - - return response.send(`Extension has been deleted at ${extensionPath}`); - - } catch (error) { - console.log('Deleting custom content failed', error); - return response.status(500).send(`Server Error: ${error.message}`); - } - }); - - /** - * Discover the extension folders - * If the folder is called third-party, search for subfolders instead - */ - app.get('/api/extensions/discover', jsonParser, function (_, response) { - - // get all folders in the extensions folder, except third-party - const extensions = fs - .readdirSync(DIRECTORIES.extensions) - .filter(f => fs.statSync(path.join(DIRECTORIES.extensions, f)).isDirectory()) - .filter(f => f !== 'third-party'); - - // get all folders in the third-party folder, if it exists + // make sure the third-party directory exists if (!fs.existsSync(path.join(DIRECTORIES.extensions, 'third-party'))) { - return response.send(extensions); + fs.mkdirSync(path.join(DIRECTORIES.extensions, 'third-party')); } - const thirdPartyExtensions = fs - .readdirSync(path.join(DIRECTORIES.extensions, 'third-party')) - .filter(f => fs.statSync(path.join(DIRECTORIES.extensions, 'third-party', f)).isDirectory()); + const url = request.body.url; + const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', path.basename(url, '.git')); - // add the third-party extensions to the extensions array - extensions.push(...thirdPartyExtensions.map(f => `third-party/${f}`)); - console.log(extensions); + if (fs.existsSync(extensionPath)) { + return response.status(409).send(`Directory already exists at ${extensionPath}`); + } + + await git.clone(url, extensionPath, { '--depth': 1 }); + console.log(`Extension has been cloned at ${extensionPath}`); + const { version, author, display_name } = await getManifest(extensionPath); + + + return response.send({ version, author, display_name, extensionPath }); + } catch (error) { + console.log('Importing custom content failed', error); + return response.status(500).send(`Server Error: ${error.message}`); + } +}); + +/** + * HTTP POST handler function to pull the latest updates from a git repository + * based on the extension name provided in the request body. It returns the latest commit hash, + * the path of the extension, the status of the repository (whether it's up-to-date or not), + * and the remote URL of the repository. + * + * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property. + * @param {Object} response - HTTP Response object used to respond to the HTTP request. + * + * @returns {void} + */ +router.post('/update', jsonParser, async (request, response) => { + const git = simpleGit(); + if (!request.body.extensionName) { + return response.status(400).send('Bad Request: extensionName is required in the request body.'); + } + + try { + const extensionName = request.body.extensionName; + const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName); + + if (!fs.existsSync(extensionPath)) { + return response.status(404).send(`Directory does not exist at ${extensionPath}`); + } + + const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); + const currentBranch = await git.cwd(extensionPath).branch(); + if (!isUpToDate) { + + await git.cwd(extensionPath).pull('origin', currentBranch.current); + console.log(`Extension has been updated at ${extensionPath}`); + } else { + console.log(`Extension is up to date at ${extensionPath}`); + } + await git.cwd(extensionPath).fetch('origin'); + const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); + const shortCommitHash = fullCommitHash.slice(0, 7); + + return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl }); + + } catch (error) { + console.log('Updating custom content failed', error); + return response.status(500).send(`Server Error: ${error.message}`); + } +}); + +/** + * HTTP POST handler function to get the current git commit hash and branch name for a given extension. + * It checks whether the repository is up-to-date with the remote, and returns the status along with + * the remote URL of the repository. + * + * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property. + * @param {Object} response - HTTP Response object used to respond to the HTTP request. + * + * @returns {void} + */ +router.post('/version', jsonParser, async (request, response) => { + const git = simpleGit(); + if (!request.body.extensionName) { + return response.status(400).send('Bad Request: extensionName is required in the request body.'); + } + + try { + const extensionName = request.body.extensionName; + const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName); + + if (!fs.existsSync(extensionPath)) { + return response.status(404).send(`Directory does not exist at ${extensionPath}`); + } + + const currentBranch = await git.cwd(extensionPath).branch(); + // get only the working branch + const currentBranchName = currentBranch.current; + await git.cwd(extensionPath).fetch('origin'); + const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); + console.log(currentBranch, currentCommitHash); + const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); + + return response.send({ currentBranchName, currentCommitHash, isUpToDate, remoteUrl }); + + } catch (error) { + console.log('Getting extension version failed', error); + return response.status(500).send(`Server Error: ${error.message}`); + } +}); + +/** + * HTTP POST handler function to delete a git repository based on the extension name provided in the request body. + * + * @param {Object} request - HTTP Request object, expects a JSON body with a 'url' property. + * @param {Object} response - HTTP Response object used to respond to the HTTP request. + * + * @returns {void} + */ +router.post('/delete', jsonParser, async (request, response) => { + if (!request.body.extensionName) { + return response.status(400).send('Bad Request: extensionName is required in the request body.'); + } + + // Sanatize the extension name to prevent directory traversal + const extensionName = sanitize(request.body.extensionName); + + try { + const extensionPath = path.join(DIRECTORIES.extensions, 'third-party', extensionName); + + if (!fs.existsSync(extensionPath)) { + return response.status(404).send(`Directory does not exist at ${extensionPath}`); + } + + await fs.promises.rmdir(extensionPath, { recursive: true }); + console.log(`Extension has been deleted at ${extensionPath}`); + + return response.send(`Extension has been deleted at ${extensionPath}`); + + } catch (error) { + console.log('Deleting custom content failed', error); + return response.status(500).send(`Server Error: ${error.message}`); + } +}); + +/** + * Discover the extension folders + * If the folder is called third-party, search for subfolders instead + */ +router.get('/api/extensions/discover', jsonParser, function (_, response) { + // get all folders in the extensions folder, except third-party + const extensions = fs + .readdirSync(DIRECTORIES.extensions) + .filter(f => fs.statSync(path.join(DIRECTORIES.extensions, f)).isDirectory()) + .filter(f => f !== 'third-party'); + + // get all folders in the third-party folder, if it exists + + if (!fs.existsSync(path.join(DIRECTORIES.extensions, 'third-party'))) { return response.send(extensions); - }); -} + } -module.exports = { - registerEndpoints, -}; + const thirdPartyExtensions = fs + .readdirSync(path.join(DIRECTORIES.extensions, 'third-party')) + .filter(f => fs.statSync(path.join(DIRECTORIES.extensions, 'third-party', f)).isDirectory()); + + // add the third-party extensions to the extensions array + extensions.push(...thirdPartyExtensions.map(f => `third-party/${f}`)); + console.log(extensions); + + + return response.send(extensions); +}); + +module.exports = { router }; From aff821aa0782b5ab048c1334e912fd7b6aa18540 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 4 Dec 2023 21:54:03 +0200 Subject: [PATCH 2/2] Fix discovery endpoint route --- src/endpoints/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/endpoints/extensions.js b/src/endpoints/extensions.js index c479b45de..9aaf93a3c 100644 --- a/src/endpoints/extensions.js +++ b/src/endpoints/extensions.js @@ -216,7 +216,7 @@ router.post('/delete', jsonParser, async (request, response) => { * Discover the extension folders * If the folder is called third-party, search for subfolders instead */ -router.get('/api/extensions/discover', jsonParser, function (_, response) { +router.get('/discover', jsonParser, function (_, response) { // get all folders in the extensions folder, except third-party const extensions = fs .readdirSync(DIRECTORIES.extensions)