From 2ee5f6ca27ed96ccde10d6f7ea2858c28147b453 Mon Sep 17 00:00:00 2001 From: borsTiHD <34000430+borsTiHD@users.noreply.github.com> Date: Sat, 22 May 2021 15:15:49 +0200 Subject: [PATCH] Express cleanup (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 👽️ REST Api >> Refactoring express server. Still need a little more cleanup. KNOWN BUG: Route for deleting files/folders not working right now. * 📝 REST Api >> Adding docu for functions and routes. * 👽️ Middleware >> Made a few functions into middlewares for better and cleaner access. * 📝 Routes >> Adding route descriptions. --- server-middleware/asyncMiddleware.js | 8 - .../controllers/scriptsController.js | 474 ++++++++++++++++++ server-middleware/middleware/asyncHandler.js | 9 + .../middleware/isCustomScript.js | 13 + server-middleware/middleware/zipDirectory.js | 27 + server-middleware/rest-api.js | 450 +---------------- server-middleware/routes/help.js | 14 + server-middleware/routes/index.js | 13 + server-middleware/routes/scripts.js | 20 + 9 files changed, 591 insertions(+), 437 deletions(-) delete mode 100644 server-middleware/asyncMiddleware.js create mode 100644 server-middleware/controllers/scriptsController.js create mode 100644 server-middleware/middleware/asyncHandler.js create mode 100644 server-middleware/middleware/isCustomScript.js create mode 100644 server-middleware/middleware/zipDirectory.js create mode 100644 server-middleware/routes/help.js create mode 100644 server-middleware/routes/index.js create mode 100644 server-middleware/routes/scripts.js diff --git a/server-middleware/asyncMiddleware.js b/server-middleware/asyncMiddleware.js deleted file mode 100644 index 79b4e5fe..00000000 --- a/server-middleware/asyncMiddleware.js +++ /dev/null @@ -1,8 +0,0 @@ -const asyncUtil = (fn) => - function asyncUtilWrap(...args) { - const fnReturn = fn(...args) - const next = args[args.length - 1] - return Promise.resolve(fnReturn).catch(next) - } - -module.exports = asyncUtil diff --git a/server-middleware/controllers/scriptsController.js b/server-middleware/controllers/scriptsController.js new file mode 100644 index 00000000..790573af --- /dev/null +++ b/server-middleware/controllers/scriptsController.js @@ -0,0 +1,474 @@ +// Imports +const path = require('path') +const fs = require('fs-extra') + +// Middleware +const asyncHandler = require('../middleware/asyncHandler') // Middleware for handling errors on promise calls +const isCustomScript = require('../middleware/isCustomScript') // Testing if given 'path' is a 'custom path' +const zipDirectory = require('../middleware/zipDirectory') // Zipping file/folder middleware + +// ChildProcess Spawn Import +const ChildProcessClass = require('../../classes/ChildProcessClass') +const childProcessSpawn = new ChildProcessClass() + +// Script Directory +const scriptPath = path.join('.', 'scripts') + +/** + * Route serving index + * @name index + * @function + * @memberof module:routers/scripts + */ +exports.index = (req, res) => { + res.json({ + _status: 'ok', + info: 'Endpoint is set up.' + }) +} + +/** + * Route serving list of files from host + * @name list + * @function + * @memberof module:routers/scripts + */ +exports.list = asyncHandler(async(req, res, next) => { + // Generates unique random IDs for every folder/file + const uniqueIds = [] + let maxIds = 100 + function generateId(max) { + let r = Math.floor(Math.random() * max) + 1 + if (!uniqueIds.includes(r)) { + uniqueIds.push(r) + } else { + r = generateId(max) + } + return r + } + + // Reads all scripts in 'root/scripts/' with subfolders + // Returns array with all existing files + async function getFiles(dir) { + maxIds += 1 // Increase unique IDs limit + const stats = await fs.stat(dir) + const fileList = { + path: dir, + name: path.basename(dir), + id: generateId(maxIds) + } + + if (stats.isDirectory()) { + fileList.type = 'folder' + const files = await fs.readdir(path.join(dir)) + maxIds += files.length // Increase unique IDs limit + fileList.children = await Promise.all(files.map(async(child) => { + const res = getFiles(path.join(dir, child)) + return res + })) + } else { + // Assuming it's a file. In real life it could be a symlink or something else! + fileList.type = 'file' + } + + return fileList + } + + // Scanning folder + const scripts = await getFiles(scriptPath) + + // Return results + res.json({ + _status: 'ok', + info: 'Files scannt', + scripts + }) +}) + +/** + * Route executes scripts from host + * @name execute + * @function + * @memberof module:routers/scripts + * @param {string} script - Query for script path. Exp.: ?script=scripts%5Ccustom%5Ctest.bat + * @param {array} args - Query for arguments. Exp.: ?args=['a', 'b', 'c'] + */ +exports.execute = asyncHandler(async(req, res, next) => { + const { query } = req + const scriptRaw = query.script + const args = query.args || [] + + // Building 'script' path + const script = path.join(scriptPath, scriptRaw.replace('scripts\\', '').replace('scripts/', '')) + + // Promise for spawning + const spawn = () => { + return new Promise((resolve, reject) => { + // Spawn Script + childProcessSpawn.execShell(script, args, (pid, output) => { }, (pid, output, exitCode) => { + resolve({ output, exitCode, pid }) + }, (error) => { + reject(error) + }) + }) + } + + // Spawn script + const response = await spawn().catch((error) => { + // REST return + res.json({ + _status: 'error', + info: 'Script not successfully executed', + error + }) + return next(error) + }) + + // REST return + res.json({ + _status: 'ok', + info: 'Script successfully executed', + response + }) +}) + +/** + * Route read a file from host + * @name read + * @function + * @memberof module:routers/scripts + * @param {string} path - Query for file path. Exp.: ?path=scripts%5Ccustom%5Ctest.bat + * @param {string} id - Query for file id (just required for response). Exp.: ?id=77 + * @param {string} name - Query for file name (just required for response). Exp.: ?name=test.bat + * @param {string} type - Query for file type (just required for response). Exp.: ?type=file + */ +exports.read = asyncHandler(async(req, res, next) => { + // Query Data + const query = req.query + const { id, name, type, path } = query + + // Scans stats + const stats = await fs.stat(path) + + // Not a folder? + if (!stats.isDirectory()) { + // Reading file and return result + const content = await fs.readFile(path, 'utf-8') + res.json({ + _status: 'ok', + info: 'File scannt', + script: { id, name, type, path, stats, content } + }) + } else { + res.json({ + _status: 'ok', + info: 'Folder scannt', + folder: { id, name, type, path, stats } + }) + } +}) + +/** + * Route downloading a file from host + * @name download + * @function + * @memberof module:routers/scripts + * @param {string} path - Query for file path. Exp.: ?path=scripts%5Ccustom%5Ctest.bat + * @param {string} name - Query for file name. Exp.: ?name=test.bat + */ +exports.download = asyncHandler(async(req, res, next) => { + // Query Data + const query = req.query + const name = query.name + const filePath = query.path + + // Scans stats + const stats = await fs.stat(filePath) + + // Not a folder? + if (stats.isFile()) { + // Reading file and return result + res.download(filePath, name) + } else { + const fileName = 'backup.zip' + const archiv = path.join(scriptPath, fileName) + await zipDirectory(filePath, archiv) + await res.download(archiv, fileName) + + // Scans stats + const statsBackupFile = await fs.stat(archiv) + if (statsBackupFile.isFile()) { + await fs.unlink(archiv).catch((error) => { + console.error(error) + return next() + }) + } else { + console.log('[Download] -> tried to delete backup.zip, but nothing happened.') + } + } +}) + +/** + * Route adding a file on host + * @name addFile + * @function + * @memberof module:routers/scripts + * @param {object} data - Object -> form data. Delivers script data: '{ path: "scripts/custom/...", script: { name: "test", ext: "bat", text: "echo test" }}' + */ +exports.addFile = asyncHandler(async(req, res, next) => { + const data = req.body + const script = data.script + const file = `${script.name}.${script.ext}` + const filePath = path.join(data.path, file) + if (isCustomScript(filePath)) { + await fs.outputFile(filePath, script.text).then(async() => { + console.log('[Add Script] -> Changed executable permissions.') + fs.chmod(filePath, '755') + }).catch((error) => { + console.error(error) + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t write data, please try again', + error + }) + return next() + }) + + // Return results + res.json({ + _status: 'ok', + info: 'File added', + request: req.body + }) + } else { + console.error('[Add Script] -> Only in custom folder allowed.') + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t write data, please try again', + error: 'Only in custom folder allowed' + }) + } +}) + +/** + * Route adding a folder on host + * @name addFolder + * @function + * @memberof module:routers/scripts + * @param {object} data - Object -> form data. Delivers folder data: '{ path: "scripts\custom", name: "test" }' + */ +exports.addFolder = asyncHandler(async(req, res, next) => { + const data = req.body + const folderName = data.name + const folderPath = data.path + const completePath = path.join(folderPath, folderName) + if (isCustomScript(completePath)) { + await fs.mkdir(completePath, { recursive: true }).catch((error) => { + console.error(error) + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t write folder, please try again', + error + }) + return next() + }) + + // Return results + res.json({ + _status: 'ok', + info: 'Folder added', + request: req.body + }) + } else { + console.error('[Add Folder] -> Only in custom folder allowed.') + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t write folder, please try again', + error: 'Only in custom folder allowed' + }) + } +}) + +/** + * Route deleting a file/folder from host + * @name delete + * @function + * @memberof module:routers/scripts + * @param {string} path - Query for file/folder path. Exp.: ?path=scripts\custom\test + */ +exports.delete = asyncHandler(async(req, res, next) => { + // Query Data + const query = req.query + const filePath = query.path + + // Scans stats and delete only if it is a file + const stats = await fs.stat(filePath) + + if (stats.isFile() && isCustomScript(filePath)) { + await fs.unlink(filePath).catch((error) => { + console.error(error) + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t delete file, please try again', + error + }) + return next() + }) + + // Return results + res.json({ + _status: 'ok', + info: 'File deleted', + request: query + }) + } else if (stats.isDirectory() && (isCustomScript(filePath) || filePath === path.join(scriptPath, 'custom'))) { + await fs.rm(filePath, { recursive: true }).catch((error) => { + console.error(error) + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t delete folder, please try again', + error + }) + return next() + }) + + // Return results + res.json({ + _status: 'ok', + info: 'Folder deleted', + request: query + }) + } else { + // Return results + const error = new Error('Couldn\'t delete. Request wasn\'t a file, or wasn\t a custom script.') + res.status(500).json({ + _status: 'error', + info: 'Something went wrong', + error: error.message + }) + } +}) + +/** + * Route edits or renames a file from host + * @name editFile + * @function + * @memberof module:routers/scripts + * @param {string} oldFile - Query for old file data. Exp.: ?oldFile: {"path":"scripts\\custom\\test.bat","name":"test.bat","id":71,"type":"file"} + * @param {string} newFile - Query for new file data. Exp.: ?newFile: {"name":"new.bat","content":"echo test"} + */ +exports.editFile = asyncHandler(async(req, res, next) => { + // Query Data + const query = req.query + const oldFile = JSON.parse(query.oldFile) + const newFile = JSON.parse(query.newFile) + + // Scans stats and edits only if it is a file + const stats = await fs.stat(oldFile.path) + if (stats.isFile() && isCustomScript(oldFile.path)) { + const oldFileNameRegexp = new RegExp(`${oldFile.name}$`, 'gm') // Matches old filename (only the end of the path) + const newPath = oldFile.path.replace(oldFileNameRegexp, newFile.name) + + // Renames file, if old and new names are different + if (oldFile.name !== newFile.name) { + await fs.rename(oldFile.path, newPath).catch((error) => { + console.error(error) + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t rename file, please try again', + error + }) + return next() + }) + } + + // Overwrite file with new content + await fs.outputFile(newPath, newFile.content).then(async() => { + console.log('[Edit Script] -> Changed executable permissions.') + fs.chmod(newPath, '755') + }).catch((error) => { + console.error(error) + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t write data, please try again', + error + }) + return next() + }) + + // Return results + res.json({ + _status: 'ok', + info: 'File edited successfully', + request: query + }) + } else { + // Return results + const error = new Error('Couldn\'t edit script. Request wasn\'t a file, or wasn\t an custom script.') + res.status(500).json({ + _status: 'error', + info: 'Something went wrong', + error: error.message + }) + return next() + } +}) + +/** + * Route edits or renames a folder from host + * @name editFolder + * @function + * @memberof module:routers/scripts + * @param {string} oldFolder - Query for old file data. Exp.: ?oldFolder: {"path":"scripts\\custom\\test","name":"test"} + * @param {string} newFolder - Query for new file data. Exp.: ?newFolder: {"name":"new"} + */ +exports.editFolder = asyncHandler(async(req, res, next) => { + // Query Data + const query = req.query + const oldFolder = JSON.parse(query.oldFolder) + const newFolder = JSON.parse(query.newFolder) + + // Scans stats and edits only if it is a folder + const stats = await fs.stat(oldFolder.path) + if (stats.isDirectory() && isCustomScript(oldFolder.path)) { + const oldFolderNameRegexp = new RegExp(`${oldFolder.name}$`, 'gm') // Matches old filename (only the end of the path) + const newPath = oldFolder.path.replace(oldFolderNameRegexp, newFolder.name) + + // Renames folder, if old and new names are different + if (oldFolder.name !== newFolder.name) { + await fs.rename(oldFolder.path, newPath).catch((error) => { + console.error(error) + // Return results + res.status(500).json({ + _status: 'error', + info: 'Couldn\'t rename folder, please try again', + error + }) + return next() + }) + } + + // Return results + res.json({ + _status: 'ok', + info: 'Folder edited successfully', + request: query + }) + } else { + // Return results + const error = new Error('Couldn\'t edit folder. Request wasn\'t a folder, or wasn\t in custom script path.') + res.status(500).json({ + _status: 'error', + info: 'Something went wrong', + error: error.message + }) + } +}) diff --git a/server-middleware/middleware/asyncHandler.js b/server-middleware/middleware/asyncHandler.js new file mode 100644 index 00000000..208fd08a --- /dev/null +++ b/server-middleware/middleware/asyncHandler.js @@ -0,0 +1,9 @@ +// Async/await middleware +// Wrapper for using promises in express routes +const asyncUtil = (fn) => + function asyncUtilWrap(req, res, next, ...args) { + const fnReturn = fn(req, res, next, ...args) + return Promise.resolve(fnReturn).catch(next) + } + +module.exports = asyncUtil diff --git a/server-middleware/middleware/isCustomScript.js b/server-middleware/middleware/isCustomScript.js new file mode 100644 index 00000000..1fc03c45 --- /dev/null +++ b/server-middleware/middleware/isCustomScript.js @@ -0,0 +1,13 @@ +/** + * Validating paths: Tests if file is in 'custom' directory + * Only 'custom' scripts are allowed + * @param {String} path - path to validate + * @returns {Boolean} - 'True': 'path' points to custom directory; 'False': 'path' is not pointing to the custom directory + */ +const isCustomScript = (path) => { + // Validates folder structure + // Returns true, if the custom path is in there + return /^scripts\\custom\\/gm.test(path) /* win path */ || /^scripts\/custom\//gm.test(path) /* linux path */ +} + +module.exports = isCustomScript diff --git a/server-middleware/middleware/zipDirectory.js b/server-middleware/middleware/zipDirectory.js new file mode 100644 index 00000000..ce1e1f0e --- /dev/null +++ b/server-middleware/middleware/zipDirectory.js @@ -0,0 +1,27 @@ +const fs = require('fs-extra') +const archiver = require('archiver') + +/** + * Middleware: Zipping a given file/folder + * @param {String} source - file/folder path + * @param {String} out - output file path + * @returns {Promise} + * @url https://stackoverflow.com/a/51518100/7625095 + */ +const zipDirectory = (source, out) => { + const type = 'zip' // Type for packaged file + const archive = archiver(type, { zlib: { level: 9 } }) + const stream = fs.createWriteStream(out) + + return new Promise((resolve, reject) => { + archive + .directory(source, false) + .on('error', (err) => reject(err)) + .pipe(stream) + + stream.on('close', () => resolve()) + archive.finalize() + }) +} + +module.exports = zipDirectory diff --git a/server-middleware/rest-api.js b/server-middleware/rest-api.js index fc5578d5..f42dbb72 100644 --- a/server-middleware/rest-api.js +++ b/server-middleware/rest-api.js @@ -1,444 +1,36 @@ // Imports -const path = require('path') -const fs = require('fs-extra') -const archiver = require('archiver') +const createError = require('http-errors') const express = require('express') -// Middleware for handling errors on promise calls -const asyncHandler = require('../server-middleware/asyncMiddleware') - -// ChildProcess Spawn Import -const ChildProcessClass = require('../classes/ChildProcessClass') -const childProcessSpawn = new ChildProcessClass() - -// Script Directory -const scriptPath = path.join('.', 'scripts') - -// Tests if file is in 'custom' directory -// Only 'custom' scripts can be deleted -function isCustomScript(path) { - // Validates folder structure - // Returns true, if the custom path is in there - return /^scripts\\custom\\/gm.test(path) /* win path */ || /^scripts\/custom\//gm.test(path) /* linux path */ -} - -/** - * @param {String} source - * @param {String} out - * @returns {Promise} - * @url https://stackoverflow.com/a/51518100/7625095 - */ -function zipDirectory(source, out) { - const type = 'zip' // Type for packaged file - const archive = archiver(type, { zlib: { level: 9 } }) - const stream = fs.createWriteStream(out) - - return new Promise((resolve, reject) => { - archive - .directory(source, false) - .on('error', (err) => reject(err)) - .pipe(stream) - - stream.on('close', () => resolve()) - archive.finalize() - }) -} +// Importing Routes +const indexRouter = require('./routes/index') +const helpRouter = require('./routes/help') +const scriptsRouter = require('./routes/scripts') // Express Init const app = express() app.use(express.json()) app.use(express.urlencoded({ extended: true })) // for form data -app.all('/', (req, res) => { - res.json({ - _status: 'ok', - info: 'Endpoint is set up.' - }) -}) +// Routes/Endpoints: +app.use('/', indexRouter) // Index +app.use('/help', helpRouter) // Help +app.use('/scripts', scriptsRouter) // Scripts -app.get('/help', (req, res) => { - console.log('[API] -> PLEASE HELP ME!') - res.json({ - _status: 'ok', - info: 'You are searching for help?' - }) +// Catch 404 and forward to error handler +app.use((req, res, next) => { + next(createError(404)) }) -app.post('/scripts/execute', asyncHandler(async(req, res, next) => { - const { query } = req - const scriptRaw = query.script - const args = query.args || [] - - // Building 'script' path - const script = path.join(scriptPath, scriptRaw.replace('scripts\\', '').replace('scripts/', '')) - - // Promise for spawning - const spawn = () => { - return new Promise((resolve, reject) => { - // Spawn Script - childProcessSpawn.execShell(script, args, (pid, output) => { }, (pid, output, exitCode) => { - resolve({ output, exitCode, pid }) - }, (error) => { - reject(error) - }) - }) - } - - // Spawn script - const response = await spawn().catch((error) => { - // REST return - res.json({ - _status: 'error', - info: 'Script not successfully executed', - error - }) - return next(error) - }) - - // REST return - res.json({ - _status: 'ok', - info: 'Script successfully executed', - response - }) -})) - -app.get('/scripts/list', asyncHandler(async(req, res, next) => { - // Generates unique random IDs for every folder/file - const uniqueIds = [] - let maxIds = 100 - function generateId(max) { - let r = Math.floor(Math.random() * max) + 1 - if (!uniqueIds.includes(r)) { - uniqueIds.push(r) - } else { - r = generateId(max) - } - return r - } - - // Reads all scripts in 'root/scripts/' with subfolders - // Returns array with all existing files - async function getFiles(dir) { - maxIds += 1 // Increase unique IDs limit - const stats = await fs.stat(dir) - const fileList = { - path: dir, - name: path.basename(dir), - id: generateId(maxIds) - } - - if (stats.isDirectory()) { - fileList.type = 'folder' - const files = await fs.readdir(path.join(dir)) - maxIds += files.length // Increase unique IDs limit - fileList.children = await Promise.all(files.map(async(child) => { - const res = getFiles(path.join(dir, child)) - return res - })) - } else { - // Assuming it's a file. In real life it could be a symlink or something else! - fileList.type = 'file' - } - - return fileList - } - - // Scanning folder - const scripts = await getFiles(scriptPath) - - // Return results - res.json({ - _status: 'ok', - info: 'Files scannt', - scripts - }) -})) - -app.get('/scripts/read', asyncHandler(async(req, res, next) => { - // Query Data - const query = req.query - const { id, name, type, path } = query - - // Scans stats - const stats = await fs.stat(path) - - // Not a folder? - if (!stats.isDirectory()) { - // Reading file and return result - const content = await fs.readFile(path, 'utf-8') - res.json({ - _status: 'ok', - info: 'File scannt', - script: { id, name, type, path, stats, content } - }) - } else { - res.json({ - _status: 'ok', - info: 'Folder scannt', - folder: { id, name, type, path, stats } - }) - } -})) - -app.get('/scripts/download', asyncHandler(async(req, res, next) => { - // Query Data - const query = req.query - const name = query.name - const filePath = query.path +// Error handler +app.use((err, req, res, next) => { + // Set locals, only providing error in development + res.locals.message = err.message + res.locals.error = req.app.get('env') === 'development' ? err : {} - // Scans stats - const stats = await fs.stat(filePath) - - // Not a folder? - if (stats.isFile()) { - // Reading file and return result - res.download(filePath, name) - } else { - const fileName = 'backup.zip' - const archiv = path.join(scriptPath, fileName) - await zipDirectory(filePath, archiv) - await res.download(archiv, fileName) - - // Scans stats - const statsBackupFile = await fs.stat(archiv) - if (statsBackupFile.isFile()) { - await fs.unlink(archiv).catch((error) => { - console.error(error) - return next() - }) - } else { - console.log('[Download] -> tried to delete backup.zip, but nothing happened.') - } - } -})) - -app.post('/scripts/add/file', asyncHandler(async(req, res, next) => { - const data = req.body - const script = data.script - const file = `${script.name}.${script.ext}` - const filePath = path.join(data.path, file) - if (isCustomScript(filePath)) { - await fs.outputFile(filePath, script.text).then(async() => { - console.log('[Add Script] -> Changed executable permissions.') - fs.chmod(filePath, '755') - }).catch((error) => { - console.error(error) - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t write data, please try again', - error - }) - return next() - }) - - // Return results - res.json({ - _status: 'ok', - info: 'File added', - request: req.body - }) - } else { - console.error('[Add Script] -> Only in custom folder allowed.') - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t write data, please try again', - error: 'Only in custom folder allowed' - }) - } -})) - -app.post('/scripts/add/folder', asyncHandler(async(req, res, next) => { - const data = req.body - const folderName = data.name - const folderPath = data.path - const completePath = path.join(folderPath, folderName) - if (isCustomScript(completePath)) { - await fs.mkdir(completePath, { recursive: true }).catch((error) => { - console.error(error) - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t write folder, please try again', - error - }) - return next() - }) - - // Return results - res.json({ - _status: 'ok', - info: 'Folder added', - request: req.body - }) - } else { - console.error('[Add Folder] -> Only in custom folder allowed.') - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t write folder, please try again', - error: 'Only in custom folder allowed' - }) - } -})) - -app.post('/scripts/delete', asyncHandler(async(req, res, next) => { - // Query Data - const query = req.query - const filePath = query.path - - // Scans stats and delete only if it is a file - const stats = await fs.stat(filePath) - - if (stats.isFile() && isCustomScript(filePath)) { - await fs.unlink(filePath).catch((error) => { - console.error(error) - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t delete file, please try again', - error - }) - return next() - }) - - // Return results - res.json({ - _status: 'ok', - info: 'File deleted', - request: query - }) - } else if (stats.isDirectory() && (isCustomScript(filePath) || filePath === path.join(scriptPath, 'custom'))) { - await fs.rm(filePath, { recursive: true }).catch((error) => { - console.error(error) - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t delete folder, please try again', - error - }) - return next() - }) - - // Return results - res.json({ - _status: 'ok', - info: 'Folder deleted', - request: query - }) - } else { - // Return results - const error = new Error('Couldn\'t delete. Request wasn\'t a file, or wasn\t a custom script.') - res.status(500).json({ - _status: 'error', - info: 'Something went wrong', - error: error.message - }) - } -})) - -app.post('/scripts/edit/file', asyncHandler(async(req, res, next) => { - // Query Data - const query = req.query - const oldFile = JSON.parse(query.oldFile) - const newFile = JSON.parse(query.newFile) - - // Scans stats and edits only if it is a file - const stats = await fs.stat(oldFile.path) - if (stats.isFile() && isCustomScript(oldFile.path)) { - const oldFileNameRegexp = new RegExp(`${oldFile.name}$`, 'gm') // Matches old filename (only the end of the path) - const newPath = oldFile.path.replace(oldFileNameRegexp, newFile.name) - - // Renames file, if old and new names are different - if (oldFile.name !== newFile.name) { - await fs.rename(oldFile.path, newPath).catch((error) => { - console.error(error) - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t rename file, please try again', - error - }) - return next() - }) - } - - // Overwrite file with new content - await fs.outputFile(newPath, newFile.content).then(async() => { - console.log('[Edit Script] -> Changed executable permissions.') - fs.chmod(newPath, '755') - }).catch((error) => { - console.error(error) - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t write data, please try again', - error - }) - return next() - }) - - // Return results - res.json({ - _status: 'ok', - info: 'File edited successfully', - request: query - }) - } else { - // Return results - const error = new Error('Couldn\'t edit script. Request wasn\'t a file, or wasn\t an custom script.') - res.status(500).json({ - _status: 'error', - info: 'Something went wrong', - error: error.message - }) - return next() - } -})) - -app.post('/scripts/edit/folder', asyncHandler(async(req, res, next) => { - // Query Data - const query = req.query - const oldFolder = JSON.parse(query.oldFolder) - const newFolder = JSON.parse(query.newFolder) - - // Scans stats and edits only if it is a folder - const stats = await fs.stat(oldFolder.path) - if (stats.isDirectory() && isCustomScript(oldFolder.path)) { - const oldFolderNameRegexp = new RegExp(`${oldFolder.name}$`, 'gm') // Matches old filename (only the end of the path) - const newPath = oldFolder.path.replace(oldFolderNameRegexp, newFolder.name) - - // Renames folder, if old and new names are different - if (oldFolder.name !== newFolder.name) { - await fs.rename(oldFolder.path, newPath).catch((error) => { - console.error(error) - // Return results - res.status(500).json({ - _status: 'error', - info: 'Couldn\'t rename folder, please try again', - error - }) - return next() - }) - } - - // Return results - res.json({ - _status: 'ok', - info: 'Folder edited successfully', - request: query - }) - } else { - // Return results - const error = new Error('Couldn\'t edit folder. Request wasn\'t a folder, or wasn\t in custom script path.') - res.status(500).json({ - _status: 'error', - info: 'Something went wrong', - error: error.message - }) - } -})) + // Render the error page + res.status(err.status || 500) + res.render('error') +}) module.exports = app diff --git a/server-middleware/routes/help.js b/server-middleware/routes/help.js new file mode 100644 index 00000000..4718515d --- /dev/null +++ b/server-middleware/routes/help.js @@ -0,0 +1,14 @@ +// Express +const express = require('express') +const router = express.Router() + +// Route: '/help' -> just for testing purpose right now +router.all('/', (req, res, next) => { + console.log('[API] -> PLEASE HELP ME!') + res.json({ + _status: 'ok', + info: 'You are searching for help?' + }) +}) + +module.exports = router diff --git a/server-middleware/routes/index.js b/server-middleware/routes/index.js new file mode 100644 index 00000000..6a0c4ed8 --- /dev/null +++ b/server-middleware/routes/index.js @@ -0,0 +1,13 @@ +// Express +const express = require('express') +const router = express.Router() + +// Route: '/' -> REST Api index endpoint +router.all('/', (req, res, next) => { + res.json({ + _status: 'ok', + info: 'Endpoint is set up.' + }) +}) + +module.exports = router diff --git a/server-middleware/routes/scripts.js b/server-middleware/routes/scripts.js new file mode 100644 index 00000000..2943a0c6 --- /dev/null +++ b/server-middleware/routes/scripts.js @@ -0,0 +1,20 @@ +// Express +const express = require('express') +const router = express.Router() + +// Controller +const controller = require('../controllers/scriptsController') + +// Router: '/scripts/..' +router.all('/', controller.index) /* ALL index. */ +router.get('/list', controller.list) /* GET list of scripts. */ +router.post('/execute', controller.execute) /* POST: executes a script/file. */ +router.get('/read', controller.read) /* GET a file and returns data. */ +router.get('/download', controller.download) /* GET: downloading a file/folder. */ +router.post('/add/file', controller.addFile) /* POST: adding a file to host. */ +router.post('/add/folder', controller.addFolder) /* POST: adding a folder to host. */ +router.post('/delete', controller.delete) /* POST: deleting a given file/folder. */ +router.post('/edit/file', controller.editFile) /* POST: editing a file. */ +router.post('/edit/folder', controller.editFolder) /* POST: editing a folder. */ + +module.exports = router