diff --git a/config.sample.js b/config.sample.js index b32441f..8988aee 100644 --- a/config.sample.js +++ b/config.sample.js @@ -1,84 +1,84 @@ module.exports = { - /* - If set to true the user will need to specify the auto-generated token - on each API call, meaning random strangers wont be able to use the service - unless they have the token loli-safe provides you with. - If it's set to false, then upload will be public for anyone to use. - */ - private: true, + /* + If set to true the user will need to specify the auto-generated token + on each API call, meaning random strangers wont be able to use the service + unless they have the token loli-safe provides you with. + If it's set to false, then upload will be public for anyone to use. + */ + private: true, - // If true, users will be able to create accounts and access their uploaded files - enableUserAccounts: true, + // If true, users will be able to create accounts and access their uploaded files + enableUserAccounts: true, - /* - Here you can decide if you want lolisafe to serve the files or if you prefer doing so via nginx. - The main difference between the two is the ease of use and the chance of analytics in the future. - If you set it to `true`, the uploaded files will be located after the host like: - https://lolisafe.moe/yourFile.jpg + /* + Here you can decide if you want lolisafe to serve the files or if you prefer doing so via nginx. + The main difference between the two is the ease of use and the chance of analytics in the future. + If you set it to `true`, the uploaded files will be located after the host like: + https://lolisafe.moe/yourFile.jpg - If you set it to `false`, you need to set nginx to directly serve whatever folder it is you are serving your - downloads in. This also gives you the ability to serve them, for example, like this: - https://files.lolisafe.moe/yourFile.jpg + If you set it to `false`, you need to set nginx to directly serve whatever folder it is you are serving your + downloads in. This also gives you the ability to serve them, for example, like this: + https://files.lolisafe.moe/yourFile.jpg - Both cases require you to type the domain where the files will be served on the `domain` key below. - Which one you use is ultimately up to you. - */ - serveFilesWithNode: false, - domain: 'https://lolisafe.moe', + Both cases require you to type the domain where the files will be served on the `domain` key below. + Which one you use is ultimately up to you. + */ + serveFilesWithNode: false, + domain: 'https://lolisafe.moe', - // Port on which to run the server - port: 9999, + // Port on which to run the server + port: 9999, - // Pages to process for the frontend - pages: ['home', 'auth', 'dashboard', 'faq'], + // Pages to process for the frontend + pages: ['home', 'auth', 'dashboard', 'faq'], - // Add file extensions here which should be blocked - blockedExtensions: [ - '.exe', - '.bat', - '.cmd', - '.msi', - '.sh' - ], + // Add file extensions here which should be blocked + blockedExtensions: [ + '.exe', + '.bat', + '.cmd', + '.msi', + '.sh' + ], - // Uploads config - uploads: { + // Uploads config + uploads: { - // Folder where images should be stored - folder: 'uploads', + // Folder where images should be stored + folder: 'uploads', - /* - Max file size allowed. Needs to be in MB - Note: When maxSize is greater than 1 MiB, you must set the client_max_body_size to the same as maxSize. - */ - maxSize: '512MB', + /* + Max file size allowed. Needs to be in MB + Note: When maxSize is greater than 1 MiB, you must set the client_max_body_size to the same as maxSize. + */ + maxSize: '512MB', - // The length of the random generated name for the uploaded files - fileLength: 32, + // The length of the random generated name for the uploaded files + fileLength: 32, - /* - NOTE: Thumbnails are only for the admin panel and they require you - to install a separate binary called graphicsmagick (http://www.graphicsmagick.org) - for images and ffmpeg (https://ffmpeg.org/) for video files - */ - generateThumbnails: false, + /* + NOTE: Thumbnails are only for the admin panel and they require you + to install a separate binary called graphicsmagick (http://www.graphicsmagick.org) + for images and ffmpeg (https://ffmpeg.org/) for video files + */ + generateThumbnails: false, - /* - Allows users to download a .zip file of all files in an album. - The file is generated when the user clicks the download button in the view - and is re-used if the album has not changed between download requests - */ - generateZips: true - }, + /* + Allows users to download a .zip file of all files in an album. + The file is generated when the user clicks the download button in the view + and is re-used if the album has not changed between download requests + */ + generateZips: true + }, - // Folder where to store logs - logsFolder: 'logs', + // Folder where to store logs + logsFolder: 'logs', - // The following values shouldn't be touched - database: { - client: 'sqlite3', - connection: { filename: './database/db' }, - useNullAsDefault: true - } -}; + // The following values shouldn't be touched + database: { + client: 'sqlite3', + connection: { filename: './database/db' }, + useNullAsDefault: true + } +} diff --git a/controllers/albumsController.js b/controllers/albumsController.js index ed57cac..2b27dfa 100644 --- a/controllers/albumsController.js +++ b/controllers/albumsController.js @@ -1,179 +1,178 @@ -const config = require('../config.js'); -const db = require('knex')(config.database); -const randomstring = require('randomstring'); -const utils = require('./utilsController.js'); -const path = require('path'); -const fs = require('fs'); -const Zip = require('jszip'); -const albumsController = {}; +const config = require('../config.js') +const db = require('knex')(config.database) +const randomstring = require('randomstring') +const utils = require('./utilsController.js') +const path = require('path') +const fs = require('fs') +const Zip = require('jszip') +const albumsController = {} albumsController.list = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - const fields = ['id', 'name']; - if (req.params.sidebar === undefined) { - fields.push('timestamp'); - fields.push('identifier'); - } + const fields = ['id', 'name'] + if (req.params.sidebar === undefined) { + fields.push('timestamp') + fields.push('identifier') + } - const albums = await db.table('albums').select(fields).where({ enabled: 1, userid: user.id }); - if (req.params.sidebar !== undefined) { - return res.json({ success: true, albums }); - } + const albums = await db.table('albums').select(fields).where({ enabled: 1, userid: user.id }) + if (req.params.sidebar !== undefined) { + return res.json({ success: true, albums }) + } - let ids = []; - for (let album of albums) { - album.date = new Date(album.timestamp * 1000) - album.date = utils.getPrettyDate(album.date) + let ids = [] + for (let album of albums) { + album.date = new Date(album.timestamp * 1000) + album.date = utils.getPrettyDate(album.date) - album.identifier = `${config.domain}/a/${album.identifier}`; - ids.push(album.id); - } + album.identifier = `${config.domain}/a/${album.identifier}` + ids.push(album.id) + } - const files = await db.table('files').whereIn('albumid', ids).select('albumid'); - const albumsCount = {}; + const files = await db.table('files').whereIn('albumid', ids).select('albumid') + const albumsCount = {} - for (let id of ids) albumsCount[id] = 0; - for (let file of files) albumsCount[file.albumid] += 1; - for (let album of albums) album.files = albumsCount[album.id]; + for (let id of ids) albumsCount[id] = 0 + for (let file of files) albumsCount[file.albumid] += 1 + for (let album of albums) album.files = albumsCount[album.id] - return res.json({ success: true, albums }); -}; + return res.json({ success: true, albums }) +} albumsController.create = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - const name = req.body.name; - if (name === undefined || name === '') { - return res.json({ success: false, description: 'No album name specified' }); - } + const name = req.body.name + if (name === undefined || name === '') { + return res.json({ success: false, description: 'No album name specified' }) + } - const album = await db.table('albums').where({ - name: name, - enabled: 1, - userid: user.id - }).first(); + const album = await db.table('albums').where({ + name: name, + enabled: 1, + userid: user.id + }).first() - if (album) { - return res.json({ success: false, description: 'There\'s already an album with that name' }) - } + if (album) { + return res.json({ success: false, description: 'There\'s already an album with that name' }) + } - await db.table('albums').insert({ - name: name, - enabled: 1, - userid: user.id, - identifier: randomstring.generate(8), - timestamp: Math.floor(Date.now() / 1000) - }); + await db.table('albums').insert({ + name: name, + enabled: 1, + userid: user.id, + identifier: randomstring.generate(8), + timestamp: Math.floor(Date.now() / 1000) + }) - return res.json({ success: true }); -}; + return res.json({ success: true }) +} albumsController.delete = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - const id = req.body.id; - if (id === undefined || id === '') { - return res.json({ success: false, description: 'No album specified' }); - } + const id = req.body.id + if (id === undefined || id === '') { + return res.json({ success: false, description: 'No album specified' }) + } - await db.table('albums').where({ id: id, userid: user.id }).update({ enabled: 0 }); - return res.json({ success: true }); -}; + await db.table('albums').where({ id: id, userid: user.id }).update({ enabled: 0 }) + return res.json({ success: true }) +} albumsController.rename = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - const id = req.body.id; - if (id === undefined || id === '') { - return res.json({ success: false, description: 'No album specified' }); - } + const id = req.body.id + if (id === undefined || id === '') { + return res.json({ success: false, description: 'No album specified' }) + } - const name = req.body.name; - if (name === undefined || name === '') { - return res.json({ success: false, description: 'No name specified' }); - } + const name = req.body.name + if (name === undefined || name === '') { + return res.json({ success: false, description: 'No name specified' }) + } - const album = await db.table('albums').where({ name: name, userid: user.id }).first(); - if (album) { - return res.json({ success: false, description: 'Name already in use' }) - } + const album = await db.table('albums').where({ name: name, userid: user.id }).first() + if (album) { + return res.json({ success: false, description: 'Name already in use' }) + } - await db.table('albums').where({ id: id, userid: user.id }).update({ name: name }) - return res.json({ success: true }); -}; + await db.table('albums').where({ id: id, userid: user.id }).update({ name: name }) + return res.json({ success: true }) +} albumsController.get = async (req, res, next) => { - const identifier = req.params.identifier; - if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }); + const identifier = req.params.identifier + if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }) - const album = await db.table('albums').where({ identifier, enabled: 1 }).first(); - if (!album) return res.json({ success: false, description: 'Album not found' }); + const album = await db.table('albums').where({ identifier, enabled: 1 }).first() + if (!album) return res.json({ success: false, description: 'Album not found' }) - const title = album.name; - const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC'); + const title = album.name + const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC') - for (let file of files) { - file.file = `${config.domain}/${file.name}`; + for (let file of files) { + file.file = `${config.domain}/${file.name}` - const ext = path.extname(file.name).toLowerCase(); - if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { - file.thumb = `${config.domain}/thumbs/${file.name.slice(0, -ext.length)}.png`; - } - } - - return res.json({ - success: true, - title: title, - count: files.length, - files - }); -}; + const ext = path.extname(file.name).toLowerCase() + if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { + file.thumb = `${config.domain}/thumbs/${file.name.slice(0, -ext.length)}.png` + } + } + return res.json({ + success: true, + title: title, + count: files.length, + files + }) +} albumsController.generateZip = async (req, res, next) => { - const identifier = req.params.identifier; - if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }); - if (!config.uploads.generateZips) return res.status(401).json({ success: false, description: 'Zip generation disabled' }); + const identifier = req.params.identifier + if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }) + if (!config.uploads.generateZips) return res.status(401).json({ success: false, description: 'Zip generation disabled' }) - const album = await db.table('albums').where({ identifier, enabled: 1 }).first(); - if (!album) return res.json({ success: false, description: 'Album not found' }); + const album = await db.table('albums').where({ identifier, enabled: 1 }).first() + if (!album) return res.json({ success: false, description: 'Album not found' }) - if (album.zipGeneratedAt > album.editedAt) { - const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`); - const fileName = `${album.name}.zip`; - return res.download(filePath, fileName); - } else { - console.log(`Generating zip for album identifier: ${identifier}`); - const files = await db.table('files').select('name').where('albumid', album.id); - if (files.length === 0) return res.json({ success: false, description: 'There are no files in the album' }); + if (album.zipGeneratedAt > album.editedAt) { + const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`) + const fileName = `${album.name}.zip` + return res.download(filePath, fileName) + } else { + console.log(`Generating zip for album identifier: ${identifier}`) + const files = await db.table('files').select('name').where('albumid', album.id) + if (files.length === 0) return res.json({ success: false, description: 'There are no files in the album' }) - const zipPath = path.join(__dirname, '..', config.uploads.folder, 'zips', `${album.identifier}.zip`); - let archive = new Zip(); + const zipPath = path.join(__dirname, '..', config.uploads.folder, 'zips', `${album.identifier}.zip`) + let archive = new Zip() - for (let file of files) { - try { - const exists = fs.statSync(path.join(__dirname, '..', config.uploads.folder, file.name)); - archive.file(file.name, fs.readFileSync(path.join(__dirname, '..', config.uploads.folder, file.name))); - } catch (err) { - console.log(err); - } - } + for (let file of files) { + try { + // const exists = fs.statSync(path.join(__dirname, '..', config.uploads.folder, file.name)) + archive.file(file.name, fs.readFileSync(path.join(__dirname, '..', config.uploads.folder, file.name))) + } catch (err) { + console.log(err) + } + } - archive - .generateNodeStream({ type: 'nodebuffer', streamFiles: true }) - .pipe(fs.createWriteStream(zipPath)) - .on('finish', async () => { - console.log(`Generated zip for album identifier: ${identifier}`); - await db.table('albums') - .where('id', album.id) - .update({ zipGeneratedAt: Math.floor(Date.now() / 1000) }); + archive + .generateNodeStream({ type: 'nodebuffer', streamFiles: true }) + .pipe(fs.createWriteStream(zipPath)) + .on('finish', async () => { + console.log(`Generated zip for album identifier: ${identifier}`) + await db.table('albums') + .where('id', album.id) + .update({ zipGeneratedAt: Math.floor(Date.now() / 1000) }) - const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`); - const fileName = `${album.name}.zip`; - return res.download(filePath, fileName); - }); - } -}; + const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`) + const fileName = `${album.name}.zip` + return res.download(filePath, fileName) + }) + } +} -module.exports = albumsController; +module.exports = albumsController diff --git a/controllers/authController.js b/controllers/authController.js index 95a151e..0dce932 100644 --- a/controllers/authController.js +++ b/controllers/authController.js @@ -1,86 +1,86 @@ -const config = require('../config.js'); -const db = require('knex')(config.database); -const bcrypt = require('bcrypt'); -const randomstring = require('randomstring'); -const utils = require('./utilsController.js'); +const config = require('../config.js') +const db = require('knex')(config.database) +const bcrypt = require('bcrypt') +const randomstring = require('randomstring') +const utils = require('./utilsController.js') -let authController = {}; +let authController = {} authController.verify = async (req, res, next) => { - const username = req.body.username; - const password = req.body.password; + const username = req.body.username + const password = req.body.password - if (username === undefined) return res.json({ success: false, description: 'No username provided' }); - if (password === undefined) return res.json({ success: false, description: 'No password provided' }); + if (username === undefined) return res.json({ success: false, description: 'No username provided' }) + if (password === undefined) return res.json({ success: false, description: 'No password provided' }) - const user = await db.table('users').where('username', username).first(); - if (!user) return res.json({ success: false, description: 'Username doesn\'t exist' }); + const user = await db.table('users').where('username', username).first() + if (!user) return res.json({ success: false, description: 'Username doesn\'t exist' }) - bcrypt.compare(password, user.password, (err, result) => { - if (err) { - console.log(err); - return res.json({ success: false, description: 'There was an error' }); - } - if (result === false) return res.json({ success: false, description: 'Wrong password' }); - return res.json({ success: true, token: user.token }); - }); -}; + bcrypt.compare(password, user.password, (err, result) => { + if (err) { + console.log(err) + return res.json({ success: false, description: 'There was an error' }) + } + if (result === false) return res.json({ success: false, description: 'Wrong password' }) + return res.json({ success: true, token: user.token }) + }) +} authController.register = async (req, res, next) => { - if (config.enableUserAccounts === false) { - return res.json({ success: false, description: 'Register is disabled at the moment' }); - } + if (config.enableUserAccounts === false) { + return res.json({ success: false, description: 'Register is disabled at the moment' }) + } - const username = req.body.username; - const password = req.body.password; + const username = req.body.username + const password = req.body.password - if (username === undefined) return res.json({ success: false, description: 'No username provided' }); - if (password === undefined) return res.json({ success: false, description: 'No password provided' }); + if (username === undefined) return res.json({ success: false, description: 'No username provided' }) + if (password === undefined) return res.json({ success: false, description: 'No password provided' }) - if (username.length < 4 || username.length > 32) { - return res.json({ success: false, description: 'Username must have 4-32 characters' }) - } - if (password.length < 6 || password.length > 64) { - return res.json({ success: false, description: 'Password must have 6-64 characters' }) - } + if (username.length < 4 || username.length > 32) { + return res.json({ success: false, description: 'Username must have 4-32 characters' }) + } + if (password.length < 6 || password.length > 64) { + return res.json({ success: false, description: 'Password must have 6-64 characters' }) + } - const user = await db.table('users').where('username', username).first(); - if (user) return res.json({ success: false, description: 'Username already exists' }); + const user = await db.table('users').where('username', username).first() + if (user) return res.json({ success: false, description: 'Username already exists' }) - bcrypt.hash(password, 10, async (err, hash) => { - if (err) { - console.log(err); - return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }); - } - const token = randomstring.generate(64); - await db.table('users').insert({ - username: username, - password: hash, - token: token - }); - return res.json({ success: true, token: token }) - }); -}; + bcrypt.hash(password, 10, async (err, hash) => { + if (err) { + console.log(err) + return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }) + } + const token = randomstring.generate(64) + await db.table('users').insert({ + username: username, + password: hash, + token: token + }) + return res.json({ success: true, token: token }) + }) +} authController.changePassword = async (req, res, next) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - let password = req.body.password; - if (password === undefined) return res.json({ success: false, description: 'No password provided' }); + let password = req.body.password + if (password === undefined) return res.json({ success: false, description: 'No password provided' }) - if (password.length < 6 || password.length > 64) { - return res.json({ success: false, description: 'Password must have 6-64 characters' }); - } + if (password.length < 6 || password.length > 64) { + return res.json({ success: false, description: 'Password must have 6-64 characters' }) + } - bcrypt.hash(password, 10, async (err, hash) => { - if (err) { - console.log(err); - return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }); - } + bcrypt.hash(password, 10, async (err, hash) => { + if (err) { + console.log(err) + return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }) + } - await db.table('users').where('id', user.id).update({ password: hash }); - return res.json({ success: true }); - }); -}; + await db.table('users').where('id', user.id).update({ password: hash }) + return res.json({ success: true }) + }) +} -module.exports = authController; +module.exports = authController diff --git a/controllers/tokenController.js b/controllers/tokenController.js index cbcc550..bb8f9c9 100644 --- a/controllers/tokenController.js +++ b/controllers/tokenController.js @@ -1,34 +1,34 @@ -const config = require('../config.js'); -const db = require('knex')(config.database); -const randomstring = require('randomstring'); -const utils = require('./utilsController.js'); +const config = require('../config.js') +const db = require('knex')(config.database) +const randomstring = require('randomstring') +const utils = require('./utilsController.js') -const tokenController = {}; +const tokenController = {} tokenController.verify = async (req, res, next) => { - const token = req.body.token; - if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }); + const token = req.body.token + if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - const user = await db.table('users').where('token', token).first(); - if (!user) return res.status(401).json({ success: false, description: 'Invalid token' }); - return res.json({ success: true, username: user.username }); -}; + const user = await db.table('users').where('token', token).first() + if (!user) return res.status(401).json({ success: false, description: 'Invalid token' }) + return res.json({ success: true, username: user.username }) +} tokenController.list = async (req, res, next) => { - const user = await utils.authorize(req, res); - return res.json({ success: true, token: user.token }); -}; + const user = await utils.authorize(req, res) + return res.json({ success: true, token: user.token }) +} tokenController.change = async (req, res, next) => { - const user = await utils.authorize(req, res); - const newtoken = randomstring.generate(64); + const user = await utils.authorize(req, res) + const newtoken = randomstring.generate(64) - await db.table('users').where('token', user.token).update({ - token: newtoken, - timestamp: Math.floor(Date.now() / 1000) - }); + await db.table('users').where('token', user.token).update({ + token: newtoken, + timestamp: Math.floor(Date.now() / 1000) + }) - res.json({ success: true, token: newtoken }); -}; + res.json({ success: true, token: newtoken }) +} -module.exports = tokenController; +module.exports = tokenController diff --git a/controllers/uploadController.js b/controllers/uploadController.js index 3fb9b55..94eb9a8 100644 --- a/controllers/uploadController.js +++ b/controllers/uploadController.js @@ -1,285 +1,285 @@ -const config = require('../config.js'); -const path = require('path'); -const multer = require('multer'); -const randomstring = require('randomstring'); -const db = require('knex')(config.database); -const crypto = require('crypto'); -const fs = require('fs'); -const utils = require('./utilsController.js'); +const config = require('../config.js') +const path = require('path') +const multer = require('multer') +const randomstring = require('randomstring') +const db = require('knex')(config.database) +const crypto = require('crypto') +const fs = require('fs') +const utils = require('./utilsController.js') -const uploadsController = {}; +const uploadsController = {} const storage = multer.diskStorage({ - destination: function(req, file, cb) { - cb(null, path.join(__dirname, '..', config.uploads.folder)); - }, - filename: function(req, file, cb) { - cb(null, randomstring.generate(config.uploads.fileLength) + path.extname(file.originalname)); - } -}); + destination: function (req, file, cb) { + cb(null, path.join(__dirname, '..', config.uploads.folder)) + }, + filename: function (req, file, cb) { + cb(null, randomstring.generate(config.uploads.fileLength) + path.extname(file.originalname)) + } +}) const upload = multer({ - storage: storage, - limits: { fileSize: config.uploads.maxSize }, - fileFilter: function(req, file, cb) { - if (config.blockedExtensions !== undefined) { - if (config.blockedExtensions.some(extension => path.extname(file.originalname).toLowerCase() === extension)) { - return cb('This file extension is not allowed'); - } - return cb(null, true); - } - return cb(null, true); - } -}).array('files[]'); + storage: storage, + limits: { fileSize: config.uploads.maxSize }, + fileFilter: function (req, file, cb) { + if (config.blockedExtensions !== undefined) { + if (config.blockedExtensions.some(extension => path.extname(file.originalname).toLowerCase() === extension)) { + return cb('This file extension is not allowed') // eslint-disable-line standard/no-callback-literal + } + return cb(null, true) + } + return cb(null, true) + } +}).array('files[]') uploadsController.upload = async (req, res, next) => { - if (config.private === true) { - await utils.authorize(req, res); - } + if (config.private === true) { + await utils.authorize(req, res) + } - const token = req.headers.token || ''; - const user = await db.table('users').where('token', token).first(); - const albumid = req.headers.albumid || req.params.albumid; + const token = req.headers.token || '' + const user = await db.table('users').where('token', token).first() + const albumid = req.headers.albumid || req.params.albumid - if (albumid && user) { - const album = await db.table('albums').where({ id: albumid, userid: user.id }).first(); - if (!album) { - return res.json({ - success: false, - description: 'Album doesn\'t exist or it doesn\'t belong to the user' - }); - } - return uploadsController.actuallyUpload(req, res, user, albumid); - } - return uploadsController.actuallyUpload(req, res, user, albumid); -}; + if (albumid && user) { + const album = await db.table('albums').where({ id: albumid, userid: user.id }).first() + if (!album) { + return res.json({ + success: false, + description: 'Album doesn\'t exist or it doesn\'t belong to the user' + }) + } + return uploadsController.actuallyUpload(req, res, user, albumid) + } + return uploadsController.actuallyUpload(req, res, user, albumid) +} uploadsController.actuallyUpload = async (req, res, userid, album) => { - upload(req, res, async err => { - if (err) { - console.error(err); - return res.json({ success: false, description: err }); - } + upload(req, res, async err => { + if (err) { + console.error(err) + return res.json({ success: false, description: err }) + } - if (req.files.length === 0) return res.json({ success: false, description: 'no-files' }); + if (req.files.length === 0) return res.json({ success: false, description: 'no-files' }) - const files = []; - const existingFiles = []; - let iteration = 1; + const files = [] + const existingFiles = [] + let iteration = 1 - req.files.forEach(async file => { - // Check if the file exists by checking hash and size - let hash = crypto.createHash('md5'); - let stream = fs.createReadStream(path.join(__dirname, '..', config.uploads.folder, file.filename)); + req.files.forEach(async file => { + // Check if the file exists by checking hash and size + let hash = crypto.createHash('md5') + let stream = fs.createReadStream(path.join(__dirname, '..', config.uploads.folder, file.filename)) - stream.on('data', data => { - hash.update(data, 'utf8'); - }); + stream.on('data', data => { + hash.update(data, 'utf8') + }) - stream.on('end', async () => { - const fileHash = hash.digest('hex'); - const dbFile = await db.table('files') - .where(function() { - if (userid === undefined) this.whereNull('userid'); - else this.where('userid', userid.id); - }) - .where({ - hash: fileHash, - size: file.size - }) - .first(); + stream.on('end', async () => { + const fileHash = hash.digest('hex') + const dbFile = await db.table('files') + .where(function () { + if (userid === undefined) this.whereNull('userid') + else this.where('userid', userid.id) + }) + .where({ + hash: fileHash, + size: file.size + }) + .first() - if (!dbFile) { - files.push({ - name: file.filename, - original: file.originalname, - type: file.mimetype, - size: file.size, - hash: fileHash, - ip: req.ip, - albumid: album, - userid: userid.id, - timestamp: Math.floor(Date.now() / 1000) - }); - } else { - uploadsController.deleteFile(file.filename).then(() => {}).catch(err => console.error(err)); - existingFiles.push(dbFile); - } + if (!dbFile) { + files.push({ + name: file.filename, + original: file.originalname, + type: file.mimetype, + size: file.size, + hash: fileHash, + ip: req.ip, + albumid: album, + userid: userid ? userid.id : null, + timestamp: Math.floor(Date.now() / 1000) + }) + } else { + uploadsController.deleteFile(file.filename).then(() => {}).catch(err => console.error(err)) + existingFiles.push(dbFile) + } - if (iteration === req.files.length) { - return uploadsController.processFilesForDisplay(req, res, files, existingFiles); - } - iteration++; - }); - }); - }); -}; + if (iteration === req.files.length) { + return uploadsController.processFilesForDisplay(req, res, files, existingFiles) + } + iteration++ + }) + }) + }) +} uploadsController.processFilesForDisplay = async (req, res, files, existingFiles) => { - let basedomain = config.domain; - if (files.length === 0) { - return res.json({ - success: true, - files: existingFiles.map(file => { - return { - name: file.name, - size: file.size, - url: `${basedomain}/${file.name}` - }; - }) - }); - } + let basedomain = config.domain + if (files.length === 0) { + return res.json({ + success: true, + files: existingFiles.map(file => { + return { + name: file.name, + size: file.size, + url: `${basedomain}/${file.name}` + } + }) + }) + } - await db.table('files').insert(files); - for (let efile of existingFiles) files.push(efile); + await db.table('files').insert(files) + for (let efile of existingFiles) files.push(efile) - res.json({ - success: true, - files: files.map(file => { - return { - name: file.name, - size: file.size, - url: `${basedomain}/${file.name}` - }; - }) - }); + res.json({ + success: true, + files: files.map(file => { + return { + name: file.name, + size: file.size, + url: `${basedomain}/${file.name}` + } + }) + }) - for (let file of files) { - let ext = path.extname(file.name).toLowerCase(); - if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { - file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`; - utils.generateThumbs(file); - } + for (let file of files) { + let ext = path.extname(file.name).toLowerCase() + if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { + file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png` + utils.generateThumbs(file) + } - if (file.albumid) { - db.table('albums').where('id', file.albumid).update('editedAt', file.timestamp).then(() => {}) - .catch(error => { console.log(error); res.json({ success: false, description: 'Error updating album' }); }); - } - } -}; + if (file.albumid) { + db.table('albums').where('id', file.albumid).update('editedAt', file.timestamp).then(() => {}) + .catch(error => { console.log(error); res.json({ success: false, description: 'Error updating album' }) }) + } + } +} uploadsController.delete = async (req, res) => { - const user = await utils.authorize(req, res); - const id = req.body.id; - if (id === undefined || id === '') { - return res.json({ success: false, description: 'No file specified' }); - } + const user = await utils.authorize(req, res) + const id = req.body.id + if (id === undefined || id === '') { + return res.json({ success: false, description: 'No file specified' }) + } - const file = await db.table('files') - .where('id', id) - .where(function() { - if (user.username !== 'root') { - this.where('userid', user.id); - } - }) - .first(); + const file = await db.table('files') + .where('id', id) + .where(function () { + if (user.username !== 'root') { + this.where('userid', user.id) + } + }) + .first() - try { - await uploadsController.deleteFile(file.name); - await db.table('files').where('id', id).del(); - if (file.albumid) { - await db.table('albums').where('id', file.albumid).update('editedAt', Math.floor(Date.now() / 1000)); - } - } catch (err) { - console.log(err); - } + try { + await uploadsController.deleteFile(file.name) + await db.table('files').where('id', id).del() + if (file.albumid) { + await db.table('albums').where('id', file.albumid).update('editedAt', Math.floor(Date.now() / 1000)) + } + } catch (err) { + console.log(err) + } - return res.json({ success: true }); -}; + return res.json({ success: true }) +} -uploadsController.deleteFile = function(file) { - const ext = path.extname(file).toLowerCase(); - return new Promise((resolve, reject) => { - fs.stat(path.join(__dirname, '..', config.uploads.folder, file), (err, stats) => { - if (err) { return reject(err); } - fs.unlink(path.join(__dirname, '..', config.uploads.folder, file), err => { - if (err) { return reject(err); } - if (!utils.imageExtensions.includes(ext) && !utils.videoExtensions.includes(ext)) { - return resolve(); - } - file = file.substr(0, file.lastIndexOf('.')) + '.png'; - fs.stat(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), (err, stats) => { - if (err) { - console.log(err); - return resolve(); - } - fs.unlink(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), err => { - if (err) { return reject(err); } - return resolve(); - }); - }); - }); - }); - }); -}; +uploadsController.deleteFile = function (file) { + const ext = path.extname(file).toLowerCase() + return new Promise((resolve, reject) => { + fs.stat(path.join(__dirname, '..', config.uploads.folder, file), (err, stats) => { + if (err) { return reject(err) } + fs.unlink(path.join(__dirname, '..', config.uploads.folder, file), err => { + if (err) { return reject(err) } + if (!utils.imageExtensions.includes(ext) && !utils.videoExtensions.includes(ext)) { + return resolve() + } + file = file.substr(0, file.lastIndexOf('.')) + '.png' + fs.stat(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), (err, stats) => { + if (err) { + console.log(err) + return resolve() + } + fs.unlink(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), err => { + if (err) { return reject(err) } + return resolve() + }) + }) + }) + }) + }) +} uploadsController.list = async (req, res) => { - const user = await utils.authorize(req, res); + const user = await utils.authorize(req, res) - let offset = req.params.page; - if (offset === undefined) offset = 0; + let offset = req.params.page + if (offset === undefined) offset = 0 - const files = await db.table('files') - .where(function() { - if (req.params.id === undefined) this.where('id', '<>', ''); - else this.where('albumid', req.params.id); - }) - .where(function() { - if (user.username !== 'root') this.where('userid', user.id); - }) - .orderBy('id', 'DESC') - .limit(25) - .offset(25 * offset) - .select('id', 'albumid', 'timestamp', 'name', 'userid'); + const files = await db.table('files') + .where(function () { + if (req.params.id === undefined) this.where('id', '<>', '') + else this.where('albumid', req.params.id) + }) + .where(function () { + if (user.username !== 'root') this.where('userid', user.id) + }) + .orderBy('id', 'DESC') + .limit(25) + .offset(25 * offset) + .select('id', 'albumid', 'timestamp', 'name', 'userid') - const albums = await db.table('albums'); - let basedomain = config.domain; - let userids = []; + const albums = await db.table('albums') + let basedomain = config.domain + let userids = [] - for (let file of files) { - file.file = `${basedomain}/${file.name}`; - file.date = new Date(file.timestamp * 1000); - file.date = utils.getPrettyDate(file.date); + for (let file of files) { + file.file = `${basedomain}/${file.name}` + file.date = new Date(file.timestamp * 1000) + file.date = utils.getPrettyDate(file.date) - file.album = ''; + file.album = '' - if (file.albumid !== undefined) { - for (let album of albums) { - if (file.albumid === album.id) { - file.album = album.name; - } - } - } + if (file.albumid !== undefined) { + for (let album of albums) { + if (file.albumid === album.id) { + file.album = album.name + } + } + } - // Only push usernames if we are root - if (user.username === 'root') { - if (file.userid !== undefined && file.userid !== null && file.userid !== '') { - userids.push(file.userid); - } - } + // Only push usernames if we are root + if (user.username === 'root') { + if (file.userid !== undefined && file.userid !== null && file.userid !== '') { + userids.push(file.userid) + } + } - let ext = path.extname(file.name).toLowerCase(); - if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { - file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`; - } - } + let ext = path.extname(file.name).toLowerCase() + if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { + file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png` + } + } - // If we are a normal user, send response - if (user.username !== 'root') return res.json({ success: true, files }); + // If we are a normal user, send response + if (user.username !== 'root') return res.json({ success: true, files }) - // If we are root but there are no uploads attached to a user, send response - if (userids.length === 0) return res.json({ success: true, files }); + // If we are root but there are no uploads attached to a user, send response + if (userids.length === 0) return res.json({ success: true, files }) - const users = await db.table('users').whereIn('id', userids); - for (let dbUser of users) { - for (let file of files) { - if (file.userid === dbUser.id) { - file.username = dbUser.username; - } - } - } + const users = await db.table('users').whereIn('id', userids) + for (let dbUser of users) { + for (let file of files) { + if (file.userid === dbUser.id) { + file.username = dbUser.username + } + } + } - return res.json({ success: true, files }); -}; + return res.json({ success: true, files }) +} -module.exports = uploadsController; +module.exports = uploadsController diff --git a/controllers/utilsController.js b/controllers/utilsController.js index ebfb36d..5cd2170 100644 --- a/controllers/utilsController.js +++ b/controllers/utilsController.js @@ -1,67 +1,67 @@ -const path = require('path'); -const config = require('../config.js'); -const fs = require('fs'); -const gm = require('gm'); -const ffmpeg = require('fluent-ffmpeg'); -const db = require('knex')(config.database); +const path = require('path') +const config = require('../config.js') +const fs = require('fs') +const gm = require('gm') +const ffmpeg = require('fluent-ffmpeg') +const db = require('knex')(config.database) -const utilsController = {}; -utilsController.imageExtensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png']; -utilsController.videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov']; +const utilsController = {} +utilsController.imageExtensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png'] +utilsController.videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov'] -utilsController.getPrettyDate = function(date) { - return date.getFullYear() + '-' - + (date.getMonth() + 1) + '-' - + date.getDate() + ' ' - + (date.getHours() < 10 ? '0' : '') - + date.getHours() + ':' - + (date.getMinutes() < 10 ? '0' : '') - + date.getMinutes() + ':' - + (date.getSeconds() < 10 ? '0' : '') - + date.getSeconds(); +utilsController.getPrettyDate = function (date) { + return date.getFullYear() + '-' + + (date.getMonth() + 1) + '-' + + date.getDate() + ' ' + + (date.getHours() < 10 ? '0' : '') + + date.getHours() + ':' + + (date.getMinutes() < 10 ? '0' : '') + + date.getMinutes() + ':' + + (date.getSeconds() < 10 ? '0' : '') + + date.getSeconds() } utilsController.authorize = async (req, res) => { - const token = req.headers.token; - if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }); + const token = req.headers.token + if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - const user = await db.table('users').where('token', token).first(); - if (!user) return res.status(401).json({ success: false, description: 'Invalid token' }); - return user; -}; + const user = await db.table('users').where('token', token).first() + if (!user) return res.status(401).json({ success: false, description: 'Invalid token' }) + return user +} -utilsController.generateThumbs = function(file, basedomain) { - if (config.uploads.generateThumbnails !== true) return; - const ext = path.extname(file.name).toLowerCase(); +utilsController.generateThumbs = function (file, basedomain) { + if (config.uploads.generateThumbnails !== true) return + const ext = path.extname(file.name).toLowerCase() - let thumbname = path.join(__dirname, '..', config.uploads.folder, 'thumbs', file.name.slice(0, -ext.length) + '.png'); - fs.access(thumbname, err => { - if (err && err.code === 'ENOENT') { - if (utilsController.videoExtensions.includes(ext)) { - ffmpeg(path.join(__dirname, '..', config.uploads.folder, file.name)) - .thumbnail({ - timestamps: [0], - filename: '%b.png', - folder: path.join(__dirname, '..', config.uploads.folder, 'thumbs'), - size: '200x?' - }) - .on('error', error => console.log('Error - ', error.message)); - } else { - let size = { - width: 200, - height: 200 - }; - gm(path.join(__dirname, '..', config.uploads.folder, file.name)) - .resize(size.width, size.height + '>') - .gravity('Center') - .extent(size.width, size.height) - .background('transparent') - .write(thumbname, error => { - if (error) console.log('Error - ', error); - }); - } - } - }); -}; + let thumbname = path.join(__dirname, '..', config.uploads.folder, 'thumbs', file.name.slice(0, -ext.length) + '.png') + fs.access(thumbname, err => { + if (err && err.code === 'ENOENT') { + if (utilsController.videoExtensions.includes(ext)) { + ffmpeg(path.join(__dirname, '..', config.uploads.folder, file.name)) + .thumbnail({ + timestamps: [0], + filename: '%b.png', + folder: path.join(__dirname, '..', config.uploads.folder, 'thumbs'), + size: '200x?' + }) + .on('error', error => console.log('Error - ', error.message)) + } else { + let size = { + width: 200, + height: 200 + } + gm(path.join(__dirname, '..', config.uploads.folder, file.name)) + .resize(size.width, size.height + '>') + .gravity('Center') + .extent(size.width, size.height) + .background('transparent') + .write(thumbname, error => { + if (error) console.log('Error - ', error) + }) + } + } + }) +} -module.exports = utilsController; +module.exports = utilsController diff --git a/database/db.js b/database/db.js index f1ec75a..5e9be2b 100644 --- a/database/db.js +++ b/database/db.js @@ -1,50 +1,49 @@ -let init = function(db){ +let init = function (db) { + // Create the tables we need to store galleries and files + db.schema.createTableIfNotExists('albums', function (table) { + table.increments() + table.integer('userid') + table.string('name') + table.string('identifier') + table.integer('enabled') + table.integer('timestamp') + }).then(() => {}) - // Create the tables we need to store galleries and files - db.schema.createTableIfNotExists('albums', function (table) { - table.increments() - table.integer('userid') - table.string('name') - table.string('identifier') - table.integer('enabled') - table.integer('timestamp') - }).then(() => {}) + db.schema.createTableIfNotExists('files', function (table) { + table.increments() + table.integer('userid') + table.string('name') + table.string('original') + table.string('type') + table.string('size') + table.string('hash') + table.string('ip') + table.integer('albumid') + table.integer('timestamp') + }).then(() => {}) - db.schema.createTableIfNotExists('files', function (table) { - table.increments() - table.integer('userid') - table.string('name') - table.string('original') - table.string('type') - table.string('size') - table.string('hash') - table.string('ip') - table.integer('albumid') - table.integer('timestamp') - }).then(() => {}) + db.schema.createTableIfNotExists('users', function (table) { + table.increments() + table.string('username') + table.string('password') + table.string('token') + table.integer('timestamp') + }).then(() => { + db.table('users').where({username: 'root'}).then((user) => { + if (user.length > 0) return - db.schema.createTableIfNotExists('users', function (table) { - table.increments() - table.string('username') - table.string('password') - table.string('token') - table.integer('timestamp') - }).then(() => { - db.table('users').where({username: 'root'}).then((user) => { - if(user.length > 0) return + require('bcrypt').hash('root', 10, function (err, hash) { + if (err) console.error('Error generating password hash for root') - require('bcrypt').hash('root', 10, function(err, hash) { - if(err) console.error('Error generating password hash for root') - - db.table('users').insert({ - username: 'root', - password: hash, - token: require('randomstring').generate(64), - timestamp: Math.floor(Date.now() / 1000) - }).then(() => {}) - }) - }) - }) + db.table('users').insert({ + username: 'root', + password: hash, + token: require('randomstring').generate(64), + timestamp: Math.floor(Date.now() / 1000) + }).then(() => {}) + }) + }) + }) } -module.exports = init \ No newline at end of file +module.exports = init diff --git a/database/migration.js b/database/migration.js index fb30779..4b2febf 100644 --- a/database/migration.js +++ b/database/migration.js @@ -1,13 +1,13 @@ -const config = require('../config.js'); -const db = require('knex')(config.database); +const config = require('../config.js') +const db = require('knex')(config.database) -const migration = {}; +const migration = {} migration.start = async () => { - await db.schema.table('albums', table => { - table.dateTime('editedAt'); - table.dateTime('zipGeneratedAt'); - }); - console.log('Migration finished! Now start lolisafe normally'); -}; + await db.schema.table('albums', table => { + table.dateTime('editedAt') + table.dateTime('zipGeneratedAt') + }) + console.log('Migration finished! Now start lolisafe normally') +} -migration.start(); +migration.start() diff --git a/lolisafe.js b/lolisafe.js index f4c3315..c6e2352 100644 --- a/lolisafe.js +++ b/lolisafe.js @@ -1,58 +1,66 @@ -const config = require('./config.js'); -const api = require('./routes/api.js'); -const album = require('./routes/album.js'); -const express = require('express'); -const helmet = require('helmet'); -const bodyParser = require('body-parser'); -const RateLimit = require('express-rate-limit'); -const db = require('knex')(config.database); -const fs = require('fs'); -const exphbs = require('express-handlebars'); -const safe = express(); +const config = require('./config.js') +const api = require('./routes/api.js') +const album = require('./routes/album.js') +const express = require('express') +const helmet = require('helmet') +const bodyParser = require('body-parser') +const RateLimit = require('express-rate-limit') +const db = require('knex')(config.database) +const fs = require('fs') +const exphbs = require('express-handlebars') +const safe = express() -require('./database/db.js')(db); +require('./database/db.js')(db) -fs.existsSync('./pages/custom') || fs.mkdirSync('./pages/custom'); -fs.existsSync(`./${config.logsFolder}`) || fs.mkdirSync(`./${ config.logsFolder}`); -fs.existsSync(`./${config.uploads.folder}`) || fs.mkdirSync(`./${config.uploads.folder}`); -fs.existsSync(`./${config.uploads.folder}/thumbs`) || fs.mkdirSync(`./${config.uploads.folder }/thumbs`); -fs.existsSync(`./${config.uploads.folder }/zips`) || fs.mkdirSync(`./${config.uploads.folder}/zips`); +fs.existsSync('./pages/custom') || fs.mkdirSync('./pages/custom') +fs.existsSync('./' + config.logsFolder) || fs.mkdirSync('./' + config.logsFolder) +fs.existsSync('./' + config.uploads.folder) || fs.mkdirSync('./' + config.uploads.folder) +fs.existsSync('./' + config.uploads.folder + '/thumbs') || fs.mkdirSync('./' + config.uploads.folder + '/thumbs') +fs.existsSync('./' + config.uploads.folder + '/zips') || fs.mkdirSync('./' + config.uploads.folder + '/zips') -safe.use(helmet()); -safe.set('trust proxy', 1); +safe.use(helmet()) +safe.set('trust proxy', 1) -safe.engine('handlebars', exphbs({ defaultLayout: 'main' })); -safe.set('view engine', 'handlebars'); -safe.enable('view cache'); +safe.engine('handlebars', exphbs({ defaultLayout: 'main' })) +safe.set('view engine', 'handlebars') +safe.enable('view cache') -let limiter = new RateLimit({ windowMs: 5000, max: 2 }); -safe.use('/api/login/', limiter); -safe.use('/api/register/', limiter); +let limiter = new RateLimit({ windowMs: 5000, max: 2 }) +safe.use('/api/login/', limiter) +safe.use('/api/register/', limiter) -safe.use(bodyParser.urlencoded({ extended: true })); -safe.use(bodyParser.json()); +safe.use(bodyParser.urlencoded({ extended: true })) +safe.use(bodyParser.json()) if (config.serveFilesWithNode) { - safe.use('/', express.static(config.uploads.folder)); + safe.use('/', express.static(config.uploads.folder)) } -safe.use('/', express.static('./public')); -safe.use('/', album); -safe.use('/api', api); +safe.use('/', express.static('./public')) +safe.use('/', album) +safe.use('/api', api) for (let page of config.pages) { - let root = './pages/'; - if (fs.existsSync(`./pages/custom/${page}.html`)) { - root = './pages/custom/'; - } - if (page === 'home') { - safe.get('/', (req, res, next) => res.sendFile(`${page}.html`, { root: root })); - } else { - safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, { root: root })); - } + let root = './pages/' + if (fs.existsSync(`./pages/custom/${page}.html`)) { + root = './pages/custom/' + } + if (page === 'home') { + safe.get('/', (req, res, next) => res.sendFile(`${page}.html`, { root: root })) + } else { + safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, { root: root })) + } } -safe.use((req, res, next) => res.status(404).sendFile('404.html', { root: './pages/error/' })); -safe.use((req, res, next) => res.status(500).sendFile('500.html', { root: './pages/error/' })); +safe.use((req, res, next) => res.status(404).sendFile('404.html', { root: './pages/error/' })) +safe.use((req, res, next) => res.status(500).sendFile('500.html', { root: './pages/error/' })) -safe.listen(config.port, () => console.log(`lolisafe started on port ${config.port}`)); +safe.listen(config.port, () => console.log(`lolisafe started on port ${config.port}`)) + +process.on('uncaughtException', err => { + console.error(`Uncaught Exception:\n${err.stack}`) +}) + +process.on('unhandledRejection', err => { + console.error(`Unhandled Rejection (Promise):\n${err.stack}`) +}) diff --git a/package.json b/package.json index e9a2396..555bef2 100644 --- a/package.json +++ b/package.json @@ -30,19 +30,9 @@ "sqlite3": "^3.1.13" }, "devDependencies": { - "eslint": "^4.4.1", - "eslint-config-aqua": "^1.5.0" + "standard": "^10.0.3" }, - "eslintConfig": { - "extends": [ - "aqua" - ], - "env": { - "browser": true, - "node": true - }, - "rules": { - "func-names": 0 - } + "standard": { + "envs": ["browser", "node"] } } diff --git a/pages/auth.html b/pages/auth.html index 85a38f4..587c49a 100644 --- a/pages/auth.html +++ b/pages/auth.html @@ -39,6 +39,7 @@ + +
diff --git a/pages/dashboard.html b/pages/dashboard.html index 9739dee..5ba6561 100644 --- a/pages/dashboard.html +++ b/pages/dashboard.html @@ -39,6 +39,7 @@ + + diff --git a/public/images/icons/browserconfig.xml b/public/images/icons/browserconfig.xml index 1c28f50..3c5d9d1 100644 --- a/public/images/icons/browserconfig.xml +++ b/public/images/icons/browserconfig.xml @@ -1,9 +1,9 @@File | -${albumOrUser} | -Date | -- |
---|
- - Submit -
- -Name | -Files | -Created At | -Public link | -- |
---|
- - Request new token -
- `; - - panel.page.appendChild(container); - - document.getElementById('getNewToken').addEventListener('click', () => { - panel.getNewToken(); - }); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.getNewToken = function() { - axios.post('/api/tokens/change') - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - swal({ - title: 'Woohoo!', - text: 'Your token was changed successfully.', - type: 'success' - }, () => { - localStorage.token = response.data.token; - location.reload(); - }); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.changePassword = function() { - panel.page.innerHTML = ''; - var container = document.createElement('div'); - container.className = 'container'; - container.innerHTML = ` -- -
- -- - Set new password -
- `; - - panel.page.appendChild(container); - - document.getElementById('sendChangePassword').addEventListener('click', () => { - if (document.getElementById('password').value === document.getElementById('passwordConfirm').value) { - panel.sendNewPassword(document.getElementById('password').value); - } else { - swal({ - title: 'Password mismatch!', - text: 'Your passwords do not match, please try again.', - type: 'error' - }, () => { - panel.changePassword(); - }); - } - }); -}; - -panel.sendNewPassword = function(pass) { - axios.post('/api/password/change', { password: pass }) - .then(response => { - if (response.data.success === false) { - if (response.data.description === 'No token provided') return panel.verifyToken(panel.token); - else return swal('An error ocurred', response.data.description, 'error'); - } - - swal({ - title: 'Woohoo!', - text: 'Your password was changed successfully.', - type: 'success' - }, () => { - location.reload(); - }); - }) - .catch(error => { - return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - console.log(error); - }); -}; - -panel.setActiveMenu = function(item) { - var menu = document.getElementById('menu'); - var items = menu.getElementsByTagName('a'); - for (var i = 0; i < items.length; i++) { items[i].className = ''; } - - item.className = 'is-active'; -}; - -window.onload = function() { - panel.preparePage(); -}; +/* eslint-disable no-unused-expressions */ +/* global swal, axios */ + +let panel = {} + +panel.page +panel.username +panel.token = localStorage.token +panel.filesView = localStorage.filesView + +panel.preparePage = function () { + if (!panel.token) { + window.location = '/auth' + return '/auth' + } + panel.verifyToken(panel.token, true) +} + +panel.verifyToken = function (token, reloadOnError) { + if (reloadOnError === undefined) { + reloadOnError = false + } + + axios.post('/api/tokens/verify', { + token: token + }) + .then(function (response) { + if (response.data.success === false) { + swal({ + title: 'An error ocurred', + text: response.data.description, + type: 'error' + }, function () { + if (reloadOnError) { + localStorage.removeItem('token') + location.location = '/auth' + } + }) + return + } + + axios.defaults.headers.common.token = token + localStorage.token = token + panel.token = token + panel.username = response.data.username + return panel.prepareDashboard() + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.prepareDashboard = function () { + panel.page = document.getElementById('page') + document.getElementById('auth').style.display = 'none' + document.getElementById('dashboard').style.display = 'block' + + document.getElementById('itemUploads').addEventListener('click', function () { + panel.setActiveMenu(this) + }) + + document.getElementById('itemManageGallery').addEventListener('click', function () { + panel.setActiveMenu(this) + }) + + document.getElementById('itemTokens').addEventListener('click', function () { + panel.setActiveMenu(this) + }) + + document.getElementById('itemPassword').addEventListener('click', function () { + panel.setActiveMenu(this) + }) + + document.getElementById('itemLogout').innerHTML = `Logout ( ${panel.username} )` + + panel.getAlbumsSidebar() +} + +panel.logout = function () { + localStorage.removeItem('token') + location.reload('/') +} + +panel.getUploads = function (album = undefined, page = undefined) { + if (page === undefined) page = 0 + + let url = '/api/uploads/' + page + if (album !== undefined) { url = '/api/album/' + album + '/' + page } + + axios.get(url).then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + var prevPage = 0 + var nextPage = page + 1 + + if (response.data.files.length < 25) { nextPage = page } + + if (page > 0) prevPage = page - 1 + + panel.page.innerHTML = '' + var container = document.createElement('div') + var pagination = `` + var listType = ` + ` + + var table, item + if (panel.filesView === 'thumbs') { + container.innerHTML = ` + ${pagination} +File | +${albumOrUser} | +Date | ++ |
---|
+ + Submit +
+ +Name | +Files | +Created At | +Public link | ++ |
---|
+ + Request new token +
+ ` + + panel.page.appendChild(container) + + document.getElementById('getNewToken').addEventListener('click', function () { + panel.getNewToken() + }) + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.getNewToken = function () { + axios.post('/api/tokens/change') + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + swal({ + title: 'Woohoo!', + text: 'Your token was changed successfully.', + type: 'success' + }, function () { + localStorage.token = response.data.token + location.reload() + }) + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.changePassword = function () { + panel.page.innerHTML = '' + var container = document.createElement('div') + container.className = 'container' + container.innerHTML = ` ++ +
+ ++ + Set new password +
+ ` + + panel.page.appendChild(container) + + document.getElementById('sendChangePassword').addEventListener('click', function () { + if (document.getElementById('password').value === document.getElementById('passwordConfirm').value) { + panel.sendNewPassword(document.getElementById('password').value) + } else { + swal({ + title: 'Password mismatch!', + text: 'Your passwords do not match, please try again.', + type: 'error' + }, function () { + panel.changePassword() + }) + } + }) +} + +panel.sendNewPassword = function (pass) { + axios.post('/api/password/change', {password: pass}) + .then(function (response) { + if (response.data.success === false) { + if (response.data.description === 'No token provided') return panel.verifyToken(panel.token) + else return swal('An error ocurred', response.data.description, 'error') + } + + swal({ + title: 'Woohoo!', + text: 'Your password was changed successfully.', + type: 'success' + }, function () { + location.reload() + }) + }) + .catch(function (error) { + console.log(error) + return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + }) +} + +panel.setActiveMenu = function (item) { + var menu = document.getElementById('menu') + var items = menu.getElementsByTagName('a') + for (var i = 0; i < items.length; i++) { items[i].className = '' } + + item.className = 'is-active' +} + +window.onload = function () { + panel.preparePage() +} diff --git a/public/js/home.js b/public/js/home.js index d932aec..4c6e876 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -1,172 +1,178 @@ -var upload = {}; +/* eslint-disable no-unused-expressions */ +/* global swal, axios, Dropzone */ -upload.isPrivate = true; -upload.token = localStorage.token; -upload.maxFileSize; +var upload = {} + +upload.isPrivate = true +upload.token = localStorage.token +upload.maxFileSize // Add the album var to the upload so we can store the album id in there -upload.album; -upload.myDropzone; +upload.album +upload.myDropzone -upload.checkIfPublic = function() { - axios.get('/api/check') - .then(response => { - upload.isPrivate = response.data.private; - upload.maxFileSize = response.data.maxFileSize; - upload.preparePage(); - }) - .catch(error => { - swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - return console.log(error); - }); -}; +upload.checkIfPublic = function () { + axios.get('/api/check') + .then(response => { + upload.isPrivate = response.data.private + upload.maxFileSize = response.data.maxFileSize + upload.preparePage() + }) + .catch(error => { + swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + return console.log(error) + }) +} -upload.preparePage = function() { - if (!upload.isPrivate) return upload.prepareUpload(); - if (!upload.token) return document.getElementById('loginToUpload').style.display = 'inline-flex'; - upload.verifyToken(upload.token, true); -}; +upload.preparePage = function () { + if (!upload.isPrivate) return upload.prepareUpload() + if (!upload.token) { + document.getElementById('loginToUpload').style.display = 'inline-flex' + return 'inline-flex' + } + upload.verifyToken(upload.token, true) +} -upload.verifyToken = function(token, reloadOnError) { - if (reloadOnError === undefined) { reloadOnError = false; } +upload.verifyToken = function (token, reloadOnError) { + if (reloadOnError === undefined) { reloadOnError = false } - axios.post('/api/tokens/verify', { token: token }) - .then(response => { - if (response.data.success === false) { - swal({ - title: 'An error ocurred', - text: response.data.description, - type: 'error' - }, () => { - if (reloadOnError) { - localStorage.removeItem('token'); - location.reload(); - } - }); - return; - } + axios.post('/api/tokens/verify', { token: token }) + .then(response => { + if (response.data.success === false) { + swal({ + title: 'An error ocurred', + text: response.data.description, + type: 'error' + }, () => { + if (reloadOnError) { + localStorage.removeItem('token') + location.reload() + } + }) + return + } - localStorage.token = token; - upload.token = token; - return upload.prepareUpload(); - }) - .catch(error => { - swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - return console.log(error); - }); -}; + localStorage.token = token + upload.token = token + return upload.prepareUpload() + }) + .catch(error => { + swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + return console.log(error) + }) +} -upload.prepareUpload = function() { - // I think this fits best here because we need to check for a valid token before we can get the albums - if (upload.token) { - var select = document.getElementById('albumSelect'); +upload.prepareUpload = function () { + // I think this fits best here because we need to check for a valid token before we can get the albums + if (upload.token) { + var select = document.getElementById('albumSelect') - select.addEventListener('change', () => { - upload.album = select.value; - }); + select.addEventListener('change', () => { + upload.album = select.value + }) - axios.get('/api/albums', { headers: { token: upload.token } }) - .then(res => { - var albums = res.data.albums; + axios.get('/api/albums', { headers: { token: upload.token } }) + .then(res => { + var albums = res.data.albums - // If the user doesn't have any albums we don't really need to display - // an album selection - if (albums.length === 0) return; + // If the user doesn't have any albums we don't really need to display + // an album selection + if (albums.length === 0) return - // Loop through the albums and create an option for each album - for (var i = 0; i < albums.length; i++) { - var opt = document.createElement('option'); - opt.value = albums[i].id; - opt.innerHTML = albums[i].name; - select.appendChild(opt); - } - // Display the album selection - document.getElementById('albumDiv').style.display = 'block'; - }) - .catch(e => { - swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error'); - return console.log(e); - }); - } + // Loop through the albums and create an option for each album + for (var i = 0; i < albums.length; i++) { + var opt = document.createElement('option') + opt.value = albums[i].id + opt.innerHTML = albums[i].name + select.appendChild(opt) + } + // Display the album selection + document.getElementById('albumDiv').style.display = 'block' + }) + .catch(e => { + swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error') + return console.log(e) + }) + } - div = document.createElement('div'); - div.id = 'dropzone'; - div.innerHTML = 'Click here or drag and drop files'; - div.style.display = 'flex'; + var div = document.createElement('div') + div.id = 'dropzone' + div.innerHTML = 'Click here or drag and drop files' + div.style.display = 'flex' - document.getElementById('maxFileSize').innerHTML = `Maximum upload size per file is ${upload.maxFileSize}`; - document.getElementById('loginToUpload').style.display = 'none'; + document.getElementById('maxFileSize').innerHTML = `Maximum upload size per file is ${upload.maxFileSize}` + document.getElementById('loginToUpload').style.display = 'none' - if (upload.token === undefined) { document.getElementById('loginLinkText').innerHTML = 'Create an account and keep track of your uploads'; } + if (upload.token === undefined) { document.getElementById('loginLinkText').innerHTML = 'Create an account and keep track of your uploads' } - document.getElementById('uploadContainer').appendChild(div); + document.getElementById('uploadContainer').appendChild(div) - upload.prepareDropzone(); -}; + upload.prepareDropzone() +} -upload.prepareDropzone = function() { - var previewNode = document.querySelector('#template'); - previewNode.id = ''; - var previewTemplate = previewNode.parentNode.innerHTML; - previewNode.parentNode.removeChild(previewNode); +upload.prepareDropzone = function () { + var previewNode = document.querySelector('#template') + previewNode.id = '' + var previewTemplate = previewNode.parentNode.innerHTML + previewNode.parentNode.removeChild(previewNode) - var dropzone = new Dropzone('div#dropzone', { - url: '/api/upload', - paramName: 'files[]', - maxFilesize: upload.maxFileSize.slice(0, -2), - parallelUploads: 2, - uploadMultiple: false, - previewsContainer: 'div#uploads', - previewTemplate: previewTemplate, - createImageThumbnails: false, - maxFiles: 1000, - autoProcessQueue: true, - headers: { token: upload.token }, - init: function() { - upload.myDropzone = this; - this.on('addedfile', file => { - document.getElementById('uploads').style.display = 'block'; - }); - // Add the selected albumid, if an album is selected, as a header - this.on('sending', (file, xhr) => { - if (upload.album) { - xhr.setRequestHeader('albumid', upload.album); - } - }); - } - }); + var dropzone = new Dropzone('div#dropzone', { + url: '/api/upload', + paramName: 'files[]', + maxFilesize: upload.maxFileSize.slice(0, -2), + parallelUploads: 2, + uploadMultiple: false, + previewsContainer: 'div#uploads', + previewTemplate: previewTemplate, + createImageThumbnails: false, + maxFiles: 1000, + autoProcessQueue: true, + headers: { token: upload.token }, + init: function () { + upload.myDropzone = this + this.on('addedfile', file => { + document.getElementById('uploads').style.display = 'block' + }) + // Add the selected albumid, if an album is selected, as a header + this.on('sending', (file, xhr) => { + if (upload.album) { + xhr.setRequestHeader('albumid', upload.album) + } + }) + } + }) - // Update the total progress bar - dropzone.on('uploadprogress', (file, progress) => { - file.previewElement.querySelector('.progress').setAttribute('value', progress); - file.previewElement.querySelector('.progress').innerHTML = `${progress}%`; - }); + // Update the total progress bar + dropzone.on('uploadprogress', (file, progress) => { + file.previewElement.querySelector('.progress').setAttribute('value', progress) + file.previewElement.querySelector('.progress').innerHTML = `${progress}%` + }) - dropzone.on('success', (file, response) => { - // Handle the responseText here. For example, add the text to the preview element: + dropzone.on('success', (file, response) => { + // Handle the responseText here. For example, add the text to the preview element: - if (response.success === false) { - var span = document.createElement('span'); - span.innerHTML = response.description; - file.previewTemplate.querySelector('.link').appendChild(span); - return; - } + if (response.success === false) { + var span = document.createElement('span') + span.innerHTML = response.description + file.previewTemplate.querySelector('.link').appendChild(span) + return + } - a = document.createElement('a'); - a.href = response.files[0].url; - a.target = '_blank'; - a.innerHTML = response.files[0].url; - file.previewTemplate.querySelector('.link').appendChild(a); + var a = document.createElement('a') + a.href = response.files[0].url + a.target = '_blank' + a.innerHTML = response.files[0].url + file.previewTemplate.querySelector('.link').appendChild(a) - file.previewTemplate.querySelector('.progress').style.display = 'none'; - }); + file.previewTemplate.querySelector('.progress').style.display = 'none' + }) - upload.prepareShareX(); -}; + upload.prepareShareX() +} -upload.prepareShareX = function() { - if (upload.token) { - var sharex_element = document.getElementById('ShareX'); - var sharex_file = `{\r\n\ +upload.prepareShareX = function () { + if (upload.token) { + var sharexElement = document.getElementById('ShareX') + var sharexFile = `{\r\n\ "Name": "${location.hostname}",\r\n\ "DestinationType": "ImageUploader, FileUploader",\r\n\ "RequestType": "POST",\r\n\ @@ -178,30 +184,29 @@ upload.prepareShareX = function() { "ResponseType": "Text",\r\n\ "URL": "$json:files[0].url$",\r\n\ "ThumbnailURL": "$json:files[0].url$"\r\n\ -}`; - var sharex_blob = new Blob([sharex_file], { type: 'application/octet-binary' }); - sharex_element.setAttribute('href', URL.createObjectURL(sharex_blob)); - sharex_element.setAttribute('download', `${location.hostname}.sxcu`); - } -}; +}` + var sharexBlob = new Blob([sharexFile], { type: 'application/octet-binary' }) + sharexElement.setAttribute('href', URL.createObjectURL(sharexBlob)) + sharexElement.setAttribute('download', `${location.hostname}.sxcu`) + } +} // Handle image paste event window.addEventListener('paste', event => { - var items = (event.clipboardData || event.originalEvent.clipboardData).items; - for (index in items) { - var item = items[index]; - if (item.kind === 'file') { - var blob = item.getAsFile(); - console.log(blob.type); - var file = new File([blob], `pasted-image.${blob.type.match(/(?:[^\/]*\/)([^;]*)/)[1]}`); - file.type = blob.type; - console.log(file); - upload.myDropzone.addFile(file); - } - } -}); - -window.onload = function() { - upload.checkIfPublic(); -}; + var items = (event.clipboardData || event.originalEvent.clipboardData).items + for (var index in items) { + var item = items[index] + if (item.kind === 'file') { + var blob = item.getAsFile() + console.log(blob.type) + var file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`) + file.type = blob.type + console.log(file) + upload.myDropzone.addFile(file) + } + } +}) +window.onload = function () { + upload.checkIfPublic() +} diff --git a/routes/album.js b/routes/album.js index 46f00d3..56d2211 100644 --- a/routes/album.js +++ b/routes/album.js @@ -1,56 +1,55 @@ -const config = require('../config.js'); -const routes = require('express').Router(); -const db = require('knex')(config.database); -const path = require('path'); -const utils = require('../controllers/utilsController.js'); +const config = require('../config.js') +const routes = require('express').Router() +const db = require('knex')(config.database) +const path = require('path') +const utils = require('../controllers/utilsController.js') routes.get('/a/:identifier', async (req, res, next) => { - let identifier = req.params.identifier; - if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }); + let identifier = req.params.identifier + if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' }) - const album = await db.table('albums').where({ identifier, enabled: 1 }).first(); - if (!album) return res.status(404).sendFile('404.html', { root: './pages/error/' }); + const album = await db.table('albums').where({ identifier, enabled: 1 }).first() + if (!album) return res.status(404).sendFile('404.html', { root: './pages/error/' }) - const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC'); - let thumb = ''; - const basedomain = config.domain; + const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC') + let thumb = '' + const basedomain = config.domain - for (let file of files) { - file.file = `${basedomain}/${file.name}`; + for (let file of files) { + file.file = `${basedomain}/${file.name}` - let ext = path.extname(file.name).toLowerCase(); - if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { - file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`; + let ext = path.extname(file.name).toLowerCase() + if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) { + file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png` - /* - If thumbnail for album is still not set, do it. - A potential improvement would be to let the user upload a specific image as an album cover - since embedding the first image could potentially result in nsfw content when pasting links. - */ + /* + If thumbnail for album is still not set, do it. + A potential improvement would be to let the user upload a specific image as an album cover + since embedding the first image could potentially result in nsfw content when pasting links. + */ - if (thumb === '') { - thumb = file.thumb; - } + if (thumb === '') { + thumb = file.thumb + } - file.thumb = `