From e7767ac7095f93393a627fd5e867af4a1ca4b011 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Sun, 16 Sep 2018 00:56:13 -0300 Subject: Routes --- src/api/routes/albums/albumDELETE.js | 35 ++++ src/api/routes/albums/albumGET.js | 52 +++++ src/api/routes/albums/albumPOST.js | 44 ++++ src/api/routes/albums/albumsGET.js | 86 ++++++++ src/api/routes/albums/link/linkEnabledPOST.js | 34 ++++ src/api/routes/albums/link/linkPOST.js | 43 ++++ src/api/routes/auth/apiKey.js | 23 +++ src/api/routes/auth/changePasswordPOST.js | 41 ++++ src/api/routes/auth/loginPOST.js | 39 ++++ src/api/routes/auth/registerPOST.js | 53 +++++ src/api/routes/baseGET.js | 13 ++ src/api/routes/files/fileDELETE.js | 32 +++ src/api/routes/files/filesGET.js | 25 +++ src/api/routes/files/uploadPOST.js | 276 ++++++++++++++++++++++++++ src/api/routes/verifyGET.js | 16 ++ 15 files changed, 812 insertions(+) create mode 100644 src/api/routes/albums/albumDELETE.js create mode 100644 src/api/routes/albums/albumGET.js create mode 100644 src/api/routes/albums/albumPOST.js create mode 100644 src/api/routes/albums/albumsGET.js create mode 100644 src/api/routes/albums/link/linkEnabledPOST.js create mode 100644 src/api/routes/albums/link/linkPOST.js create mode 100644 src/api/routes/auth/apiKey.js create mode 100644 src/api/routes/auth/changePasswordPOST.js create mode 100644 src/api/routes/auth/loginPOST.js create mode 100644 src/api/routes/auth/registerPOST.js create mode 100644 src/api/routes/baseGET.js create mode 100644 src/api/routes/files/fileDELETE.js create mode 100644 src/api/routes/files/filesGET.js create mode 100644 src/api/routes/files/uploadPOST.js create mode 100644 src/api/routes/verifyGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js new file mode 100644 index 0000000..ef98137 --- /dev/null +++ b/src/api/routes/albums/albumDELETE.js @@ -0,0 +1,35 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const db = require('knex')(config.server.database); +const Util = require('../../utils/Util'); +const log = require('../../utils/Log'); + +class albumDELETE extends Route { + constructor() { + super('/album/:id/:purge*?', 'delete'); + } + + async run(req, res, user) { + const { id, purge } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid album ID supplied' }); + + const album = await db.table('albums').where({ + id, + userId: user.id + }).first(); + + if (!album) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + try { + if (purge) { + await Util.deleteAllFilesFromAlbum(id); + } + await db.table('albums').where({ id }).delete(); + return res.json({ message: 'The album was deleted successfully' }); + } catch (error) { + log.error(error); + return res.json({ message: 'There was a problem deleting the album' }); + } + } +} + +module.exports = albumDELETE; diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js new file mode 100644 index 0000000..80affd2 --- /dev/null +++ b/src/api/routes/albums/albumGET.js @@ -0,0 +1,52 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const db = require('knex')(config.server.database); + +class albumGET extends Route { + constructor() { + super('/album/:identifier', 'get', { bypassAuth: true }); + } + + async run(req, res) { + const { identifier } = req.params; + if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); + + const link = await db.table('links').where({ + identifier, + enabled: true + }).first(); + if (!link) return res.status(400).json({ message: 'The identifier supplied could not be found' }); + + const album = await db.table('albums').where('id', link.albumId).first(); + if (!album) return res.status(400).json({ message: 'Album not found' }); + + const fileList = await db.table('albumsFiles').where('albumId', link.albumId); + const fileIds = fileList.filter(el => el.file.fileId); + const files = await db.table('files') + .where('id', fileIds) + .select('name'); + + return res.json({ + message: 'Successfully retrieved files', + files + }); + } +} + +class albumsDropdownGET extends Route { + constructor() { + super('/albums/:identifier', 'get'); + } + + async run(req, res, user) { + const albums = await db.table('albums') + .where('userId', user.id) + .select('id', 'name'); + return res.json({ + message: 'Successfully retrieved albums', + albums + }); + } +} + +module.exports = [albumGET, albumsDropdownGET]; diff --git a/src/api/routes/albums/albumPOST.js b/src/api/routes/albums/albumPOST.js new file mode 100644 index 0000000..24ccca8 --- /dev/null +++ b/src/api/routes/albums/albumPOST.js @@ -0,0 +1,44 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const db = require('knex')(config.server.database); +const moment = require('moment'); + +class albumPOST extends Route { + constructor() { + super('/album/new', 'post'); + } + + async run(req, res, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { name } = req.body; + if (!name) return res.status(400).json({ message: 'No name provided' }); + + const album = await db.table('albums').where({ + name, + enabled: true, + userId: user.id + }).first(); + + if (album) return res.status(401).json({ message: 'There\'s already an album with that name' }); + + const now = moment.utc().toDate(); + /* + const identifier = await Util.getUniqueAlbumIdentifier(); + if (!identifier) { + console.error('Couldn\'t allocate an identifier for an album'); + return res.status(500).json({ message: 'There was a problem allocating an identifier to the album' }); + } + */ + await db.table('albums').insert({ + name, + enabled: true, + userId: user.id, + createdAt: now, + editedAt: now + }); + + return res.json({ message: 'The album was created successfully' }); + } +} + +module.exports = albumPOST; diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js new file mode 100644 index 0000000..b19e03a --- /dev/null +++ b/src/api/routes/albums/albumsGET.js @@ -0,0 +1,86 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const db = require('knex')(config.server.database); +const Util = require('../../utils/Util'); + +class albumsGET extends Route { + constructor() { + super('/albums/mini', 'get'); + } + + async run(req, res, user) { + /* + Let's fetch the albums. This route will only return a small portion + of the album files for displaying on the dashboard. It's probably useless + for anyone consuming the API outside of the lolisafe frontend. + */ + const albums = await db.table('albums') + .where('userId', user.id) + // .where('enabled', true) + .select('id', 'name', 'createdAt', 'editedAt'); + + for (const album of albums) { + /* + Fetch every public link the album has + */ + const links = await db.table('links').where('albumId', album.id); // eslint-disable-line no-await-in-loop + + /* + Fetch the total amount of files each album has. + */ + const fileCount = await db.table('albumsFiles') // eslint-disable-line no-await-in-loop + .where('albumId', album.id) + .count({ count: 'id' }); + + /* + Fetch the file list from each album but limit it to 5 per album + */ + const filesToFetch = await db.table('albumsFiles') // eslint-disable-line no-await-in-loop + .where('albumId', album.id) + .select('fileId') + .orderBy('id', 'desc') + .limit(5); + + /* + Fetch the actual files + */ + const files = await db.table('files') // eslint-disable-line no-await-in-loop + .whereIn('id', filesToFetch.map(el => el.fileId)) + .select('id', 'name', 'hash', 'original', 'size', 'type', 'createdAt', 'editedAt'); + + /* + Fetch thumbnails and stuff + */ + for (let file of files) { + file = Util.constructFilePublicLink(file); + } + + album.links = links; + album.fileCount = fileCount[0].count; + album.files = files; + } + + return res.json({ + message: 'Successfully retrieved albums', + albums + }); + } +} + +class albumsDropdownGET extends Route { + constructor() { + super('/albums/dropdown', 'get'); + } + + async run(req, res, user) { + const albums = await db.table('albums') + .where('userId', user.id) + .select('id', 'name'); + return res.json({ + message: 'Successfully retrieved albums', + albums + }); + } +} + +module.exports = [albumsGET, albumsDropdownGET]; diff --git a/src/api/routes/albums/link/linkEnabledPOST.js b/src/api/routes/albums/link/linkEnabledPOST.js new file mode 100644 index 0000000..863fe0b --- /dev/null +++ b/src/api/routes/albums/link/linkEnabledPOST.js @@ -0,0 +1,34 @@ +const Route = require('../../../structures/Route'); +const config = require('../../../../../config'); +const db = require('knex')(config.server.database); +const log = require('../../../utils/Log'); + +class linkEnabledPOST extends Route { + constructor() { + super('/album/link/enabled', 'post'); + } + + async run(req, res, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { identifier, enabled } = req.body; + if (!identifier) return res.status(400).json({ message: 'Invalid album identifier supplied' }); + + const link = await db.table('links').where({ + identifier, + userId: user.id + }).first(); + + if (!link) return res.status(400).json({ message: 'The link doesn\'t exist or doesn\'t belong to the user' }); + try { + await db.table('links') + .where({ identifier }) + .update({ enabled }); + return res.json({ message: 'The link status was changed successfully' }); + } catch (error) { + log.error(error); + return res.json({ message: 'There was a problem changing the status of the link' }); + } + } +} + +module.exports = linkEnabledPOST; diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js new file mode 100644 index 0000000..e8f3731 --- /dev/null +++ b/src/api/routes/albums/link/linkPOST.js @@ -0,0 +1,43 @@ +const Route = require('../../../structures/Route'); +const config = require('../../../../../config'); +const db = require('knex')(config.server.database); +const Util = require('../../../utils/Util'); +const log = require('../../../utils/Log'); + +class linkPOST extends Route { + constructor() { + super('/album/link/new', 'post'); + } + + async run(req, res) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { albumId, enabled, enableDownload, expiresAt } = req.body; + if (!albumId) return res.status(400).json({ message: 'No album provided' }); + + const exists = await db.table('albums').where('id', albumId).first(); + if (!exists) return res.status(400).json({ message: 'Album doesn\t exist' }); + + const identifier = Util.getUniqueAlbumIdentifier(); + if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); + + try { + await db.table('links').insert({ + identifier, + albumId, + enabled, + enableDownload, + expiresAt + }); + + return res.json({ + message: 'The link was created successfully', + identifier + }); + } catch (error) { + log.error(error); + return res.status(500).json({ message: 'There was a problem creating the link' }); + } + } +} + +module.exports = linkPOST; diff --git a/src/api/routes/auth/apiKey.js b/src/api/routes/auth/apiKey.js new file mode 100644 index 0000000..84df2e3 --- /dev/null +++ b/src/api/routes/auth/apiKey.js @@ -0,0 +1,23 @@ +const Route = require('../../structures/Route'); + +class apiKeyGET extends Route { + constructor() { + super('/auth/apiKey', 'get'); + } + + run(req, res, user) { + return res.json({ message: 'Hai hai api works.' }); + } +} + +class apiKeyPOST extends Route { + constructor() { + super('/auth/apiKey', 'post'); + } + + run(req, res, user) { + return res.json({ message: 'Hai hai api works.' }); + } +} + +module.exports = [apiKeyGET, apiKeyPOST]; diff --git a/src/api/routes/auth/changePasswordPOST.js b/src/api/routes/auth/changePasswordPOST.js new file mode 100644 index 0000000..bd64320 --- /dev/null +++ b/src/api/routes/auth/changePasswordPOST.js @@ -0,0 +1,41 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const log = require('../../utils/Log'); +const db = require('knex')(config.server.database); +const bcrypt = require('bcrypt'); +const moment = require('moment'); + +class changePasswordPOST extends Route { + constructor() { + super('/auth/password/change', 'post'); + } + + async run(req, res, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { password, newPassword } = req.body; + if (!password || !newPassword) return res.status(401).json({ message: 'Invalid body provided' }); + + if (newPassword.length < 6 || newPassword.length > 64) { + return res.status(400).json({ message: 'Password must have 6-64 characters' }); + } + + let hash; + try { + hash = await bcrypt.hash(newPassword, 10); + } catch (error) { + log.error('Error generating password hash'); + log.error(error); + return res.status(401).json({ message: 'There was a problem processing your account' }); + } + + const now = moment.utc().toDate(); + await db.table('users').where('id', user.id).update({ + password: hash, + passwordEditedAt: now + }); + + return res.json({ message: 'The password was changed successfully' }); + } +} + +module.exports = changePasswordPOST; diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js new file mode 100644 index 0000000..7e85812 --- /dev/null +++ b/src/api/routes/auth/loginPOST.js @@ -0,0 +1,39 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const db = require('knex')(config.server.database); +const bcrypt = require('bcrypt'); +const moment = require('moment'); +const JWT = require('jsonwebtoken'); + +class loginPOST extends Route { + constructor() { + super('/auth/login', 'post', { bypassAuth: true }); + } + + async run(req, res) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { username, password } = req.body; + if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' }); + + const user = await db.table('users').where('username', username).first(); + if (!user) return res.status(401).json({ message: 'Invalid authorization' }); + + const comparePassword = await bcrypt.compare(password, user.password); + if (!comparePassword) return res.status(401).json({ message: 'Invalid authorization.' }); + + const jwt = JWT.sign({ + iss: 'lolisafe', + sub: user.id, + iat: moment.utc().valueOf() + }, config.server.secret, { expiresIn: '30d' }); + + return res.json({ + message: 'Successfully logged in.', + user: { username: user.username }, + token: jwt, + apiKey: user.apiKey + }); + } +} + +module.exports = loginPOST; diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js new file mode 100644 index 0000000..dad45fd --- /dev/null +++ b/src/api/routes/auth/registerPOST.js @@ -0,0 +1,53 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const log = require('../../utils/Log'); +const db = require('knex')(config.server.database); +const bcrypt = require('bcrypt'); +const randomstring = require('randomstring'); +const moment = require('moment'); + +class registerPOST extends Route { + constructor() { + super('/auth/register', 'post', { bypassAuth: true }); + } + + async run(req, res) { + if (!config.enableCreateUserAccounts) return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { username, password } = req.body; + if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' }); + + if (username.length < 4 || username.length > 32) { + return res.status(400).json({ message: 'Username must have 4-32 characters' }); + } + if (password.length < 6 || password.length > 64) { + return res.status(400).json({ message: 'Password must have 6-64 characters' }); + } + + const user = await db.table('users').where('username', username).first(); + if (user) return res.status(401).json({ message: 'Username already exists' }); + + let hash; + try { + hash = await bcrypt.hash(password, 10); + } catch (error) { + log.error('Error generating password hash'); + log.error(error); + return res.status(401).json({ message: 'There was a problem processing your account' }); + } + + const now = moment.utc().toDate(); + await db.table('users').insert({ + username, + password: hash, + passwordEditedAt: now, + apiKey: randomstring.generate(64), + apiKeyEditedAt: now, + createdAt: now, + editedAt: now + }); + return res.json({ message: 'The account was created successfully' }); + } +} + +module.exports = registerPOST; diff --git a/src/api/routes/baseGET.js b/src/api/routes/baseGET.js new file mode 100644 index 0000000..a6c01ea --- /dev/null +++ b/src/api/routes/baseGET.js @@ -0,0 +1,13 @@ +const Route = require('../structures/Route'); + +class verifyGET extends Route { + constructor() { + super('/', 'get', { bypassAuth: true }); + } + + run(req, res) { + return res.json({ message: 'Hai hai api desu.' }); + } +} + +module.exports = verifyGET; diff --git a/src/api/routes/files/fileDELETE.js b/src/api/routes/files/fileDELETE.js new file mode 100644 index 0000000..2f2a4cf --- /dev/null +++ b/src/api/routes/files/fileDELETE.js @@ -0,0 +1,32 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const db = require('knex')(config.server.database); +const Util = require('../../utils/Util'); +const log = require('../../utils/Log'); + +class fileDELETE extends Route { + constructor() { + super('/file/:id', 'delete'); + } + + async run(req, res, user) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); + + const file = await db.table('files').where({ + id, + userId: user.id + }).first(); + + if (!file) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + try { + await Util.deleteFile(file.name, true); + return res.json({ message: 'The file was deleted successfully' }); + } catch (error) { + log.error(error); + return res.json({ message: 'There was a problem deleting the file' }); + } + } +} + +module.exports = fileDELETE; diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js new file mode 100644 index 0000000..98cf3aa --- /dev/null +++ b/src/api/routes/files/filesGET.js @@ -0,0 +1,25 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const db = require('knex')(config.server.database); +const Util = require('../../utils/Util'); + +class filesGET extends Route { + constructor() { + super('/files', 'get'); + } + + async run(req, res, user) { + const files = await db.table('files') + .where('userId', user.id) + .orderBy('id', 'desc'); + for (let file of files) { + file = Util.constructFilePublicLink(file); + } + return res.json({ + message: 'Successfully retrieved files', + files + }); + } +} + +module.exports = filesGET; diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js new file mode 100644 index 0000000..e152ac8 --- /dev/null +++ b/src/api/routes/files/uploadPOST.js @@ -0,0 +1,276 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const path = require('path'); +const Util = require('../../utils/Util'); +const db = require('knex')(config.server.database); +const moment = require('moment'); +const log = require('../../utils/Log'); +const jetpack = require('fs-jetpack'); +const Busboy = require('busboy'); +const fs = require('fs'); + +/* + TODO: Sometimes pics are being uploaded twice. Hash comparison not working? + TODO: Strip exif data if the owner/user configured it as such + TODO: If source has transparency generate a png thumbnail, otherwise a jpg. + TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover. + TODO: If source is a video, generate a thumb of the first frame and save the video length. + TODO: Check that the async isAuthorized works and is not nulling out + TODO: Store timestamps in human readable format? +*/ + +class uploadPOST extends Route { + constructor() { + super('/upload', 'post', { bypassAuth: true }); + } + + async run(req, res) { + const user = await Util.isAuthorized(req); + if (!user && !config.uploads.allowAnonymousUploads) return res.status(401).json({ message: 'Not authorized to use this resource' }); + return this.uploadFile(req, res, user); + } + + async processFile(req, res, user, file) { + /* + Check if the user is trying to upload to an album + */ + const albumId = req.body.albumid || req.headers.albumid; + if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); + if (albumId && user) { + const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); + if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); + } + + if (!albumId) log.info('Incoming file'); + else log.info(`Incoming file for album ${albumId}`); + + let upload = file.data; + /* + If it's a chunked upload but this is not the last part of the chunk, just green light. + Otherwise, put the file together and process it + */ + if (file.body.uuid) { + if (file.body.chunkindex < file.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if + /* + We got a chunk that is not the last part, send smoke signal that we received it. + */ + return res.json({ message: 'Successfully uploaded chunk' }); + } else { + /* + Seems we finally got the last part of a chunk upload + */ + const uploadsDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder); + const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', file.body.uuid); + const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' }); + const originalname = Util.getFilenameFromPath(chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.'))); + + const tempFile = { + filename: Util.getUniqueFilename(originalname), + originalname, + size: file.body.totalfilesize + }; + + for (const chunkFile of chunkFiles) { + try { + const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop + await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop + } catch (error) { + log.error(error); + } + } + + try { + await jetpack.removeAsync(chunkedFileDir); + } catch (error) { + log.error(error); + } + + upload = tempFile; + } + } + + /* + First let's get the hash of the file. This will be useful to check if the file + has already been upload by either the user or an anonymous user. + In case this is true, instead of uploading it again we retrieve the url + of the file that is already saved and thus don't store extra copies of the same file. + */ + const hash = await Util.getFileHash(upload.filename); // eslint-disable-line no-await-in-loop + const exists = await db.table('files') // eslint-disable-line no-await-in-loop + .where(function() { + if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this + else this.where('userId', user.id); // eslint-disable-line no-invalid-this + }) + .where({ + hash, + size: upload.size + }) + .first(); + + if (exists) { + res.json({ + message: 'Successfully uploaded file BUT IT EXISTED ALREADY', + name: exists.name, + size: exists.size, + url: `${config.filesServeLocation}/${exists.name}` + }); + + return Util.deleteFile(upload.filename); + } + + /* + The file doesn't appear to exist yet for this user, so let's + store the details on the database. + */ + const now = moment.utc().toDate(); + let inserted = null; + try { + inserted = await db.table('files').insert({ + userId: user ? user.id : null, + name: upload.filename, + original: upload.originalname, + type: upload.mimetype || '', + size: upload.size, + hash, + ip: req.ip, + createdAt: now, + editedAt: now + }); + } catch (error) { + log.error('There was an error saving the file to the database'); + log.error(error); + return res.status(500).json({ message: 'There was an error uploading the file.' }); + } + + res.json({ + message: 'Successfully uploaded file', + name: upload.filename, + size: upload.size, + url: `${config.filesServeLocation}/${upload.filename}` + }); + + /* + If the upload had an album specified we make sure to create the relation + and update the according timestamps.. + */ + if (albumId) { + try { + await db.table('albumsFiles').insert({ albumId, fileId: inserted[0] }); + await db.table('albums').where('id', albumId).update('editedAt', now); + } catch (error) { + log.error('There was an error updating editedAt on an album'); + log.error(error); + } + } + + /* + If exif removal has been force service-wide or requested by the user, remove it + */ + if (config.uploads.forceStripExif) { // || user.settings.stripExif) { + Util.removeExif(upload.filename); + } + + /* + Generate those thumbnails + */ + return Util.generateThumbnails(upload.filename); + } + + uploadFile(req, res, user) { + const busboy = new Busboy({ + headers: req.headers, + limits: { + fileSize: config.uploads.uploadMaxSize * (1000 * 1000), + files: 1 + } + }); + + const fileToUpload = { + data: {}, + body: {} + }; + + /* + Note: For this to work on every case, whoever is uploading a chunk + should really send the body first and the file last. Otherwise lolisafe + may not catch the field on time and the chunk may end up being saved + as a standalone file, completely broken. + */ + busboy.on('field', (fieldname, val) => { + if (/^dz/.test(fieldname)) { + fileToUpload.body[fieldname.substring(2)] = val; + } else { + fileToUpload.body[fieldname] = val; + } + }); + + /* + Hey ther's a file! Let's upload it. + */ + busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { + let name, saveTo; + + /* + Let check whether the file is part of a chunk upload or if it's a standalone one. + If the former, we should store them separately and join all the pieces after we + receive the last one. + */ + const ext = path.extname(filename).toLowerCase(); + if (Util.isExtensionBlocked(ext)) return res.status(400).json({ message: 'This extension is not allowed.' }); + + if (!fileToUpload.body.uuid) { + name = Util.getUniqueFilename(filename); + if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' }); + saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, name); + } else { + name = `${filename}.${fileToUpload.body.chunkindex}`; + const chunkDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid); + jetpack.dir(chunkDir); + saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid, name); + } + + /* + Let's save some metadata for the db. + */ + fileToUpload.data = { filename: name, originalname: filename, encoding, mimetype }; + const stream = fs.createWriteStream(saveTo); + + file.on('data', data => { + fileToUpload.data.size = data.length; + }); + + /* + The file that is being uploaded is bigger than the limit specified on the config file + and thus we should close the stream and delete the file. + */ + file.on('limit', () => { + file.unpipe(stream); + stream.end(); + jetpack.removeAsync(saveTo); + return res.status(400).json({ message: 'The file is too big.' }); + }); + + file.on('error', err => { + log.error('There was an error uploading a file'); + log.error(err); + return res.status(500).json({ message: 'There was an error uploading the file.' }); + }); + + /* + TODO: Does this even work?? + */ + return file.pipe(stream); + }); + + busboy.on('error', err => { + log.error('There was an error uploading a file'); + log.error(err); + return res.status(500).json({ message: 'There was an error uploading the file.' }); + }); + + busboy.on('finish', () => this.processFile(req, res, user, fileToUpload)); + req.pipe(busboy); + } +} + +module.exports = uploadPOST; diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js new file mode 100644 index 0000000..29b521e --- /dev/null +++ b/src/api/routes/verifyGET.js @@ -0,0 +1,16 @@ +const Route = require('../structures/Route'); + +class verifyGET extends Route { + constructor() { + super('/verify', 'get'); + } + + run(req, res, user) { + return res.json({ + message: 'Successfully verified token', + user + }); + } +} + +module.exports = verifyGET; -- cgit v1.2.3 From 04cb6dcce574efbaecf80071acd8219a8b5fd6f7 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Sun, 16 Sep 2018 01:10:46 -0300 Subject: We dont need the second one, probably --- src/api/routes/albums/albumGET.js | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index 80affd2..f9e5208 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -33,20 +33,4 @@ class albumGET extends Route { } } -class albumsDropdownGET extends Route { - constructor() { - super('/albums/:identifier', 'get'); - } - - async run(req, res, user) { - const albums = await db.table('albums') - .where('userId', user.id) - .select('id', 'name'); - return res.json({ - message: 'Successfully retrieved albums', - albums - }); - } -} - -module.exports = [albumGET, albumsDropdownGET]; +module.exports = albumGET; -- cgit v1.2.3 From 90011334147eaa3b480e0dc9f80cc83bb83b3cd5 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Sun, 16 Sep 2018 05:42:38 -0300 Subject: Links are managed elsewhere, so there's no point in this --- src/api/routes/albums/albumPOST.js | 4 ++-- src/api/routes/files/uploadPOST.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumPOST.js b/src/api/routes/albums/albumPOST.js index 24ccca8..c2e7c4e 100644 --- a/src/api/routes/albums/albumPOST.js +++ b/src/api/routes/albums/albumPOST.js @@ -15,7 +15,7 @@ class albumPOST extends Route { const album = await db.table('albums').where({ name, - enabled: true, + // enabled: true, userId: user.id }).first(); @@ -31,7 +31,7 @@ class albumPOST extends Route { */ await db.table('albums').insert({ name, - enabled: true, + // enabled: true, userId: user.id, createdAt: now, editedAt: now diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index e152ac8..9ecf7ee 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -41,8 +41,10 @@ class uploadPOST extends Route { if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); } + /* if (!albumId) log.info('Incoming file'); else log.info(`Incoming file for album ${albumId}`); + */ let upload = file.data; /* @@ -167,7 +169,7 @@ class uploadPOST extends Route { If exif removal has been force service-wide or requested by the user, remove it */ if (config.uploads.forceStripExif) { // || user.settings.stripExif) { - Util.removeExif(upload.filename); + // Util.removeExif(upload.filename); } /* -- cgit v1.2.3 From e073fb4317ae8bd55dfcd0531de10e67383aa408 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Sun, 16 Sep 2018 17:52:46 -0300 Subject: Links can now be created --- src/api/routes/albums/link/linkPOST.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index e8f3731..26a527a 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -11,22 +11,22 @@ class linkPOST extends Route { async run(req, res) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); - const { albumId, enabled, enableDownload, expiresAt } = req.body; + const { albumId } = req.body; if (!albumId) return res.status(400).json({ message: 'No album provided' }); const exists = await db.table('albums').where('id', albumId).first(); if (!exists) return res.status(400).json({ message: 'Album doesn\t exist' }); - const identifier = Util.getUniqueAlbumIdentifier(); + const identifier = await Util.getUniqueAlbumIdentifier(); if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); try { await db.table('links').insert({ identifier, albumId, - enabled, - enableDownload, - expiresAt + enabled: true, + enableDownload: true, + expiresAt: null }); return res.json({ -- cgit v1.2.3 From 7df56eb91c4cf22c6e7323e24881bc527a2c1ad6 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Sun, 16 Sep 2018 17:53:14 -0300 Subject: Switching to postgresql as the default had some implications --- src/api/routes/files/uploadPOST.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 9ecf7ee..9769bed 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -137,7 +137,11 @@ class uploadPOST extends Route { ip: req.ip, createdAt: now, editedAt: now - }); + }, 'id'); + /* + TODO: Something funny here, I'm not sure since I don't use MySQL but I think the argument id + on the insert function on top behaves differently on psql/mysql/sqlite. Needs testing. + */ } catch (error) { log.error('There was an error saving the file to the database'); log.error(error); -- cgit v1.2.3 From 46ed1c6a824252fc5ae0dad5b5c2a369bad9ad39 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Mon, 17 Sep 2018 04:37:27 -0300 Subject: This route should handle more stuff, so it does now --- src/api/routes/albums/link/linkEditPOST.js | 38 +++++++++++++++++++++++++++ src/api/routes/albums/link/linkEnabledPOST.js | 34 ------------------------ 2 files changed, 38 insertions(+), 34 deletions(-) create mode 100644 src/api/routes/albums/link/linkEditPOST.js delete mode 100644 src/api/routes/albums/link/linkEnabledPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js new file mode 100644 index 0000000..46b851a --- /dev/null +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -0,0 +1,38 @@ +const Route = require('../../../structures/Route'); +const config = require('../../../../../config'); +const db = require('knex')(config.server.database); +const log = require('../../../utils/Log'); + +class linkEditPOST extends Route { + constructor() { + super('/album/link/edit', 'post'); + } + + async run(req, res, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { identifier, enabled, enableDownload, expiresAt } = req.body; + if (!identifier) return res.status(400).json({ message: 'Invalid album identifier supplied' }); + + const link = await db.table('links').where({ + identifier, + userId: user.id + }).first(); + + if (!link) return res.status(400).json({ message: 'The link doesn\'t exist or doesn\'t belong to the user' }); + try { + await db.table('links') + .where({ identifier }) + .update({ + enabled: enabled || false, + enableDownload: enableDownload || false, + expiresAt // This one should be null if not supplied + }); + return res.json({ message: 'Editing the link was successfully' }); + } catch (error) { + log.error(error); + return res.json({ message: 'There was a problem editing the link' }); + } + } +} + +module.exports = linkEditPOST; diff --git a/src/api/routes/albums/link/linkEnabledPOST.js b/src/api/routes/albums/link/linkEnabledPOST.js deleted file mode 100644 index 863fe0b..0000000 --- a/src/api/routes/albums/link/linkEnabledPOST.js +++ /dev/null @@ -1,34 +0,0 @@ -const Route = require('../../../structures/Route'); -const config = require('../../../../../config'); -const db = require('knex')(config.server.database); -const log = require('../../../utils/Log'); - -class linkEnabledPOST extends Route { - constructor() { - super('/album/link/enabled', 'post'); - } - - async run(req, res, user) { - if (!req.body) return res.status(400).json({ message: 'No body provided' }); - const { identifier, enabled } = req.body; - if (!identifier) return res.status(400).json({ message: 'Invalid album identifier supplied' }); - - const link = await db.table('links').where({ - identifier, - userId: user.id - }).first(); - - if (!link) return res.status(400).json({ message: 'The link doesn\'t exist or doesn\'t belong to the user' }); - try { - await db.table('links') - .where({ identifier }) - .update({ enabled }); - return res.json({ message: 'The link status was changed successfully' }); - } catch (error) { - log.error(error); - return res.json({ message: 'There was a problem changing the status of the link' }); - } - } -} - -module.exports = linkEnabledPOST; -- cgit v1.2.3 From c2c6e99878853fafdbd5e708c3163921f8529ae1 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Mon, 17 Sep 2018 04:38:25 -0300 Subject: Public albums wooo! --- src/api/routes/albums/albumGET.js | 11 +++++++++-- src/api/routes/albums/link/linkPOST.js | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index f9e5208..655db13 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -1,6 +1,7 @@ const Route = require('../../structures/Route'); const config = require('../../../../config'); const db = require('knex')(config.server.database); +const Util = require('../../utils/Util'); class albumGET extends Route { constructor() { @@ -21,13 +22,19 @@ class albumGET extends Route { if (!album) return res.status(400).json({ message: 'Album not found' }); const fileList = await db.table('albumsFiles').where('albumId', link.albumId); - const fileIds = fileList.filter(el => el.file.fileId); + const fileIds = fileList.map(el => el.fileId); const files = await db.table('files') - .where('id', fileIds) + .whereIn('id', fileIds) + .orderBy('id', 'desc') .select('name'); + for (let file of files) { + file = Util.constructFilePublicLink(file); + } return res.json({ message: 'Successfully retrieved files', + name: album.name, + downloadEnabled: link.enableDownload, files }); } diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 26a527a..9c8c0bc 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -17,6 +17,9 @@ class linkPOST extends Route { const exists = await db.table('albums').where('id', albumId).first(); if (!exists) return res.status(400).json({ message: 'Album doesn\t exist' }); + const count = await db.table('links').where('albumId', albumId).count({ count: 'id' }); + if (count[0].count >= config.albums.maxLinksPerAlbum) return res.status(400).json({ message: 'Maximum links per album reached' }); + const identifier = await Util.getUniqueAlbumIdentifier(); if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); -- cgit v1.2.3 From f2c885b718528d42df412e612520fb471c46d0bd Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Mon, 17 Sep 2018 04:55:42 -0300 Subject: Commented all the code --- src/api/routes/albums/albumDELETE.js | 16 +++++++++++----- src/api/routes/albums/albumGET.js | 25 ++++++++++++++++++++----- src/api/routes/albums/albumPOST.js | 18 ++++-------------- src/api/routes/albums/link/linkEditPOST.js | 10 +++++----- src/api/routes/albums/link/linkPOST.js | 9 +++++++++ src/api/routes/auth/loginPOST.js | 9 +++++++++ src/api/routes/auth/registerPOST.js | 9 +++++++++ src/api/routes/files/fileDELETE.js | 13 ++++++++----- src/api/routes/files/filesGET.js | 8 ++++++++ 9 files changed, 83 insertions(+), 34 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index ef98137..eefbf41 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -13,16 +13,22 @@ class albumDELETE extends Route { const { id, purge } = req.params; if (!id) return res.status(400).json({ message: 'Invalid album ID supplied' }); - const album = await db.table('albums').where({ - id, - userId: user.id - }).first(); - + /* + Check fi the album exists + */ + const album = await db.table('albums').where({ id, userId: user.id }).first(); if (!album) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + try { + /* + Should we also delete every file of that album? + */ if (purge) { await Util.deleteAllFilesFromAlbum(id); } + /* + Delete the album + */ await db.table('albums').where({ id }).delete(); return res.json({ message: 'The album was deleted successfully' }); } catch (error) { diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index 655db13..b63811c 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -12,25 +12,40 @@ class albumGET extends Route { const { identifier } = req.params; if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); - const link = await db.table('links').where({ - identifier, - enabled: true - }).first(); + /* + Make sure it exists and it's enabled + */ + const link = await db.table('links').where({ identifier, enabled: true }).first(); if (!link) return res.status(400).json({ message: 'The identifier supplied could not be found' }); + /* + Same with the album, just to make sure is not a deleted album and a leftover link + */ const album = await db.table('albums').where('id', link.albumId).first(); if (!album) return res.status(400).json({ message: 'Album not found' }); - const fileList = await db.table('albumsFiles').where('albumId', link.albumId); + /* + Grab the files in a very unoptimized way. (This should be a join between both tables) + */ + const fileList = await db.table('albumsFiles').where('albumId', link.albumId).select('fileId'); const fileIds = fileList.map(el => el.fileId); const files = await db.table('files') .whereIn('id', fileIds) .orderBy('id', 'desc') .select('name'); + /* + Create the links for each file + */ for (let file of files) { file = Util.constructFilePublicLink(file); } + + /* + Add 1 more view to the link + */ + await db.table('links').where({ identifier }).update('views', Number(link.views) + 1); + return res.json({ message: 'Successfully retrieved files', name: album.name, diff --git a/src/api/routes/albums/albumPOST.js b/src/api/routes/albums/albumPOST.js index c2e7c4e..12b88fa 100644 --- a/src/api/routes/albums/albumPOST.js +++ b/src/api/routes/albums/albumPOST.js @@ -13,25 +13,15 @@ class albumPOST extends Route { const { name } = req.body; if (!name) return res.status(400).json({ message: 'No name provided' }); - const album = await db.table('albums').where({ - name, - // enabled: true, - userId: user.id - }).first(); - + /* + Check that an album with that name doesn't exist yet + */ + const album = await db.table('albums').where({ name, userId: user.id }).first(); if (album) return res.status(401).json({ message: 'There\'s already an album with that name' }); const now = moment.utc().toDate(); - /* - const identifier = await Util.getUniqueAlbumIdentifier(); - if (!identifier) { - console.error('Couldn\'t allocate an identifier for an album'); - return res.status(500).json({ message: 'There was a problem allocating an identifier to the album' }); - } - */ await db.table('albums').insert({ name, - // enabled: true, userId: user.id, createdAt: now, editedAt: now diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index 46b851a..d9dbcac 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -13,12 +13,12 @@ class linkEditPOST extends Route { const { identifier, enabled, enableDownload, expiresAt } = req.body; if (!identifier) return res.status(400).json({ message: 'Invalid album identifier supplied' }); - const link = await db.table('links').where({ - identifier, - userId: user.id - }).first(); - + /* + Make sure the link exists + */ + const link = await db.table('links').where({ identifier, userId: user.id }).first(); if (!link) return res.status(400).json({ message: 'The link doesn\'t exist or doesn\'t belong to the user' }); + try { await db.table('links') .where({ identifier }) diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 9c8c0bc..4b24eae 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -14,12 +14,21 @@ class linkPOST extends Route { const { albumId } = req.body; if (!albumId) return res.status(400).json({ message: 'No album provided' }); + /* + Make sure the album exists + */ const exists = await db.table('albums').where('id', albumId).first(); if (!exists) return res.status(400).json({ message: 'Album doesn\t exist' }); + /* + Count the amount of links created for that album already and error out if max was reached + */ const count = await db.table('links').where('albumId', albumId).count({ count: 'id' }); if (count[0].count >= config.albums.maxLinksPerAlbum) return res.status(400).json({ message: 'Maximum links per album reached' }); + /* + Try to allocate a new identifier on the db + */ const identifier = await Util.getUniqueAlbumIdentifier(); if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index 7e85812..eaf09e8 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -15,12 +15,21 @@ class loginPOST extends Route { const { username, password } = req.body; if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' }); + /* + Checks if the user exists + */ const user = await db.table('users').where('username', username).first(); if (!user) return res.status(401).json({ message: 'Invalid authorization' }); + /* + Checks if the password is right + */ const comparePassword = await bcrypt.compare(password, user.password); if (!comparePassword) return res.status(401).json({ message: 'Invalid authorization.' }); + /* + Create the jwt with some data + */ const jwt = JWT.sign({ iss: 'lolisafe', sub: user.id, diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index dad45fd..d3532f4 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -24,9 +24,15 @@ class registerPOST extends Route { return res.status(400).json({ message: 'Password must have 6-64 characters' }); } + /* + Make sure the username doesn't exist yet + */ const user = await db.table('users').where('username', username).first(); if (user) return res.status(401).json({ message: 'Username already exists' }); + /* + Hash the supplied password + */ let hash; try { hash = await bcrypt.hash(password, 10); @@ -36,6 +42,9 @@ class registerPOST extends Route { return res.status(401).json({ message: 'There was a problem processing your account' }); } + /* + Create the user + */ const now = moment.utc().toDate(); await db.table('users').insert({ username, diff --git a/src/api/routes/files/fileDELETE.js b/src/api/routes/files/fileDELETE.js index 2f2a4cf..b50e576 100644 --- a/src/api/routes/files/fileDELETE.js +++ b/src/api/routes/files/fileDELETE.js @@ -13,12 +13,15 @@ class fileDELETE extends Route { const { id } = req.params; if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); - const file = await db.table('files').where({ - id, - userId: user.id - }).first(); - + /* + Make sure the file exists + */ + const file = await db.table('files').where({ id, userId: user.id }).first(); if (!file) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + + /* + Delete the file + */ try { await Util.deleteFile(file.name, true); return res.json({ message: 'The file was deleted successfully' }); diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index 98cf3aa..d1b6619 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -9,12 +9,20 @@ class filesGET extends Route { } async run(req, res, user) { + /* + Get all the files from the user + */ const files = await db.table('files') .where('userId', user.id) .orderBy('id', 'desc'); + + /* + For each file, create the public link to be able to display the file + */ for (let file of files) { file = Util.constructFilePublicLink(file); } + return res.json({ message: 'Successfully retrieved files', files -- cgit v1.2.3 From e8bb2c5a7f6a82b93debac7f489c653c7f5d0b54 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Tue, 18 Sep 2018 01:49:13 -0300 Subject: Stupid hash was working, the size changes for some reason when uploading --- src/api/routes/files/uploadPOST.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 9769bed..580e3fd 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -10,7 +10,6 @@ const Busboy = require('busboy'); const fs = require('fs'); /* - TODO: Sometimes pics are being uploaded twice. Hash comparison not working? TODO: Strip exif data if the owner/user configured it as such TODO: If source has transparency generate a png thumbnail, otherwise a jpg. TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover. @@ -103,10 +102,7 @@ class uploadPOST extends Route { if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this else this.where('userId', user.id); // eslint-disable-line no-invalid-this }) - .where({ - hash, - size: upload.size - }) + .where({ hash }) .first(); if (exists) { -- cgit v1.2.3 From 4b2b02110b457d8ebeee78e1bdf99eb0660d0626 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Tue, 18 Sep 2018 03:34:00 -0300 Subject: We can now download albums yayyyy --- src/api/routes/albums/albumZipGET.js | 78 ++++++++++++++++++++++++++++++++++ src/api/routes/albums/link/linkPOST.js | 3 +- 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/api/routes/albums/albumZipGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js new file mode 100644 index 0000000..7a853cd --- /dev/null +++ b/src/api/routes/albums/albumZipGET.js @@ -0,0 +1,78 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const db = require('knex')(config.server.database); +const Util = require('../../utils/Util'); +const log = require('../../utils/Log'); +const path = require('path'); +const jetpack = require('fs-jetpack'); + +class albumGET extends Route { + constructor() { + super('/album/:identifier/zip', 'get', { bypassAuth: true }); + } + + async run(req, res) { + const { identifier } = req.params; + if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); + + /* + Make sure it exists and it's enabled + */ + const link = await db.table('links').where({ identifier, enabled: true }).first(); + if (!link) return res.status(400).json({ message: 'The identifier supplied could not be found' }); + + /* + Same with the album, just to make sure is not a deleted album and a leftover link + */ + const album = await db.table('albums').where('id', link.albumId).first(); + if (!album) return res.status(400).json({ message: 'Album not found' }); + + /* + If the date when the album was zipped is greater than the album's last edit, we just send the zip to the user + */ + if (album.zippedAt > album.editedAt) { + const filePath = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'zips', `${album.userId}-${album.id}.zip`); + const exists = await jetpack.existsAsync(filePath); + /* + Make sure the file exists just in case, and if not, continue to it's generation. + */ + if (exists) { + const fileName = `lolisafe-${identifier}.zip`; + return res.download(filePath, fileName); + } + } + + /* + Grab the files in a very unoptimized way. (This should be a join between both tables) + */ + const fileList = await db.table('albumsFiles').where('albumId', link.albumId).select('fileId'); + + /* + If there are no files, stop here + */ + if (!fileList) return res.status(400).json({ message: 'Can\'t download an empty album' }); + + /* + Get the actual files + */ + const fileIds = fileList.map(el => el.fileId); + const files = await db.table('files') + .whereIn('id', fileIds) + .select('name'); + const filesToZip = files.map(el => el.name); + + try { + Util.createZip(filesToZip, album); + await db.table('albums').where('id', link.albumId).update('zippedAt', db.fn.now()); + + const filePath = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'zips', `${album.userId}-${album.id}.zip`); + const fileName = `lolisafe-${identifier}.zip`; + return res.download(filePath, fileName); + } catch (error) { + log.error(error); + return res.status(500).json({ message: 'There was a problem downloading the album' }); + } + } +} + +module.exports = albumGET; diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 4b24eae..1edf891 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -9,7 +9,7 @@ class linkPOST extends Route { super('/album/link/new', 'post'); } - async run(req, res) { + async run(req, res, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { albumId } = req.body; if (!albumId) return res.status(400).json({ message: 'No album provided' }); @@ -35,6 +35,7 @@ class linkPOST extends Route { try { await db.table('links').insert({ identifier, + userId: user.id, albumId, enabled: true, enableDownload: true, -- cgit v1.2.3 From 8ca6784eec8d8f1e4a9c4f6875704f09aae1103a Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Tue, 18 Sep 2018 03:52:49 -0300 Subject: Better error handling on invalid links --- src/api/routes/albums/albumGET.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index b63811c..f5e339f 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -16,13 +16,13 @@ class albumGET extends Route { Make sure it exists and it's enabled */ const link = await db.table('links').where({ identifier, enabled: true }).first(); - if (!link) return res.status(400).json({ message: 'The identifier supplied could not be found' }); + if (!link) return res.status(404).json({ message: 'The identifier supplied could not be found' }); /* Same with the album, just to make sure is not a deleted album and a leftover link */ const album = await db.table('albums').where('id', link.albumId).first(); - if (!album) return res.status(400).json({ message: 'Album not found' }); + if (!album) return res.status(404).json({ message: 'Album not found' }); /* Grab the files in a very unoptimized way. (This should be a join between both tables) -- cgit v1.2.3 From 04fb2218cd6a5d32a0b4c1d8de9b9ad43994888d Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Tue, 18 Sep 2018 04:21:34 -0300 Subject: Return less info to the user when verifying --- src/api/routes/verifyGET.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js index 29b521e..b6ade59 100644 --- a/src/api/routes/verifyGET.js +++ b/src/api/routes/verifyGET.js @@ -6,9 +6,16 @@ class verifyGET extends Route { } run(req, res, user) { + const returnUser = { + id: user.id, + username: user.username, + apiKey: user.apiKey, + isAdmin: user.isAdmin + }; + return res.json({ message: 'Successfully verified token', - user + user: returnUser }); } } -- cgit v1.2.3 From 430af8306b1ab17e59a6dabf8f65ab816d28695d Mon Sep 17 00:00:00 2001 From: Pitu Date: Wed, 19 Sep 2018 04:45:50 -0300 Subject: Switch to Nuxt.js --- src/api/routes/files/uploadPOST_Multer.js.bak | 380 ++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 src/api/routes/files/uploadPOST_Multer.js.bak (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST_Multer.js.bak b/src/api/routes/files/uploadPOST_Multer.js.bak new file mode 100644 index 0000000..d6e6436 --- /dev/null +++ b/src/api/routes/files/uploadPOST_Multer.js.bak @@ -0,0 +1,380 @@ +const Route = require('../../structures/Route'); +const config = require('../../../../config'); +const path = require('path'); +const multer = require('multer'); +const Util = require('../../utils/Util'); +const db = require('knex')(config.server.database); +const moment = require('moment'); +const log = require('../../utils/Log'); +const jetpack = require('fs-jetpack'); +const Busboy = require('busboy'); +const fs = require('fs'); +// WE SHOULD ALSO STRIP EXIF UNLESS THE USER SPECIFIED THEY WANT IT. +// https://github.com/WeebDev/lolisafe/issues/110 +class uploadPOST extends Route { + constructor() { + super('/upload', 'post', { bypassAuth: true }); + } + + async run(req, res) { + const user = Util.isAuthorized(req); + if (!user && !config.uploads.allowAnonymousUploads) return res.status(401).json({ message: 'Not authorized to use this resource' }); + + /* + const albumId = req.body.albumId || req.headers.albumId; + if (this.albumId && !this.user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); + if (this.albumId && this.user) { + const album = await db.table('albums').where({ id: this.albumId, userId: this.user.id }).first(); + if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); + } + */ + return this.uploadFile(req, res, user); + } + + async processFile(req, res, user, file) { + /* + Check if the user is trying to upload to an album + */ + const albumId = req.body.albumId || req.headers.albumId; + if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); + if (albumId && user) { + const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); + if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); + } + + let upload = file.data; + /* + If it's a chunked upload but this is not the last part of the chunk, just green light. + Otherwise, put the file together and process it + */ + if (file.body.uuid) { + if (file.body.chunkindex < file.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if + /* + We got a chunk that is not the last part, send smoke signal that we received it. + */ + return res.json({ message: 'Successfully uploaded chunk' }); + } else { + /* + Seems we finally got the last part of a chunk upload + */ + const uploadsDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder); + const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', file.body.uuid); + const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' }); + const originalname = chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.')); + + const tempFile = { + filename: Util.getUniqueFilename(originalname), + originalname, + size: file.body.totalfilesize + }; + + for (const chunkFile of chunkFiles) { + try { + const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop + await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop + } catch (error) { + console.error(error); + } + } + upload = tempFile; + } + } + + console.log(upload); + const hash = await Util.getFileHash(upload.filename); // eslint-disable-line no-await-in-loop + const exists = await db.table('files') // eslint-disable-line no-await-in-loop + .where(function() { + if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this + else this.where('userId', user.id); // eslint-disable-line no-invalid-this + }) + .where({ + hash, + size: upload.size + }) + .first(); + + if (exists) { + res.json({ + message: 'Successfully uploaded file', + name: exists.name, + size: exists.size, + url: `${config.filesServeLocation}/${exists.name}` + }); + + return Util.deleteFile(upload.filename); + } + + const now = moment.utc().toDate(); + try { + await db.table('files').insert({ + userId: user ? user.id : null, + name: upload.filename, + original: upload.originalname, + type: upload.mimetype || '', + size: upload.size, + hash, + ip: req.ip, + albumId: albumId ? albumId : null, + createdAt: now, + editedAt: now + }); + } catch (error) { + log.error('There was an error saving the file to the database'); + console.log(error); + return res.status(500).json({ message: 'There was an error uploading the file.' }); + } + + res.json({ + message: 'Successfully uploaded file', + name: upload.filename, + size: upload.size, + url: `${config.filesServeLocation}/${upload.filename}` + }); + + if (albumId) { + try { + db.table('albums').where('id', albumId).update('editedAt', now); + } catch (error) { + log.error('There was an error updating editedAt on an album'); + console.error(error); + } + } + + // return Util.generateThumbnail(file.filename); + } + + uploadFile(req, res, user) { + const busboy = new Busboy({ + headers: req.headers, + limits: { + fileSize: config.uploads.uploadMaxSize * (1000 * 1000), + files: 1 + } + }); + + const fileToUpload = { + data: {}, + body: {} + }; + + /* + Note: For this to work on every case, whoever is uploading a chunk + should really send the body first and the file last. Otherwise lolisafe + may not catch the field on time and the chunk may end up being saved + as a standalone file, completely broken. + */ + busboy.on('field', (fieldname, val) => { + if (/^dz/.test(fieldname)) { + fileToUpload.body[fieldname.substring(2)] = val; + } else { + fileToUpload.body[fieldname] = val; + } + }); + + /* + Hey ther's a file! Let's upload it. + */ + busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { + let name, saveTo; + + /* + Let check whether the file is part of a chunk upload or if it's a standalone one. + If the former, we should store them separately and join all the pieces after we + receive the last one. + */ + if (!fileToUpload.body.uuid) { + name = Util.getUniqueFilename(filename); + if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' }); + saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, name); + } else { + name = `${filename}.${fileToUpload.body.chunkindex}`; + const chunkDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid); + jetpack.dir(chunkDir); + saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid, name); + } + + /* + Let's save some metadata for the db. + */ + fileToUpload.data = { filename: name, originalname: filename, encoding, mimetype }; + const stream = fs.createWriteStream(saveTo); + + file.on('data', data => { + fileToUpload.data.size = data.length; + }); + + /* + The file that is being uploaded is bigger than the limit specified on the config file + and thus we should close the stream and delete the file. + */ + file.on('limit', () => { + file.unpipe(stream); + stream.end(); + jetpack.removeAsync(saveTo); + res.status(400).json({ message: 'The file is too big.' }); + }); + + file.pipe(stream); + }); + + busboy.on('error', err => { + log.error('There was an error uploading a file'); + console.error(err); + return res.status(500).json({ message: 'There was an error uploading the file.' }); + }); + + busboy.on('finish', () => this.processFile(req, res, user, fileToUpload)); + req.pipe(busboy); + + // return req.pipe(busboy); + + /* + return upload(this.req, this.res, async err => { + if (err) { + log.error('There was an error uploading a file'); + console.error(err); + return this.res.status(500).json({ message: 'There was an error uploading the file.' }); + } + + log.info('---'); + console.log(this.req.file); + log.info('---'); + + let file = this.req.file; + if (this.req.body.uuid) { + // If it's a chunked upload but this is not the last part of the chunk, just green light. + // Otherwise, put the file together and process it + if (this.req.body.chunkindex < this.req.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if + log.info('Hey this is a chunk, sweet.'); + return this.res.json({ message: 'Successfully uploaded chunk' }); + } else { + log.info('Hey this is the last part of a chunk, sweet.'); + + const uploadsDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder); + const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', this.req.body.uuid); + const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' }); + const originalname = chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.')); + + const tempFile = { + filename: Util.getUniqueFilename(originalname), + originalname, + size: this.req.body.totalfilesize + }; + + for (const chunkFile of chunkFiles) { + try { + const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop + await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop + } catch (error) { + console.error(error); + } + } + file = tempFile; + } + } + + const { user } = this; + // console.log(file); + if (!file.filename) return log.error('This file doesnt have a filename!'); + // console.log(file); + const hash = await Util.getFileHash(file.filename); // eslint-disable-line no-await-in-loop + const exists = await db.table('files') // eslint-disable-line no-await-in-loop + .where(function() { + if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this + else this.where('userId', user.id); // eslint-disable-line no-invalid-this + }) + .where({ + hash, + size: file.size + }) + .first(); + + if (exists) { + this.res.json({ + message: 'Successfully uploaded file', + name: exists.name, + size: exists.size, + url: `${config.filesServeLocation}/${exists.name}` + }); + + return Util.deleteFile(file.filename); + } + + const now = moment.utc().toDate(); + try { + await db.table('files').insert({ + userId: this.user ? this.user.id : null, + name: file.filename, + original: file.originalname, + type: file.mimetype || '', + size: file.size, + hash, + ip: this.req.ip, + albumId: this.albumId ? this.albumId : null, + createdAt: now, + editedAt: now + }); + } catch (error) { + log.error('There was an error saving the file to the database'); + console.log(error); + return this.res.status(500).json({ message: 'There was an error uploading the file.' }); + } + + this.res.json({ + message: 'Successfully uploaded file', + name: file.filename, + size: file.size, + url: `${config.filesServeLocation}/${file.filename}` + }); + + if (this.albumId) { + try { + db.table('albums').where('id', this.albumId).update('editedAt', now); + } catch (error) { + log.error('There was an error updating editedAt on an album'); + console.error(error); + } + } + + // return Util.generateThumbnail(file.filename); + }); + */ + } +} + +/* +const upload = multer({ + limits: config.uploads.uploadMaxSize, + fileFilter(req, file, cb) { + const ext = path.extname(file.originalname).toLowerCase(); + if (Util.isExtensionBlocked(ext)) return cb('This file extension is not allowed'); + + // Remove those pesky dz prefixes. Thanks to BobbyWibowo. + for (const key in req.body) { + if (!/^dz/.test(key)) continue; + req.body[key.replace(/^dz/, '')] = req.body[key]; + delete req.body[key]; + } + + return cb(null, true); + }, + storage: multer.diskStorage({ + destination(req, file, cb) { + if (!req.body.uuid) return cb(null, path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder)); + // Hey, we have chunks + + const chunkDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', req.body.uuid); + jetpack.dir(chunkDir); + return cb(null, chunkDir); + return cb(null, path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder)); + }, + filename(req, file, cb) { + // if (req.body.uuid) return cb(null, `${file.originalname}.${req.body.chunkindex}`); + const filename = Util.getUniqueFilename(file.originalname); + // if (!filename) return cb('Could not allocate a unique file name'); + return cb(null, filename); + } + }) +}).single('file'); +*/ +module.exports = uploadPOST; -- cgit v1.2.3 From 63f327e49d24f30ddbf682929e53743b9877a03c Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 18 Feb 2019 23:43:15 +0900 Subject: CRLF to LF --- src/api/routes/files/uploadPOST.js | 1 - src/api/routes/files/uploadPOST_Multer.js.bak | 380 -------------------------- 2 files changed, 381 deletions(-) delete mode 100644 src/api/routes/files/uploadPOST_Multer.js.bak (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 580e3fd..f217167 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -15,7 +15,6 @@ const fs = require('fs'); TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover. TODO: If source is a video, generate a thumb of the first frame and save the video length. TODO: Check that the async isAuthorized works and is not nulling out - TODO: Store timestamps in human readable format? */ class uploadPOST extends Route { diff --git a/src/api/routes/files/uploadPOST_Multer.js.bak b/src/api/routes/files/uploadPOST_Multer.js.bak deleted file mode 100644 index d6e6436..0000000 --- a/src/api/routes/files/uploadPOST_Multer.js.bak +++ /dev/null @@ -1,380 +0,0 @@ -const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const path = require('path'); -const multer = require('multer'); -const Util = require('../../utils/Util'); -const db = require('knex')(config.server.database); -const moment = require('moment'); -const log = require('../../utils/Log'); -const jetpack = require('fs-jetpack'); -const Busboy = require('busboy'); -const fs = require('fs'); -// WE SHOULD ALSO STRIP EXIF UNLESS THE USER SPECIFIED THEY WANT IT. -// https://github.com/WeebDev/lolisafe/issues/110 -class uploadPOST extends Route { - constructor() { - super('/upload', 'post', { bypassAuth: true }); - } - - async run(req, res) { - const user = Util.isAuthorized(req); - if (!user && !config.uploads.allowAnonymousUploads) return res.status(401).json({ message: 'Not authorized to use this resource' }); - - /* - const albumId = req.body.albumId || req.headers.albumId; - if (this.albumId && !this.user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); - if (this.albumId && this.user) { - const album = await db.table('albums').where({ id: this.albumId, userId: this.user.id }).first(); - if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); - } - */ - return this.uploadFile(req, res, user); - } - - async processFile(req, res, user, file) { - /* - Check if the user is trying to upload to an album - */ - const albumId = req.body.albumId || req.headers.albumId; - if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); - if (albumId && user) { - const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); - if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); - } - - let upload = file.data; - /* - If it's a chunked upload but this is not the last part of the chunk, just green light. - Otherwise, put the file together and process it - */ - if (file.body.uuid) { - if (file.body.chunkindex < file.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if - /* - We got a chunk that is not the last part, send smoke signal that we received it. - */ - return res.json({ message: 'Successfully uploaded chunk' }); - } else { - /* - Seems we finally got the last part of a chunk upload - */ - const uploadsDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder); - const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', file.body.uuid); - const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' }); - const originalname = chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.')); - - const tempFile = { - filename: Util.getUniqueFilename(originalname), - originalname, - size: file.body.totalfilesize - }; - - for (const chunkFile of chunkFiles) { - try { - const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop - await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop - } catch (error) { - console.error(error); - } - } - upload = tempFile; - } - } - - console.log(upload); - const hash = await Util.getFileHash(upload.filename); // eslint-disable-line no-await-in-loop - const exists = await db.table('files') // eslint-disable-line no-await-in-loop - .where(function() { - if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this - else this.where('userId', user.id); // eslint-disable-line no-invalid-this - }) - .where({ - hash, - size: upload.size - }) - .first(); - - if (exists) { - res.json({ - message: 'Successfully uploaded file', - name: exists.name, - size: exists.size, - url: `${config.filesServeLocation}/${exists.name}` - }); - - return Util.deleteFile(upload.filename); - } - - const now = moment.utc().toDate(); - try { - await db.table('files').insert({ - userId: user ? user.id : null, - name: upload.filename, - original: upload.originalname, - type: upload.mimetype || '', - size: upload.size, - hash, - ip: req.ip, - albumId: albumId ? albumId : null, - createdAt: now, - editedAt: now - }); - } catch (error) { - log.error('There was an error saving the file to the database'); - console.log(error); - return res.status(500).json({ message: 'There was an error uploading the file.' }); - } - - res.json({ - message: 'Successfully uploaded file', - name: upload.filename, - size: upload.size, - url: `${config.filesServeLocation}/${upload.filename}` - }); - - if (albumId) { - try { - db.table('albums').where('id', albumId).update('editedAt', now); - } catch (error) { - log.error('There was an error updating editedAt on an album'); - console.error(error); - } - } - - // return Util.generateThumbnail(file.filename); - } - - uploadFile(req, res, user) { - const busboy = new Busboy({ - headers: req.headers, - limits: { - fileSize: config.uploads.uploadMaxSize * (1000 * 1000), - files: 1 - } - }); - - const fileToUpload = { - data: {}, - body: {} - }; - - /* - Note: For this to work on every case, whoever is uploading a chunk - should really send the body first and the file last. Otherwise lolisafe - may not catch the field on time and the chunk may end up being saved - as a standalone file, completely broken. - */ - busboy.on('field', (fieldname, val) => { - if (/^dz/.test(fieldname)) { - fileToUpload.body[fieldname.substring(2)] = val; - } else { - fileToUpload.body[fieldname] = val; - } - }); - - /* - Hey ther's a file! Let's upload it. - */ - busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { - let name, saveTo; - - /* - Let check whether the file is part of a chunk upload or if it's a standalone one. - If the former, we should store them separately and join all the pieces after we - receive the last one. - */ - if (!fileToUpload.body.uuid) { - name = Util.getUniqueFilename(filename); - if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' }); - saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, name); - } else { - name = `${filename}.${fileToUpload.body.chunkindex}`; - const chunkDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid); - jetpack.dir(chunkDir); - saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid, name); - } - - /* - Let's save some metadata for the db. - */ - fileToUpload.data = { filename: name, originalname: filename, encoding, mimetype }; - const stream = fs.createWriteStream(saveTo); - - file.on('data', data => { - fileToUpload.data.size = data.length; - }); - - /* - The file that is being uploaded is bigger than the limit specified on the config file - and thus we should close the stream and delete the file. - */ - file.on('limit', () => { - file.unpipe(stream); - stream.end(); - jetpack.removeAsync(saveTo); - res.status(400).json({ message: 'The file is too big.' }); - }); - - file.pipe(stream); - }); - - busboy.on('error', err => { - log.error('There was an error uploading a file'); - console.error(err); - return res.status(500).json({ message: 'There was an error uploading the file.' }); - }); - - busboy.on('finish', () => this.processFile(req, res, user, fileToUpload)); - req.pipe(busboy); - - // return req.pipe(busboy); - - /* - return upload(this.req, this.res, async err => { - if (err) { - log.error('There was an error uploading a file'); - console.error(err); - return this.res.status(500).json({ message: 'There was an error uploading the file.' }); - } - - log.info('---'); - console.log(this.req.file); - log.info('---'); - - let file = this.req.file; - if (this.req.body.uuid) { - // If it's a chunked upload but this is not the last part of the chunk, just green light. - // Otherwise, put the file together and process it - if (this.req.body.chunkindex < this.req.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if - log.info('Hey this is a chunk, sweet.'); - return this.res.json({ message: 'Successfully uploaded chunk' }); - } else { - log.info('Hey this is the last part of a chunk, sweet.'); - - const uploadsDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder); - const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', this.req.body.uuid); - const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' }); - const originalname = chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.')); - - const tempFile = { - filename: Util.getUniqueFilename(originalname), - originalname, - size: this.req.body.totalfilesize - }; - - for (const chunkFile of chunkFiles) { - try { - const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop - await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop - } catch (error) { - console.error(error); - } - } - file = tempFile; - } - } - - const { user } = this; - // console.log(file); - if (!file.filename) return log.error('This file doesnt have a filename!'); - // console.log(file); - const hash = await Util.getFileHash(file.filename); // eslint-disable-line no-await-in-loop - const exists = await db.table('files') // eslint-disable-line no-await-in-loop - .where(function() { - if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this - else this.where('userId', user.id); // eslint-disable-line no-invalid-this - }) - .where({ - hash, - size: file.size - }) - .first(); - - if (exists) { - this.res.json({ - message: 'Successfully uploaded file', - name: exists.name, - size: exists.size, - url: `${config.filesServeLocation}/${exists.name}` - }); - - return Util.deleteFile(file.filename); - } - - const now = moment.utc().toDate(); - try { - await db.table('files').insert({ - userId: this.user ? this.user.id : null, - name: file.filename, - original: file.originalname, - type: file.mimetype || '', - size: file.size, - hash, - ip: this.req.ip, - albumId: this.albumId ? this.albumId : null, - createdAt: now, - editedAt: now - }); - } catch (error) { - log.error('There was an error saving the file to the database'); - console.log(error); - return this.res.status(500).json({ message: 'There was an error uploading the file.' }); - } - - this.res.json({ - message: 'Successfully uploaded file', - name: file.filename, - size: file.size, - url: `${config.filesServeLocation}/${file.filename}` - }); - - if (this.albumId) { - try { - db.table('albums').where('id', this.albumId).update('editedAt', now); - } catch (error) { - log.error('There was an error updating editedAt on an album'); - console.error(error); - } - } - - // return Util.generateThumbnail(file.filename); - }); - */ - } -} - -/* -const upload = multer({ - limits: config.uploads.uploadMaxSize, - fileFilter(req, file, cb) { - const ext = path.extname(file.originalname).toLowerCase(); - if (Util.isExtensionBlocked(ext)) return cb('This file extension is not allowed'); - - // Remove those pesky dz prefixes. Thanks to BobbyWibowo. - for (const key in req.body) { - if (!/^dz/.test(key)) continue; - req.body[key.replace(/^dz/, '')] = req.body[key]; - delete req.body[key]; - } - - return cb(null, true); - }, - storage: multer.diskStorage({ - destination(req, file, cb) { - if (!req.body.uuid) return cb(null, path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder)); - // Hey, we have chunks - - const chunkDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', req.body.uuid); - jetpack.dir(chunkDir); - return cb(null, chunkDir); - return cb(null, path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder)); - }, - filename(req, file, cb) { - // if (req.body.uuid) return cb(null, `${file.originalname}.${req.body.chunkindex}`); - const filename = Util.getUniqueFilename(file.originalname); - // if (!filename) return cb('Could not allocate a unique file name'); - return cb(null, filename); - } - }) -}).single('file'); -*/ -module.exports = uploadPOST; -- cgit v1.2.3 From e33cf304495d327b152a01cc6906643ccd8dd62a Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 19 Feb 2019 00:06:38 +0900 Subject: Changes --- src/api/routes/configGET.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/api/routes/configGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/configGET.js b/src/api/routes/configGET.js new file mode 100644 index 0000000..4bc7e15 --- /dev/null +++ b/src/api/routes/configGET.js @@ -0,0 +1,22 @@ +const Route = require('../structures/Route'); +const config = require('../../../config'); + +class configGET extends Route { + constructor() { + super('/config', 'get', { bypassAuth: true }); + } + + run(req, res) { + return res.json({ + version: process.env.npm_package_version, + URL: config.filesServeLocatio, + baseURL: config.backendLocation, + serviceName: config.serviceName, + maxFileSize: config.uploads.uploadMaxSize, + chunkSize: config.uploads.chunkSize, + maxLinksPerAlbum: config.albums.maxLinksPerAlbum + }); + } +} + +module.exports = configGET; -- cgit v1.2.3 From 89a271818ed25b0a17a17dd1d6804e34d1f2ec0f Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 19 Feb 2019 23:52:24 +0900 Subject: Switch config to .env --- src/api/routes/albums/albumDELETE.js | 4 +- src/api/routes/albums/albumGET.js | 4 +- src/api/routes/albums/albumPOST.js | 4 +- src/api/routes/albums/albumZipGET.js | 8 +-- src/api/routes/albums/albumsGET.js | 6 +- src/api/routes/albums/link/linkEditPOST.js | 4 +- src/api/routes/albums/link/linkPOST.js | 6 +- src/api/routes/auth/changePasswordPOST.js | 4 +- src/api/routes/auth/registerPOST.js | 6 +- src/api/routes/configGET.js | 22 -------- src/api/routes/files/fileDELETE.js | 4 +- src/api/routes/files/filesGET.js | 4 +- src/api/routes/files/uploadPOST.js | 90 +++++++++++++++--------------- src/api/routes/verifyGET.js | 2 +- 14 files changed, 61 insertions(+), 107 deletions(-) delete mode 100644 src/api/routes/configGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index eefbf41..3fdf209 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -1,6 +1,4 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const db = require('knex')(config.server.database); const Util = require('../../utils/Util'); const log = require('../../utils/Log'); @@ -9,7 +7,7 @@ class albumDELETE extends Route { super('/album/:id/:purge*?', 'delete'); } - async run(req, res, user) { + async run(req, res, db, user) { const { id, purge } = req.params; if (!id) return res.status(400).json({ message: 'Invalid album ID supplied' }); diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index f5e339f..59398a1 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -1,6 +1,4 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const db = require('knex')(config.server.database); const Util = require('../../utils/Util'); class albumGET extends Route { @@ -8,7 +6,7 @@ class albumGET extends Route { super('/album/:identifier', 'get', { bypassAuth: true }); } - async run(req, res) { + async run(req, res, db) { const { identifier } = req.params; if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); diff --git a/src/api/routes/albums/albumPOST.js b/src/api/routes/albums/albumPOST.js index 12b88fa..0d3a44c 100644 --- a/src/api/routes/albums/albumPOST.js +++ b/src/api/routes/albums/albumPOST.js @@ -1,6 +1,4 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const db = require('knex')(config.server.database); const moment = require('moment'); class albumPOST extends Route { @@ -8,7 +6,7 @@ class albumPOST extends Route { super('/album/new', 'post'); } - async run(req, res, user) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { name } = req.body; if (!name) return res.status(400).json({ message: 'No name provided' }); diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index 7a853cd..9419654 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -1,6 +1,4 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const db = require('knex')(config.server.database); const Util = require('../../utils/Util'); const log = require('../../utils/Log'); const path = require('path'); @@ -11,7 +9,7 @@ class albumGET extends Route { super('/album/:identifier/zip', 'get', { bypassAuth: true }); } - async run(req, res) { + async run(req, res, db) { const { identifier } = req.params; if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); @@ -31,7 +29,7 @@ class albumGET extends Route { If the date when the album was zipped is greater than the album's last edit, we just send the zip to the user */ if (album.zippedAt > album.editedAt) { - const filePath = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'zips', `${album.userId}-${album.id}.zip`); + const filePath = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); const exists = await jetpack.existsAsync(filePath); /* Make sure the file exists just in case, and if not, continue to it's generation. @@ -65,7 +63,7 @@ class albumGET extends Route { Util.createZip(filesToZip, album); await db.table('albums').where('id', link.albumId).update('zippedAt', db.fn.now()); - const filePath = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'zips', `${album.userId}-${album.id}.zip`); + const filePath = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); const fileName = `lolisafe-${identifier}.zip`; return res.download(filePath, fileName); } catch (error) { diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index b19e03a..3be1213 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -1,6 +1,4 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const db = require('knex')(config.server.database); const Util = require('../../utils/Util'); class albumsGET extends Route { @@ -8,7 +6,7 @@ class albumsGET extends Route { super('/albums/mini', 'get'); } - async run(req, res, user) { + async run(req, res, db, user) { /* Let's fetch the albums. This route will only return a small portion of the album files for displaying on the dashboard. It's probably useless @@ -72,7 +70,7 @@ class albumsDropdownGET extends Route { super('/albums/dropdown', 'get'); } - async run(req, res, user) { + async run(req, res, db, user) { const albums = await db.table('albums') .where('userId', user.id) .select('id', 'name'); diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index d9dbcac..753c496 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -1,6 +1,4 @@ const Route = require('../../../structures/Route'); -const config = require('../../../../../config'); -const db = require('knex')(config.server.database); const log = require('../../../utils/Log'); class linkEditPOST extends Route { @@ -8,7 +6,7 @@ class linkEditPOST extends Route { super('/album/link/edit', 'post'); } - async run(req, res, user) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { identifier, enabled, enableDownload, expiresAt } = req.body; if (!identifier) return res.status(400).json({ message: 'Invalid album identifier supplied' }); diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 1edf891..91e1521 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -1,6 +1,4 @@ const Route = require('../../../structures/Route'); -const config = require('../../../../../config'); -const db = require('knex')(config.server.database); const Util = require('../../../utils/Util'); const log = require('../../../utils/Log'); @@ -9,7 +7,7 @@ class linkPOST extends Route { super('/album/link/new', 'post'); } - async run(req, res, user) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { albumId } = req.body; if (!albumId) return res.status(400).json({ message: 'No album provided' }); @@ -24,7 +22,7 @@ class linkPOST extends Route { Count the amount of links created for that album already and error out if max was reached */ const count = await db.table('links').where('albumId', albumId).count({ count: 'id' }); - if (count[0].count >= config.albums.maxLinksPerAlbum) return res.status(400).json({ message: 'Maximum links per album reached' }); + if (count[0].count >= process.env.MAX_LINKS_PER_ALBUM) return res.status(400).json({ message: 'Maximum links per album reached' }); /* Try to allocate a new identifier on the db diff --git a/src/api/routes/auth/changePasswordPOST.js b/src/api/routes/auth/changePasswordPOST.js index bd64320..d698896 100644 --- a/src/api/routes/auth/changePasswordPOST.js +++ b/src/api/routes/auth/changePasswordPOST.js @@ -1,7 +1,5 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); const log = require('../../utils/Log'); -const db = require('knex')(config.server.database); const bcrypt = require('bcrypt'); const moment = require('moment'); @@ -10,7 +8,7 @@ class changePasswordPOST extends Route { super('/auth/password/change', 'post'); } - async run(req, res, user) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { password, newPassword } = req.body; if (!password || !newPassword) return res.status(401).json({ message: 'Invalid body provided' }); diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index d3532f4..762eaf2 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -1,7 +1,5 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); const log = require('../../utils/Log'); -const db = require('knex')(config.server.database); const bcrypt = require('bcrypt'); const randomstring = require('randomstring'); const moment = require('moment'); @@ -11,8 +9,8 @@ class registerPOST extends Route { super('/auth/register', 'post', { bypassAuth: true }); } - async run(req, res) { - if (!config.enableCreateUserAccounts) return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); + async run(req, res, db) { + if (!process.env.USER_ACCOUNTS) return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { username, password } = req.body; if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' }); diff --git a/src/api/routes/configGET.js b/src/api/routes/configGET.js deleted file mode 100644 index 4bc7e15..0000000 --- a/src/api/routes/configGET.js +++ /dev/null @@ -1,22 +0,0 @@ -const Route = require('../structures/Route'); -const config = require('../../../config'); - -class configGET extends Route { - constructor() { - super('/config', 'get', { bypassAuth: true }); - } - - run(req, res) { - return res.json({ - version: process.env.npm_package_version, - URL: config.filesServeLocatio, - baseURL: config.backendLocation, - serviceName: config.serviceName, - maxFileSize: config.uploads.uploadMaxSize, - chunkSize: config.uploads.chunkSize, - maxLinksPerAlbum: config.albums.maxLinksPerAlbum - }); - } -} - -module.exports = configGET; diff --git a/src/api/routes/files/fileDELETE.js b/src/api/routes/files/fileDELETE.js index b50e576..c659255 100644 --- a/src/api/routes/files/fileDELETE.js +++ b/src/api/routes/files/fileDELETE.js @@ -1,6 +1,4 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const db = require('knex')(config.server.database); const Util = require('../../utils/Util'); const log = require('../../utils/Log'); @@ -9,7 +7,7 @@ class fileDELETE extends Route { super('/file/:id', 'delete'); } - async run(req, res, user) { + async run(req, res, db, user) { const { id } = req.params; if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index d1b6619..b41996b 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -1,6 +1,4 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const db = require('knex')(config.server.database); const Util = require('../../utils/Util'); class filesGET extends Route { @@ -8,7 +6,7 @@ class filesGET extends Route { super('/files', 'get'); } - async run(req, res, user) { + async run(req, res, db, user) { /* Get all the files from the user */ diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index f217167..f83148f 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -1,8 +1,6 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); const path = require('path'); const Util = require('../../utils/Util'); -const db = require('knex')(config.server.database); const moment = require('moment'); const log = require('../../utils/Log'); const jetpack = require('fs-jetpack'); @@ -22,13 +20,13 @@ class uploadPOST extends Route { super('/upload', 'post', { bypassAuth: true }); } - async run(req, res) { + async run(req, res, db) { const user = await Util.isAuthorized(req); - if (!user && !config.uploads.allowAnonymousUploads) return res.status(401).json({ message: 'Not authorized to use this resource' }); - return this.uploadFile(req, res, user); + if (!user && !process.env.PUBLIC_MODE) return res.status(401).json({ message: 'Not authorized to use this resource' }); + return this.uploadFile(req, res, db, user); } - async processFile(req, res, user, file) { + async processFile(req, res, db, user, file) { /* Check if the user is trying to upload to an album */ @@ -55,38 +53,37 @@ class uploadPOST extends Route { We got a chunk that is not the last part, send smoke signal that we received it. */ return res.json({ message: 'Successfully uploaded chunk' }); - } else { - /* - Seems we finally got the last part of a chunk upload - */ - const uploadsDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder); - const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', file.body.uuid); - const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' }); - const originalname = Util.getFilenameFromPath(chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.'))); - - const tempFile = { - filename: Util.getUniqueFilename(originalname), - originalname, - size: file.body.totalfilesize - }; - - for (const chunkFile of chunkFiles) { - try { - const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop - await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop - } catch (error) { - log.error(error); - } - } - + } + /* + Seems we finally got the last part of a chunk upload + */ + const uploadsDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER); + const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', file.body.uuid); + const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' }); + const originalname = Util.getFilenameFromPath(chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.'))); + + const tempFile = { + filename: Util.getUniqueFilename(originalname), + originalname, + size: file.body.totalfilesize + }; + + for (const chunkFile of chunkFiles) { try { - await jetpack.removeAsync(chunkedFileDir); + const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop + await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop } catch (error) { log.error(error); } + } - upload = tempFile; + try { + await jetpack.removeAsync(chunkedFileDir); + } catch (error) { + log.error(error); } + + upload = tempFile; } /* @@ -109,7 +106,7 @@ class uploadPOST extends Route { message: 'Successfully uploaded file BUT IT EXISTED ALREADY', name: exists.name, size: exists.size, - url: `${config.filesServeLocation}/${exists.name}` + url: `${process.env.DOMAIN}/${exists.name}` }); return Util.deleteFile(upload.filename); @@ -147,7 +144,7 @@ class uploadPOST extends Route { message: 'Successfully uploaded file', name: upload.filename, size: upload.size, - url: `${config.filesServeLocation}/${upload.filename}` + url: `${process.env.DOMAIN}/${upload.filename}` }); /* @@ -167,7 +164,7 @@ class uploadPOST extends Route { /* If exif removal has been force service-wide or requested by the user, remove it */ - if (config.uploads.forceStripExif) { // || user.settings.stripExif) { + if (process.env.STRIP_EXIF) { // || user.settings.stripExif) { // Util.removeExif(upload.filename); } @@ -177,11 +174,11 @@ class uploadPOST extends Route { return Util.generateThumbnails(upload.filename); } - uploadFile(req, res, user) { + uploadFile(req, res, db, user) { const busboy = new Busboy({ headers: req.headers, limits: { - fileSize: config.uploads.uploadMaxSize * (1000 * 1000), + fileSize: process.env.MAX_SIZE * (1000 * 1000), files: 1 } }); @@ -209,7 +206,8 @@ class uploadPOST extends Route { Hey ther's a file! Let's upload it. */ busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { - let name, saveTo; + let name; + let saveTo; /* Let check whether the file is part of a chunk upload or if it's a standalone one. @@ -219,15 +217,15 @@ class uploadPOST extends Route { const ext = path.extname(filename).toLowerCase(); if (Util.isExtensionBlocked(ext)) return res.status(400).json({ message: 'This extension is not allowed.' }); - if (!fileToUpload.body.uuid) { - name = Util.getUniqueFilename(filename); - if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' }); - saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, name); - } else { + if (fileToUpload.body.uuid) { name = `${filename}.${fileToUpload.body.chunkindex}`; - const chunkDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid); + const chunkDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', fileToUpload.body.uuid); jetpack.dir(chunkDir); - saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid, name); + saveTo = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', fileToUpload.body.uuid, name); + } else { + name = Util.getUniqueFilename(filename); + if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' }); + saveTo = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, name); } /* @@ -269,7 +267,7 @@ class uploadPOST extends Route { return res.status(500).json({ message: 'There was an error uploading the file.' }); }); - busboy.on('finish', () => this.processFile(req, res, user, fileToUpload)); + busboy.on('finish', () => this.processFile(req, res, db, user, fileToUpload)); req.pipe(busboy); } } diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js index b6ade59..e588c22 100644 --- a/src/api/routes/verifyGET.js +++ b/src/api/routes/verifyGET.js @@ -5,7 +5,7 @@ class verifyGET extends Route { super('/verify', 'get'); } - run(req, res, user) { + run(req, res, db, user) { const returnUser = { id: user.id, username: user.username, -- cgit v1.2.3 From a284a9a0645774547d9b56887504cd72161e11ff Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 22 Feb 2019 00:37:20 +0900 Subject: Leftovers --- src/api/routes/auth/loginPOST.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index eaf09e8..760e54b 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -1,6 +1,4 @@ const Route = require('../../structures/Route'); -const config = require('../../../../config'); -const db = require('knex')(config.server.database); const bcrypt = require('bcrypt'); const moment = require('moment'); const JWT = require('jsonwebtoken'); @@ -10,7 +8,7 @@ class loginPOST extends Route { super('/auth/login', 'post', { bypassAuth: true }); } - async run(req, res) { + async run(req, res, db) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { username, password } = req.body; if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' }); @@ -34,7 +32,7 @@ class loginPOST extends Route { iss: 'lolisafe', sub: user.id, iat: moment.utc().valueOf() - }, config.server.secret, { expiresIn: '30d' }); + }, process.env.SECRET, { expiresIn: '30d' }); return res.json({ message: 'Successfully logged in.', -- cgit v1.2.3 From 5b7dcc75763dca4416a4fdbe85cb6c70fad58dbb Mon Sep 17 00:00:00 2001 From: Kana Date: Fri, 22 Feb 2019 10:06:43 +0900 Subject: WIP deleteUrl --- src/api/routes/files/uploadPOST.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index f83148f..d6cb8b7 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -106,7 +106,8 @@ class uploadPOST extends Route { message: 'Successfully uploaded file BUT IT EXISTED ALREADY', name: exists.name, size: exists.size, - url: `${process.env.DOMAIN}/${exists.name}` + url: `${process.env.DOMAIN}/${exists.name}`, + deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}` }); return Util.deleteFile(upload.filename); @@ -145,6 +146,7 @@ class uploadPOST extends Route { name: upload.filename, size: upload.size, url: `${process.env.DOMAIN}/${upload.filename}` + // deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}` }); /* -- cgit v1.2.3 From fc95cb7b0f047806937c25f0fc1104c72b0a32cb Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 23 Feb 2019 00:45:45 +0900 Subject: Better DB handling and stuff --- src/api/routes/albums/link/linkPOST.js | 3 ++- src/api/routes/auth/registerPOST.js | 4 +++- src/api/routes/files/uploadPOST.js | 44 ++++++++++++++++++++++++---------- 3 files changed, 36 insertions(+), 15 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 91e1521..968e57d 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -37,7 +37,8 @@ class linkPOST extends Route { albumId, enabled: true, enableDownload: true, - expiresAt: null + expiresAt: null, + views: 0 }); return res.json({ diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index 762eaf2..ee8e5ae 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -51,7 +51,9 @@ class registerPOST extends Route { apiKey: randomstring.generate(64), apiKeyEditedAt: now, createdAt: now, - editedAt: now + editedAt: now, + enabled: true, + isAdmin: false }); return res.json({ message: 'The account was created successfully' }); } diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index d6cb8b7..fdab035 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -118,19 +118,37 @@ class uploadPOST extends Route { store the details on the database. */ const now = moment.utc().toDate(); - let inserted = null; + let insertedId = null; try { - inserted = await db.table('files').insert({ - userId: user ? user.id : null, - name: upload.filename, - original: upload.originalname, - type: upload.mimetype || '', - size: upload.size, - hash, - ip: req.ip, - createdAt: now, - editedAt: now - }, 'id'); + /* + This is so fucking dumb + */ + if (process.env.DB_CLIENT === 'sqlite3') { + insertedId = await db.table('files').insert({ + userId: user ? user.id : null, + name: upload.filename, + original: upload.originalname, + type: upload.mimetype || '', + size: upload.size, + hash, + ip: req.ip, + createdAt: now, + editedAt: now + }); + } else { + insertedId = await db.table('files').insert({ + userId: user ? user.id : null, + name: upload.filename, + original: upload.originalname, + type: upload.mimetype || '', + size: upload.size, + hash, + ip: req.ip, + createdAt: now, + editedAt: now + }, 'id'); + } + /* TODO: Something funny here, I'm not sure since I don't use MySQL but I think the argument id on the insert function on top behaves differently on psql/mysql/sqlite. Needs testing. @@ -155,7 +173,7 @@ class uploadPOST extends Route { */ if (albumId) { try { - await db.table('albumsFiles').insert({ albumId, fileId: inserted[0] }); + await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] }); await db.table('albums').where('id', albumId).update('editedAt', now); } catch (error) { log.error('There was an error updating editedAt on an album'); -- cgit v1.2.3 From c8e0ebd8ff48c0f679b6a46a8a23fa2ed5077598 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 23 Feb 2019 02:30:53 +0900 Subject: add deleteUrl when uploading a file --- src/api/routes/files/uploadPOST.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index fdab035..52e92d6 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -163,8 +163,8 @@ class uploadPOST extends Route { message: 'Successfully uploaded file', name: upload.filename, size: upload.size, - url: `${process.env.DOMAIN}/${upload.filename}` - // deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}` + url: `${process.env.DOMAIN}/${upload.filename}`, + deleteUrl: `${process.env.DOMAIN}/api/file/${insertedId[0]}` }); /* -- cgit v1.2.3 From 80732ff90ad8dd0aebc986816f0afd87aecc4ffa Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 26 Feb 2019 22:26:18 +0900 Subject: User promotion/demotion --- src/api/routes/admin/userDemote.js | 27 +++++++++++++++++++++++++++ src/api/routes/admin/userPromote.js | 27 +++++++++++++++++++++++++++ src/api/routes/admin/usersGET.js | 23 +++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/api/routes/admin/userDemote.js create mode 100644 src/api/routes/admin/userPromote.js create mode 100644 src/api/routes/admin/usersGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/admin/userDemote.js b/src/api/routes/admin/userDemote.js new file mode 100644 index 0000000..e9c37a0 --- /dev/null +++ b/src/api/routes/admin/userDemote.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); + +class userDemote extends Route { + constructor() { + super('/admin/users/demote', 'get', { adminOnly: true }); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { id } = req.body; + if (!id) return res.status(400).json({ message: 'No name provided' }); + + try { + await db.table('users') + .where({ id }) + .update({ isAdmin: false }); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully promoted user' + }); + } +} + +module.exports = userDemote; diff --git a/src/api/routes/admin/userPromote.js b/src/api/routes/admin/userPromote.js new file mode 100644 index 0000000..caae176 --- /dev/null +++ b/src/api/routes/admin/userPromote.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); + +class userPromote extends Route { + constructor() { + super('/admin/users/promote', 'get', { adminOnly: true }); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { id } = req.body; + if (!id) return res.status(400).json({ message: 'No name provided' }); + + try { + await db.table('users') + .where({ id }) + .update({ isAdmin: true }); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully promoted user' + }); + } +} + +module.exports = userPromote; diff --git a/src/api/routes/admin/usersGET.js b/src/api/routes/admin/usersGET.js new file mode 100644 index 0000000..52a707f --- /dev/null +++ b/src/api/routes/admin/usersGET.js @@ -0,0 +1,23 @@ +const Route = require('../../structures/Route'); + +class usersGET extends Route { + constructor() { + super('/admin/users', 'get', { adminOnly: true }); + } + + async run(req, res, db) { + try { + const users = await db.table('users') + .select('id', 'username', 'enabled', 'isAdmin', 'createdAt'); + + return res.json({ + message: 'Successfully retrieved users', + users + }); + } catch (error) { + return super.error(res, error); + } + } +} + +module.exports = usersGET; -- cgit v1.2.3 From 6cd31674d5d72a20eae25cd2296d9270d999e270 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 26 Feb 2019 22:26:54 +0900 Subject: Not needed anymore --- src/api/routes/files/uploadPOST.js | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 52e92d6..b003311 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -148,11 +148,6 @@ class uploadPOST extends Route { editedAt: now }, 'id'); } - - /* - TODO: Something funny here, I'm not sure since I don't use MySQL but I think the argument id - on the insert function on top behaves differently on psql/mysql/sqlite. Needs testing. - */ } catch (error) { log.error('There was an error saving the file to the database'); log.error(error); -- cgit v1.2.3 From 7a74647d3e5b5681b9d5d3fa9b6e12d062232683 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 26 Feb 2019 23:13:24 +0900 Subject: User management --- src/api/routes/admin/userDemote.js | 6 +++--- src/api/routes/admin/userDisable.js | 27 +++++++++++++++++++++++++++ src/api/routes/admin/userEnable.js | 27 +++++++++++++++++++++++++++ src/api/routes/admin/userPromote.js | 4 ++-- 4 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 src/api/routes/admin/userDisable.js create mode 100644 src/api/routes/admin/userEnable.js (limited to 'src/api/routes') diff --git a/src/api/routes/admin/userDemote.js b/src/api/routes/admin/userDemote.js index e9c37a0..fa288fc 100644 --- a/src/api/routes/admin/userDemote.js +++ b/src/api/routes/admin/userDemote.js @@ -2,13 +2,13 @@ const Route = require('../../structures/Route'); class userDemote extends Route { constructor() { - super('/admin/users/demote', 'get', { adminOnly: true }); + super('/admin/users/demote', 'post', { adminOnly: true }); } async run(req, res, db) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; - if (!id) return res.status(400).json({ message: 'No name provided' }); + if (!id) return res.status(400).json({ message: 'No id provided' }); try { await db.table('users') @@ -19,7 +19,7 @@ class userDemote extends Route { } return res.json({ - message: 'Successfully promoted user' + message: 'Successfully demoted user' }); } } diff --git a/src/api/routes/admin/userDisable.js b/src/api/routes/admin/userDisable.js new file mode 100644 index 0000000..c7dffa8 --- /dev/null +++ b/src/api/routes/admin/userDisable.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); + +class userDisable extends Route { + constructor() { + super('/admin/users/disable', 'post', { adminOnly: true }); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { id } = req.body; + if (!id) return res.status(400).json({ message: 'No id provided' }); + + try { + await db.table('users') + .where({ id }) + .update({ enabled: false }); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully disabled user' + }); + } +} + +module.exports = userDisable; diff --git a/src/api/routes/admin/userEnable.js b/src/api/routes/admin/userEnable.js new file mode 100644 index 0000000..7e5743d --- /dev/null +++ b/src/api/routes/admin/userEnable.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); + +class userEnable extends Route { + constructor() { + super('/admin/users/enable', 'post', { adminOnly: true }); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { id } = req.body; + if (!id) return res.status(400).json({ message: 'No id provided' }); + + try { + await db.table('users') + .where({ id }) + .update({ enabled: true }); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully enabled user' + }); + } +} + +module.exports = userEnable; diff --git a/src/api/routes/admin/userPromote.js b/src/api/routes/admin/userPromote.js index caae176..4062dfa 100644 --- a/src/api/routes/admin/userPromote.js +++ b/src/api/routes/admin/userPromote.js @@ -2,13 +2,13 @@ const Route = require('../../structures/Route'); class userPromote extends Route { constructor() { - super('/admin/users/promote', 'get', { adminOnly: true }); + super('/admin/users/promote', 'post', { adminOnly: true }); } async run(req, res, db) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; - if (!id) return res.status(400).json({ message: 'No name provided' }); + if (!id) return res.status(400).json({ message: 'No id provided' }); try { await db.table('users') -- cgit v1.2.3 From f37d20694386e59622fdfab586a9b580011efce6 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 28 Feb 2019 23:26:28 +0900 Subject: Change password and api keys --- src/api/routes/auth/apiKey.js | 23 ------------------ src/api/routes/user/apiKey.js | 27 +++++++++++++++++++++ src/api/routes/user/changePasswordPOST.js | 40 +++++++++++++++++++++++++++++++ src/api/routes/user/userGET.js | 21 ++++++++++++++++ 4 files changed, 88 insertions(+), 23 deletions(-) delete mode 100644 src/api/routes/auth/apiKey.js create mode 100644 src/api/routes/user/apiKey.js create mode 100644 src/api/routes/user/changePasswordPOST.js create mode 100644 src/api/routes/user/userGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/auth/apiKey.js b/src/api/routes/auth/apiKey.js deleted file mode 100644 index 84df2e3..0000000 --- a/src/api/routes/auth/apiKey.js +++ /dev/null @@ -1,23 +0,0 @@ -const Route = require('../../structures/Route'); - -class apiKeyGET extends Route { - constructor() { - super('/auth/apiKey', 'get'); - } - - run(req, res, user) { - return res.json({ message: 'Hai hai api works.' }); - } -} - -class apiKeyPOST extends Route { - constructor() { - super('/auth/apiKey', 'post'); - } - - run(req, res, user) { - return res.json({ message: 'Hai hai api works.' }); - } -} - -module.exports = [apiKeyGET, apiKeyPOST]; diff --git a/src/api/routes/user/apiKey.js b/src/api/routes/user/apiKey.js new file mode 100644 index 0000000..820e28c --- /dev/null +++ b/src/api/routes/user/apiKey.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); +const randomstring = require('randomstring'); +const moment = require('moment'); + +class apiKeyPOST extends Route { + constructor() { + super('/user/apikey/change', 'post'); + } + + async run(req, res, db, user) { + const now = moment.utc().toDate(); + const apiKey = randomstring.generate(64); + await db.table('users') + .where({ id: user.id }) + .update({ + apiKey, + apiKeyEditedAt: now + }); + + return res.json({ + message: 'Successfully created new api key', + apiKey + }); + } +} + +module.exports = apiKeyPOST; diff --git a/src/api/routes/user/changePasswordPOST.js b/src/api/routes/user/changePasswordPOST.js new file mode 100644 index 0000000..d73cff3 --- /dev/null +++ b/src/api/routes/user/changePasswordPOST.js @@ -0,0 +1,40 @@ +const Route = require('../../structures/Route'); +const log = require('../../utils/Log'); +const bcrypt = require('bcrypt'); +const moment = require('moment'); + +class changePasswordPOST extends Route { + constructor() { + super('/user/password/change', 'post'); + } + + async run(req, res, db, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { password, newPassword } = req.body; + if (!password || !newPassword) return res.status(401).json({ message: 'Invalid body provided' }); + if (password === newPassword) return res.status(400).json({ message: 'Passwords have to be different' }); + + if (newPassword.length < 6 || newPassword.length > 64) { + return res.status(400).json({ message: 'Password must have 6-64 characters' }); + } + + let hash; + try { + hash = await bcrypt.hash(newPassword, 10); + } catch (error) { + log.error('Error generating password hash'); + log.error(error); + return res.status(401).json({ message: 'There was a problem processing your account' }); + } + + const now = moment.utc().toDate(); + await db.table('users').where('id', user.id).update({ + password: hash, + passwordEditedAt: now + }); + + return res.json({ message: 'The password was changed successfully' }); + } +} + +module.exports = changePasswordPOST; diff --git a/src/api/routes/user/userGET.js b/src/api/routes/user/userGET.js new file mode 100644 index 0000000..7929aac --- /dev/null +++ b/src/api/routes/user/userGET.js @@ -0,0 +1,21 @@ +const Route = require('../../structures/Route'); + +class usersGET extends Route { + constructor() { + super('/users/me', 'get'); + } + + run(req, res, db, user) { + return res.json({ + message: 'Successfully retrieved user', + user: { + id: user.id, + username: user.username, + isAdmin: user.isAdmin, + apiKey: user.apiKey + } + }); + } +} + +module.exports = usersGET; -- cgit v1.2.3 From c169ab6dc1727c7ca5dd45fcaeb419b44cbf1908 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 28 Feb 2019 23:26:44 +0900 Subject: Some stuff --- src/api/routes/auth/changePasswordPOST.js | 39 ------------------------------- src/api/routes/auth/loginPOST.js | 7 +++++- src/api/routes/service/configGET.js | 28 ++++++++++++++++++++++ src/api/routes/service/restartPOST.js | 14 +++++++++++ 4 files changed, 48 insertions(+), 40 deletions(-) delete mode 100644 src/api/routes/auth/changePasswordPOST.js create mode 100644 src/api/routes/service/configGET.js create mode 100644 src/api/routes/service/restartPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/auth/changePasswordPOST.js b/src/api/routes/auth/changePasswordPOST.js deleted file mode 100644 index d698896..0000000 --- a/src/api/routes/auth/changePasswordPOST.js +++ /dev/null @@ -1,39 +0,0 @@ -const Route = require('../../structures/Route'); -const log = require('../../utils/Log'); -const bcrypt = require('bcrypt'); -const moment = require('moment'); - -class changePasswordPOST extends Route { - constructor() { - super('/auth/password/change', 'post'); - } - - async run(req, res, db, user) { - if (!req.body) return res.status(400).json({ message: 'No body provided' }); - const { password, newPassword } = req.body; - if (!password || !newPassword) return res.status(401).json({ message: 'Invalid body provided' }); - - if (newPassword.length < 6 || newPassword.length > 64) { - return res.status(400).json({ message: 'Password must have 6-64 characters' }); - } - - let hash; - try { - hash = await bcrypt.hash(newPassword, 10); - } catch (error) { - log.error('Error generating password hash'); - log.error(error); - return res.status(401).json({ message: 'There was a problem processing your account' }); - } - - const now = moment.utc().toDate(); - await db.table('users').where('id', user.id).update({ - password: hash, - passwordEditedAt: now - }); - - return res.json({ message: 'The password was changed successfully' }); - } -} - -module.exports = changePasswordPOST; diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index 760e54b..38bbc49 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -36,7 +36,12 @@ class loginPOST extends Route { return res.json({ message: 'Successfully logged in.', - user: { username: user.username }, + user: { + id: user.id, + username: user.username, + apiKey: user.apiKey, + isAdmin: user.isAdmin + }, token: jwt, apiKey: user.apiKey }); diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js new file mode 100644 index 0000000..230b594 --- /dev/null +++ b/src/api/routes/service/configGET.js @@ -0,0 +1,28 @@ +const Route = require('../../structures/Route'); + +class configGET extends Route { + constructor() { + super('/service/config', 'get', { adminOnly: true }); + } + + run(req, res) { + return res.json({ + message: 'Successfully retrieved config', + config: { + serviceName: process.env.SERVICE_NAME, + uploadFolder: process.env.UPLOAD_FOLDER, + linksPerAlbum: process.env.MAX_LINKS_PER_ALBUM, + maxUploadSize: process.env.MAX_SIZE, + filenameLength: process.env.GENERATED_FILENAME_LENGTH, + albumLinkLength: process.env.GENERATED_ALBUM_LENGTH, + generateThumbnails: process.env.GENERATE_THUMBNAILS, + generateZips: process.env.GENERATE_ZIPS, + stripExif: process.env.STRIP_EXIF, + publicMode: process.env.PUBLIC_MODE, + enableAccounts: process.env.USER_ACCOUNTS + } + }); + } +} + +module.exports = configGET; diff --git a/src/api/routes/service/restartPOST.js b/src/api/routes/service/restartPOST.js new file mode 100644 index 0000000..530cc91 --- /dev/null +++ b/src/api/routes/service/restartPOST.js @@ -0,0 +1,14 @@ +const Route = require('../../structures/Route'); + +class restartPOST extends Route { + constructor() { + super('/service/restart', 'post', { adminOnly: true }); + } + + run(req, res) { + res.json({ message: 'Restarting...' }); + process.exit(0); + } +} + +module.exports = restartPOST; -- cgit v1.2.3 From 9f5a3d15f55fea03052627f3bd4d97a4284cdf7c Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 28 Feb 2019 23:51:59 +0900 Subject: Purge user's files --- src/api/routes/admin/userPurge.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/api/routes/admin/userPurge.js (limited to 'src/api/routes') diff --git a/src/api/routes/admin/userPurge.js b/src/api/routes/admin/userPurge.js new file mode 100644 index 0000000..90f6ec9 --- /dev/null +++ b/src/api/routes/admin/userPurge.js @@ -0,0 +1,26 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class userDemote extends Route { + constructor() { + super('/admin/users/purge', 'post', { adminOnly: true }); + } + + async run(req, res) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { id } = req.body; + if (!id) return res.status(400).json({ message: 'No id provided' }); + + try { + await Util.deleteAllFilesFromUser(id); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully deleted the user\'s files' + }); + } +} + +module.exports = userDemote; -- cgit v1.2.3 From 9cba85c47cfde1decbee513e48f82deff27a438d Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 28 Feb 2019 23:52:04 +0900 Subject: changes --- src/api/routes/auth/loginPOST.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/api/routes') diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index 38bbc49..205737a 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -19,6 +19,11 @@ class loginPOST extends Route { const user = await db.table('users').where('username', username).first(); if (!user) return res.status(401).json({ message: 'Invalid authorization' }); + /* + Checks if the user is disabled + */ + if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); + /* Checks if the password is right */ -- cgit v1.2.3 From 5a701536cf8433aff24185da1cd40fe4ce71c24f Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 1 Mar 2019 01:14:34 +0900 Subject: todo --- src/api/routes/files/uploadPOST.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index b003311..920981b 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -6,7 +6,7 @@ const log = require('../../utils/Log'); const jetpack = require('fs-jetpack'); const Busboy = require('busboy'); const fs = require('fs'); - +const { dump } = require('dumper.js'); /* TODO: Strip exif data if the owner/user configured it as such TODO: If source has transparency generate a png thumbnail, otherwise a jpg. @@ -22,6 +22,13 @@ class uploadPOST extends Route { async run(req, res, db) { const user = await Util.isAuthorized(req); + // TODO: .env variables are all casted to strings. pepehands + // https://github.com/niftylettuce/dotenv-parse-variables + dump(user); + dump(process.env.PUBLIC_MODE); + console.log('user', user); + console.log('public_mode', process.env.PUBLIC_MODE); + if (!user && !process.env.PUBLIC_MODE) return res.status(401).json({ message: 'Not authorized to use this resource' }); return this.uploadFile(req, res, db, user); } -- cgit v1.2.3 From 47ca404b6bc45b0626b19c076da3fea5412404a7 Mon Sep 17 00:00:00 2001 From: Kana Date: Fri, 1 Mar 2019 12:50:48 +0900 Subject: Update uploadPOST.js --- src/api/routes/files/uploadPOST.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 920981b..33c4551 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -23,13 +23,7 @@ class uploadPOST extends Route { async run(req, res, db) { const user = await Util.isAuthorized(req); // TODO: .env variables are all casted to strings. pepehands - // https://github.com/niftylettuce/dotenv-parse-variables - dump(user); - dump(process.env.PUBLIC_MODE); - console.log('user', user); - console.log('public_mode', process.env.PUBLIC_MODE); - - if (!user && !process.env.PUBLIC_MODE) return res.status(401).json({ message: 'Not authorized to use this resource' }); + if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); return this.uploadFile(req, res, db, user); } -- cgit v1.2.3 From 04ee40e6734d5272c723c5614c0b9b6f1fb53304 Mon Sep 17 00:00:00 2001 From: Kana Date: Fri, 1 Mar 2019 13:53:53 +0900 Subject: Update uploadPOST.js --- src/api/routes/files/uploadPOST.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 33c4551..82e9d09 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -13,6 +13,7 @@ const { dump } = require('dumper.js'); TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover. TODO: If source is a video, generate a thumb of the first frame and save the video length. TODO: Check that the async isAuthorized works and is not nulling out + TODO: Lowercase the file extensions */ class uploadPOST extends Route { -- cgit v1.2.3 From 73d85e8c7938e1db30da3cc4354b143d4a078473 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 2 Mar 2019 02:08:11 +0900 Subject: Enviroment variables parsing fix --- src/api/routes/albums/link/linkPOST.js | 2 +- src/api/routes/auth/registerPOST.js | 2 +- src/api/routes/files/uploadPOST.js | 4 ++-- src/api/routes/service/configGET.js | 18 +++++++++--------- 4 files changed, 13 insertions(+), 13 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 968e57d..e929c89 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -22,7 +22,7 @@ class linkPOST extends Route { Count the amount of links created for that album already and error out if max was reached */ const count = await db.table('links').where('albumId', albumId).count({ count: 'id' }); - if (count[0].count >= process.env.MAX_LINKS_PER_ALBUM) return res.status(400).json({ message: 'Maximum links per album reached' }); + if (count[0].count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10)) return res.status(400).json({ message: 'Maximum links per album reached' }); /* Try to allocate a new identifier on the db diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index ee8e5ae..0bd8cfd 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -10,7 +10,7 @@ class registerPOST extends Route { } async run(req, res, db) { - if (!process.env.USER_ACCOUNTS) return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); + if (process.env.USER_ACCOUNTS == 'false') return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { username, password } = req.body; if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' }); diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 82e9d09..e88091a 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -181,7 +181,7 @@ class uploadPOST extends Route { /* If exif removal has been force service-wide or requested by the user, remove it */ - if (process.env.STRIP_EXIF) { // || user.settings.stripExif) { + if (process.env.STRIP_EXIF == 'true') { // || user.settings.stripExif) { // Util.removeExif(upload.filename); } @@ -195,7 +195,7 @@ class uploadPOST extends Route { const busboy = new Busboy({ headers: req.headers, limits: { - fileSize: process.env.MAX_SIZE * (1000 * 1000), + fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000), files: 1 } }); diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js index 230b594..e12c57b 100644 --- a/src/api/routes/service/configGET.js +++ b/src/api/routes/service/configGET.js @@ -11,15 +11,15 @@ class configGET extends Route { config: { serviceName: process.env.SERVICE_NAME, uploadFolder: process.env.UPLOAD_FOLDER, - linksPerAlbum: process.env.MAX_LINKS_PER_ALBUM, - maxUploadSize: process.env.MAX_SIZE, - filenameLength: process.env.GENERATED_FILENAME_LENGTH, - albumLinkLength: process.env.GENERATED_ALBUM_LENGTH, - generateThumbnails: process.env.GENERATE_THUMBNAILS, - generateZips: process.env.GENERATE_ZIPS, - stripExif: process.env.STRIP_EXIF, - publicMode: process.env.PUBLIC_MODE, - enableAccounts: process.env.USER_ACCOUNTS + linksPerAlbum: parseInt(process.env.MAX_LINKS_PER_ALBUM, 10), + maxUploadSize: parseInt(process.env.MAX_SIZE, 10), + filenameLength: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10), + albumLinkLength: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), + generateThumbnails: process.env.GENERATE_THUMBNAILS == 'true' ? true : false, + generateZips: process.env.GENERATE_ZIPS == 'true' ? true : false, + stripExif: process.env.STRIP_EXIF == 'true' ? true : false, + publicMode: process.env.PUBLIC_MODE == 'true' ? true : false, + enableAccounts: process.env.USER_ACCOUNTS == 'true' ? true : false } }); } -- cgit v1.2.3 From ce76a8ec7ba2084ab75fb901933b3d4d84505032 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 2 Mar 2019 22:07:24 +0900 Subject: Be able to add a file to multiple albums --- src/api/routes/files/albumAddPOST.js | 26 ++++++++++++++++++++++++++ src/api/routes/files/albumDelPOST.js | 27 +++++++++++++++++++++++++++ src/api/routes/files/filesGET.js | 15 +++++++++++++++ src/api/routes/files/uploadPOST.js | 4 ---- 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 src/api/routes/files/albumAddPOST.js create mode 100644 src/api/routes/files/albumDelPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/files/albumAddPOST.js b/src/api/routes/files/albumAddPOST.js new file mode 100644 index 0000000..fc4ee71 --- /dev/null +++ b/src/api/routes/files/albumAddPOST.js @@ -0,0 +1,26 @@ +const Route = require('../../structures/Route'); + +class albumAddPOST extends Route { + constructor() { + super('/file/album/add', 'post'); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { fileId, albumId } = req.body; + if (!fileId || !albumId) return res.status(400).json({ message: 'No id provided' }); + + try { + await db.table('albumsFiles') + .insert({ fileId, albumId }); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully added file to album' + }); + } +} + +module.exports = albumAddPOST; diff --git a/src/api/routes/files/albumDelPOST.js b/src/api/routes/files/albumDelPOST.js new file mode 100644 index 0000000..fd6bbd0 --- /dev/null +++ b/src/api/routes/files/albumDelPOST.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); + +class albumDelPOST extends Route { + constructor() { + super('/file/album/del', 'post'); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { fileId, albumId } = req.body; + if (!fileId || !albumId) return res.status(400).json({ message: 'No id provided' }); + + try { + await db.table('albumsFiles') + .where({ fileId, albumId }) + .delete(); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully removed file from album' + }); + } +} + +module.exports = albumDelPOST; diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index b41996b..ce288ff 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -14,6 +14,21 @@ class filesGET extends Route { .where('userId', user.id) .orderBy('id', 'desc'); + for (const file of files) { + file.albums = []; + const albumFiles = await db.table('albumsFiles') + .where('fileId', file.id); + if (!albumFiles.length) continue; + + for (const albumFile of albumFiles) { + const album = await db.table('albums') + .where('id', albumFile.albumId) + .select('id', 'name') + .first(); + if (!album) continue; + file.albums.push(album); + } + } /* For each file, create the public link to be able to display the file */ diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index e88091a..ef95b83 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -6,14 +6,11 @@ const log = require('../../utils/Log'); const jetpack = require('fs-jetpack'); const Busboy = require('busboy'); const fs = require('fs'); -const { dump } = require('dumper.js'); /* TODO: Strip exif data if the owner/user configured it as such TODO: If source has transparency generate a png thumbnail, otherwise a jpg. TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover. TODO: If source is a video, generate a thumb of the first frame and save the video length. - TODO: Check that the async isAuthorized works and is not nulling out - TODO: Lowercase the file extensions */ class uploadPOST extends Route { @@ -23,7 +20,6 @@ class uploadPOST extends Route { async run(req, res, db) { const user = await Util.isAuthorized(req); - // TODO: .env variables are all casted to strings. pepehands if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); return this.uploadFile(req, res, db, user); } -- cgit v1.2.3 From 789f5fc259b90dd6a3b21fd2aef1a9e54a19506e Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 2 Mar 2019 22:16:35 +0900 Subject: Removed google analytics --- src/api/routes/albums/link/linkEditPOST.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index 753c496..bb3c41b 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -25,7 +25,7 @@ class linkEditPOST extends Route { enableDownload: enableDownload || false, expiresAt // This one should be null if not supplied }); - return res.json({ message: 'Editing the link was successfully' }); + return res.json({ message: 'Editing the link was successfull' }); } catch (error) { log.error(error); return res.json({ message: 'There was a problem editing the link' }); -- cgit v1.2.3 From 99bc74875edb44b4e679b17158511474cd575e10 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 2 Mar 2019 22:36:16 +0900 Subject: Various password fixes --- src/api/routes/user/changePasswordPOST.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/api/routes') diff --git a/src/api/routes/user/changePasswordPOST.js b/src/api/routes/user/changePasswordPOST.js index d73cff3..9cd621e 100644 --- a/src/api/routes/user/changePasswordPOST.js +++ b/src/api/routes/user/changePasswordPOST.js @@ -14,6 +14,12 @@ class changePasswordPOST extends Route { if (!password || !newPassword) return res.status(401).json({ message: 'Invalid body provided' }); if (password === newPassword) return res.status(400).json({ message: 'Passwords have to be different' }); + /* + Checks if the password is right + */ + const comparePassword = await bcrypt.compare(password, user.password); + if (!comparePassword) return res.status(401).json({ message: 'Current password is incorrect' }); + if (newPassword.length < 6 || newPassword.length > 64) { return res.status(400).json({ message: 'Password must have 6-64 characters' }); } -- cgit v1.2.3 From 3ce7657871bfe392d73ab67a6c1a8a10543e3d98 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 2 Mar 2019 22:36:28 +0900 Subject: wip --- src/api/routes/albums/albumGET.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index 59398a1..fae7dd1 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -14,7 +14,7 @@ class albumGET extends Route { Make sure it exists and it's enabled */ const link = await db.table('links').where({ identifier, enabled: true }).first(); - if (!link) return res.status(404).json({ message: 'The identifier supplied could not be found' }); + if (!link) return res.status(404).json({ message: 'The album could not be found' }); /* Same with the album, just to make sure is not a deleted album and a leftover link -- cgit v1.2.3 From 71f24504317a8391209789275549a94be5c99e4e Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 8 Mar 2019 00:47:30 +0900 Subject: WIP --- src/api/routes/albums/link/linkDELETE.js | 36 ++++++++++++++++++++++++++++++ src/api/routes/albums/link/linkEditPOST.js | 3 +-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/api/routes/albums/link/linkDELETE.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js new file mode 100644 index 0000000..4f948ba --- /dev/null +++ b/src/api/routes/albums/link/linkDELETE.js @@ -0,0 +1,36 @@ +const Route = require('../../../structures/Route'); + +class linkDELETE extends Route { + constructor() { + super('/album/link/delete/:identifier', 'delete'); + } + + async run(req, res, db) { + const { identifier } = req.params; + if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); + + try { + const link = await db.table('links') + .where({ identifier }) + .first(); + + if (!link) return res.status(400).json({ message: 'Identifier doesn\'t exist' }); + + await db.table('links') + .where({ id: link.id }) + .delete(); + await db.table('albumsLinks') + .where({ linkId: link.id }) + .delete(); + } catch (error) { + console.log(error); + return super.error(res, error); + } + + return res.json({ + message: 'Successfully deleted link' + }); + } +} + +module.exports = linkDELETE; diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index bb3c41b..1db0a53 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -8,7 +8,7 @@ class linkEditPOST extends Route { async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); - const { identifier, enabled, enableDownload, expiresAt } = req.body; + const { identifier, enableDownload, expiresAt } = req.body; if (!identifier) return res.status(400).json({ message: 'Invalid album identifier supplied' }); /* @@ -21,7 +21,6 @@ class linkEditPOST extends Route { await db.table('links') .where({ identifier }) .update({ - enabled: enabled || false, enableDownload: enableDownload || false, expiresAt // This one should be null if not supplied }); -- cgit v1.2.3 From 85ac74483764de66d2be0f6ea1ff84626e32ffff Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 12 Mar 2019 05:31:55 +0000 Subject: Typo --- src/api/routes/albums/link/linkEditPOST.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index 1db0a53..0586f08 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -24,7 +24,7 @@ class linkEditPOST extends Route { enableDownload: enableDownload || false, expiresAt // This one should be null if not supplied }); - return res.json({ message: 'Editing the link was successfull' }); + return res.json({ message: 'Editing the link was successful' }); } catch (error) { log.error(error); return res.json({ message: 'There was a problem editing the link' }); -- cgit v1.2.3 From dd98cecfebff9f98383d604b9a20fa8a8f32380a Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 12 Mar 2019 05:38:13 +0000 Subject: Error consistency --- src/api/routes/albums/albumDELETE.js | 3 +-- src/api/routes/albums/link/linkDELETE.js | 1 - src/api/routes/albums/link/linkEditPOST.js | 3 +-- src/api/routes/albums/link/linkPOST.js | 3 +-- 4 files changed, 3 insertions(+), 7 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index 3fdf209..23dcf40 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -30,8 +30,7 @@ class albumDELETE extends Route { await db.table('albums').where({ id }).delete(); return res.json({ message: 'The album was deleted successfully' }); } catch (error) { - log.error(error); - return res.json({ message: 'There was a problem deleting the album' }); + return super.error(res, error); } } } diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js index 4f948ba..d6d98c4 100644 --- a/src/api/routes/albums/link/linkDELETE.js +++ b/src/api/routes/albums/link/linkDELETE.js @@ -23,7 +23,6 @@ class linkDELETE extends Route { .where({ linkId: link.id }) .delete(); } catch (error) { - console.log(error); return super.error(res, error); } diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index 0586f08..6776b73 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -26,8 +26,7 @@ class linkEditPOST extends Route { }); return res.json({ message: 'Editing the link was successful' }); } catch (error) { - log.error(error); - return res.json({ message: 'There was a problem editing the link' }); + return super.error(res, error); } } } diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index e929c89..297348c 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -46,8 +46,7 @@ class linkPOST extends Route { identifier }); } catch (error) { - log.error(error); - return res.status(500).json({ message: 'There was a problem creating the link' }); + return super.error(res, error); } } } -- cgit v1.2.3 From 197e69f2f2194df4ad23bb913c9efd39e1501b96 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 12 Mar 2019 05:48:01 +0000 Subject: Prevent snowflakes from demoting/disabling themselves --- src/api/routes/admin/userDemote.js | 3 ++- src/api/routes/admin/userDisable.js | 1 + src/api/routes/admin/userEnable.js | 1 + src/api/routes/admin/userPromote.js | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/admin/userDemote.js b/src/api/routes/admin/userDemote.js index fa288fc..b430a48 100644 --- a/src/api/routes/admin/userDemote.js +++ b/src/api/routes/admin/userDemote.js @@ -5,10 +5,11 @@ class userDemote extends Route { super('/admin/users/demote', 'post', { adminOnly: true }); } - async run(req, res, db) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; if (!id) return res.status(400).json({ message: 'No id provided' }); + if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' }); try { await db.table('users') diff --git a/src/api/routes/admin/userDisable.js b/src/api/routes/admin/userDisable.js index c7dffa8..65bcf4e 100644 --- a/src/api/routes/admin/userDisable.js +++ b/src/api/routes/admin/userDisable.js @@ -9,6 +9,7 @@ class userDisable extends Route { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; if (!id) return res.status(400).json({ message: 'No id provided' }); + if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' }); try { await db.table('users') diff --git a/src/api/routes/admin/userEnable.js b/src/api/routes/admin/userEnable.js index 7e5743d..bdba7a6 100644 --- a/src/api/routes/admin/userEnable.js +++ b/src/api/routes/admin/userEnable.js @@ -9,6 +9,7 @@ class userEnable extends Route { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; if (!id) return res.status(400).json({ message: 'No id provided' }); + if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' }); try { await db.table('users') diff --git a/src/api/routes/admin/userPromote.js b/src/api/routes/admin/userPromote.js index 4062dfa..6534d16 100644 --- a/src/api/routes/admin/userPromote.js +++ b/src/api/routes/admin/userPromote.js @@ -9,6 +9,7 @@ class userPromote extends Route { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; if (!id) return res.status(400).json({ message: 'No id provided' }); + if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' }); try { await db.table('users') -- cgit v1.2.3 From 00058e9915a09f5abcd8d130a144f5c68d10428a Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 12 Mar 2019 06:03:15 +0000 Subject: stuff --- src/api/routes/albums/albumDELETE.js | 2 +- src/api/routes/albums/albumZipGET.js | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index 23dcf40..adaa02f 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -12,7 +12,7 @@ class albumDELETE extends Route { if (!id) return res.status(400).json({ message: 'Invalid album ID supplied' }); /* - Check fi the album exists + Check if the album exists */ const album = await db.table('albums').where({ id, userId: user.id }).first(); if (!album) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index 9419654..b2c9fa7 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -16,13 +16,17 @@ class albumGET extends Route { /* Make sure it exists and it's enabled */ - const link = await db.table('links').where({ identifier, enabled: true }).first(); + const link = await db.table('links') + .where({ identifier, enabled: true }) + .first(); if (!link) return res.status(400).json({ message: 'The identifier supplied could not be found' }); /* Same with the album, just to make sure is not a deleted album and a leftover link */ - const album = await db.table('albums').where('id', link.albumId).first(); + const album = await db.table('albums') + .where('id', link.albumId) + .first(); if (!album) return res.status(400).json({ message: 'Album not found' }); /* @@ -43,12 +47,14 @@ class albumGET extends Route { /* Grab the files in a very unoptimized way. (This should be a join between both tables) */ - const fileList = await db.table('albumsFiles').where('albumId', link.albumId).select('fileId'); + const fileList = await db.table('albumsFiles') + .where('albumId', link.albumId) + .select('fileId'); /* If there are no files, stop here */ - if (!fileList) return res.status(400).json({ message: 'Can\'t download an empty album' }); + if (!fileList || !fileList.length) return res.status(400).json({ message: 'Can\'t download an empty album' }); /* Get the actual files @@ -61,7 +67,9 @@ class albumGET extends Route { try { Util.createZip(filesToZip, album); - await db.table('albums').where('id', link.albumId).update('zippedAt', db.fn.now()); + await db.table('albums') + .where('id', link.albumId) + .update('zippedAt', db.fn.now()); const filePath = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); const fileName = `lolisafe-${identifier}.zip`; -- cgit v1.2.3 From 4ab3796fbde97862a86588239e440058f21a4e3e Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 12 Mar 2019 06:19:57 +0000 Subject: Add TODO --- src/api/routes/files/uploadPOST.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index ef95b83..4e6ac55 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -11,6 +11,8 @@ const fs = require('fs'); TODO: If source has transparency generate a png thumbnail, otherwise a jpg. TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover. TODO: If source is a video, generate a thumb of the first frame and save the video length. + + TODO: Think if its worth making a folder with the user uuid in uploads/ and upload the pictures there so that this way at least not every single file will be in 1 directory */ class uploadPOST extends Route { -- cgit v1.2.3 From 771a5c2bf7480b6e023d8eeeded1c6f698b779e8 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 12 Mar 2019 06:32:10 +0000 Subject: Not tested, but should delete an album --- src/api/routes/albums/albumDELETE.js | 1 - 1 file changed, 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index adaa02f..b43d046 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -1,6 +1,5 @@ const Route = require('../../structures/Route'); const Util = require('../../utils/Util'); -const log = require('../../utils/Log'); class albumDELETE extends Route { constructor() { -- cgit v1.2.3 From f11cb56db87c297d72c18bf967ebdacd9959ef64 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 12 Mar 2019 07:02:26 +0000 Subject: Clicking on album title takes you to the album now --- src/api/routes/albums/albumFullGET.js | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/api/routes/albums/albumFullGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js new file mode 100644 index 0000000..629f57a --- /dev/null +++ b/src/api/routes/albums/albumFullGET.js @@ -0,0 +1,55 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class albumGET extends Route { + constructor() { + super('/album/:id/full', 'get'); + } + + async run(req, res, db) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid id supplied' }); + + const album = await db.table('albums').where({ id, userId: user.id }).first(); + if (!album) return res.status(404).json({ message: 'Album not found' }); + + /* + Grab the files in a very unoptimized way. (This should be a join between both tables) + */ + const fileList = await db.table('albumsFiles').where('albumId', id).select('fileId'); + const fileIds = fileList.map(el => el.fileId); + const files = await db.table('files') + .whereIn('id', fileIds) + .orderBy('id', 'desc'); + + for (const file of files) { + file.albums = []; + const albumFiles = await db.table('albumsFiles') + .where('fileId', file.id); + if (!albumFiles.length) continue; + + for (const albumFile of albumFiles) { + const album = await db.table('albums') + .where('id', albumFile.albumId) + .select('id', 'name') + .first(); + if (!album) continue; + file.albums.push(album); + } + } + /* + For each file, create the public link to be able to display the file + */ + for (let file of files) { + file = Util.constructFilePublicLink(file); + } + + return res.json({ + message: 'Successfully retrieved album', + name: album.name, + files + }); + } +} + +module.exports = albumGET; -- cgit v1.2.3 From 79eb00f71cc18dbb195a29bd79871d35176f33d1 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 14 Mar 2019 23:14:24 +0900 Subject: Small fixes --- src/api/routes/admin/userDisable.js | 2 +- src/api/routes/admin/userEnable.js | 2 +- src/api/routes/admin/userPromote.js | 2 +- src/api/routes/albums/albumFullGET.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/admin/userDisable.js b/src/api/routes/admin/userDisable.js index 65bcf4e..e39c811 100644 --- a/src/api/routes/admin/userDisable.js +++ b/src/api/routes/admin/userDisable.js @@ -5,7 +5,7 @@ class userDisable extends Route { super('/admin/users/disable', 'post', { adminOnly: true }); } - async run(req, res, db) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; if (!id) return res.status(400).json({ message: 'No id provided' }); diff --git a/src/api/routes/admin/userEnable.js b/src/api/routes/admin/userEnable.js index bdba7a6..cff622f 100644 --- a/src/api/routes/admin/userEnable.js +++ b/src/api/routes/admin/userEnable.js @@ -5,7 +5,7 @@ class userEnable extends Route { super('/admin/users/enable', 'post', { adminOnly: true }); } - async run(req, res, db) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; if (!id) return res.status(400).json({ message: 'No id provided' }); diff --git a/src/api/routes/admin/userPromote.js b/src/api/routes/admin/userPromote.js index 6534d16..4a5ed88 100644 --- a/src/api/routes/admin/userPromote.js +++ b/src/api/routes/admin/userPromote.js @@ -5,7 +5,7 @@ class userPromote extends Route { super('/admin/users/promote', 'post', { adminOnly: true }); } - async run(req, res, db) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { id } = req.body; if (!id) return res.status(400).json({ message: 'No id provided' }); diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js index 629f57a..f92f9ae 100644 --- a/src/api/routes/albums/albumFullGET.js +++ b/src/api/routes/albums/albumFullGET.js @@ -6,7 +6,7 @@ class albumGET extends Route { super('/album/:id/full', 'get'); } - async run(req, res, db) { + async run(req, res, db, user) { const { id } = req.params; if (!id) return res.status(400).json({ message: 'Invalid id supplied' }); -- cgit v1.2.3 From 497a961a3844afccc763ebdfa2d77f107318394a Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 14 Mar 2019 23:14:37 +0900 Subject: Tags --- src/api/routes/tags/tagDELETE.js | 37 +++++++++++++++++++++++++++++++++++++ src/api/routes/tags/tagPOST.js | 34 ++++++++++++++++++++++++++++++++++ src/api/routes/tags/tagsGET.js | 31 +++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 src/api/routes/tags/tagDELETE.js create mode 100644 src/api/routes/tags/tagPOST.js create mode 100644 src/api/routes/tags/tagsGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/tags/tagDELETE.js b/src/api/routes/tags/tagDELETE.js new file mode 100644 index 0000000..c03ca64 --- /dev/null +++ b/src/api/routes/tags/tagDELETE.js @@ -0,0 +1,37 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class tagDELETE extends Route { + constructor() { + super('/tag/:id/:purge*?', 'delete'); + } + + async run(req, res, db, user) { + const { id, purge } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid tag supplied' }); + + /* + Check if the tag exists + */ + const tag = await db.table('tags').where({ id, userId: user.id }).first(); + if (!tag) return res.status(400).json({ message: 'The tag doesn\'t exist or doesn\'t belong to the user' }); + + try { + /* + Should we also delete every file of that tag? + */ + if (purge) { + await Util.deleteAllFilesFromTag(id); + } + /* + Delete the tag + */ + await db.table('tags').where({ id }).delete(); + return res.json({ message: 'The tag was deleted successfully' }); + } catch (error) { + return super.error(res, error); + } + } +} + +module.exports = tagDELETE; diff --git a/src/api/routes/tags/tagPOST.js b/src/api/routes/tags/tagPOST.js new file mode 100644 index 0000000..0df36e1 --- /dev/null +++ b/src/api/routes/tags/tagPOST.js @@ -0,0 +1,34 @@ +const Route = require('../../structures/Route'); +const moment = require('moment'); +const util = require('../../utils/Util'); + +class tagPOST extends Route { + constructor() { + super('/tag/new', 'post'); + } + + async run(req, res, db, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { name } = req.body; + if (!name) return res.status(400).json({ message: 'No name provided' }); + + /* + Check that a tag with that name doesn't exist yet + */ + const tag = await db.table('tags').where({ name, userId: user.id }).first(); + if (tag) return res.status(401).json({ message: 'There\'s already a tag with that name' }); + + const now = moment.utc().toDate(); + await db.table('tags').insert({ + name, + uuid: util.uuid(), + userId: user.id, + createdAt: now, + editedAt: now + }); + + return res.json({ message: 'The album was created successfully' }); + } +} + +module.exports = tagPOST; diff --git a/src/api/routes/tags/tagsGET.js b/src/api/routes/tags/tagsGET.js new file mode 100644 index 0000000..871148e --- /dev/null +++ b/src/api/routes/tags/tagsGET.js @@ -0,0 +1,31 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class tagsGET extends Route { + constructor() { + super('/tags', 'get'); + } + + async run(req, res, db, user) { + try { + const tags = await db.table('tags') + .where('userId', user.id); + + for (const tag of tags) { + const files = await db.table('fileTags') + .where({ tagId: tag.id }); + + tag.count = files.length ? files.length : 0; + } + + return res.json({ + message: 'Successfully retrieved tags', + tags + }); + } catch (error) { + return super.error(res, error); + } + } +} + +module.exports = tagsGET; -- cgit v1.2.3 From f06c8c9d336cbee561d9a80abc78568c28463e52 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 14 Mar 2019 23:14:45 +0900 Subject: dunno what's wrong here yet --- src/api/routes/albums/link/linkDELETE.js | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js index d6d98c4..3ec4d9d 100644 --- a/src/api/routes/albums/link/linkDELETE.js +++ b/src/api/routes/albums/link/linkDELETE.js @@ -1,4 +1,5 @@ const Route = require('../../../structures/Route'); +const { dump } = require('dumper.js'); class linkDELETE extends Route { constructor() { @@ -6,6 +7,10 @@ class linkDELETE extends Route { } async run(req, res, db) { + console.log('------------------------------'); + console.log('YES HI'); + console.log('------------------------------'); + console.log('WHO NEEDS FANCY DEBUGGING TOOLS ANYWAYS'); const { identifier } = req.params; if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); @@ -14,6 +19,8 @@ class linkDELETE extends Route { .where({ identifier }) .first(); + dump(link); + if (!link) return res.status(400).json({ message: 'Identifier doesn\'t exist' }); await db.table('links') -- cgit v1.2.3 From 107d1f4750e8f82a628b528c4ec200e918be271d Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 19 Mar 2019 07:58:36 +0000 Subject: API key WIP --- src/api/routes/auth/registerPOST.js | 3 --- src/api/routes/user/apiKey.js | 29 ++++++++++++++++++++--------- src/api/routes/user/userGET.js | 3 +-- src/api/routes/verifyGET.js | 1 - 4 files changed, 21 insertions(+), 15 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index 0bd8cfd..feeb360 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -1,7 +1,6 @@ const Route = require('../../structures/Route'); const log = require('../../utils/Log'); const bcrypt = require('bcrypt'); -const randomstring = require('randomstring'); const moment = require('moment'); class registerPOST extends Route { @@ -48,8 +47,6 @@ class registerPOST extends Route { username, password: hash, passwordEditedAt: now, - apiKey: randomstring.generate(64), - apiKeyEditedAt: now, createdAt: now, editedAt: now, enabled: true, diff --git a/src/api/routes/user/apiKey.js b/src/api/routes/user/apiKey.js index 820e28c..7de6cb8 100644 --- a/src/api/routes/user/apiKey.js +++ b/src/api/routes/user/apiKey.js @@ -1,6 +1,7 @@ const Route = require('../../structures/Route'); const randomstring = require('randomstring'); const moment = require('moment'); +const bcrypt = require('bcrypt'); class apiKeyPOST extends Route { constructor() { @@ -10,17 +11,27 @@ class apiKeyPOST extends Route { async run(req, res, db, user) { const now = moment.utc().toDate(); const apiKey = randomstring.generate(64); - await db.table('users') - .where({ id: user.id }) - .update({ - apiKey, - apiKeyEditedAt: now + + try { + const hash = await bcrypt.hash(apiKey, 10); + + await db.table('users') + .where({ id: user.id }) + .update({ + apiKey: hash, + apiKeyEditedAt: now + }); + + return res.json({ + message: 'Successfully created new api key', + apiKey }); - return res.json({ - message: 'Successfully created new api key', - apiKey - }); + } catch (error) { + return super.error(res, error); + } + + } } diff --git a/src/api/routes/user/userGET.js b/src/api/routes/user/userGET.js index 7929aac..fe46fd4 100644 --- a/src/api/routes/user/userGET.js +++ b/src/api/routes/user/userGET.js @@ -11,8 +11,7 @@ class usersGET extends Route { user: { id: user.id, username: user.username, - isAdmin: user.isAdmin, - apiKey: user.apiKey + isAdmin: user.isAdmin } }); } diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js index e588c22..5875dbb 100644 --- a/src/api/routes/verifyGET.js +++ b/src/api/routes/verifyGET.js @@ -9,7 +9,6 @@ class verifyGET extends Route { const returnUser = { id: user.id, username: user.username, - apiKey: user.apiKey, isAdmin: user.isAdmin }; -- cgit v1.2.3 From 5df57517365623ffde5acb3f6d06dffe07960704 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 29 Mar 2019 00:36:28 +0900 Subject: Removed apikey from user object and added route for requesting a new one --- src/api/routes/user/apiKey.js | 23 ++++++++++++++++------- src/api/routes/user/userGET.js | 3 +-- src/api/routes/verifyGET.js | 1 - 3 files changed, 17 insertions(+), 10 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/user/apiKey.js b/src/api/routes/user/apiKey.js index 820e28c..f80d563 100644 --- a/src/api/routes/user/apiKey.js +++ b/src/api/routes/user/apiKey.js @@ -1,21 +1,30 @@ const Route = require('../../structures/Route'); const randomstring = require('randomstring'); const moment = require('moment'); +const bcrypt = require('bcrypt'); +const { dump } = require('dumper.js'); class apiKeyPOST extends Route { constructor() { - super('/user/apikey/change', 'post'); + super('/user/apikey/change', 'post', { noApiKey: true }); } async run(req, res, db, user) { const now = moment.utc().toDate(); const apiKey = randomstring.generate(64); - await db.table('users') - .where({ id: user.id }) - .update({ - apiKey, - apiKeyEditedAt: now - }); + + try { + const hash = await bcrypt.hash(apiKey, 10); + await db.table('users') + .where({ id: user.id }) + .update({ + apiKey: hash, + apiKeyEditedAt: now + }); + } catch (error) { + dump(error); + return res.status(401).json({ message: 'There was a problem processing your account' }); + } return res.json({ message: 'Successfully created new api key', diff --git a/src/api/routes/user/userGET.js b/src/api/routes/user/userGET.js index 7929aac..fe46fd4 100644 --- a/src/api/routes/user/userGET.js +++ b/src/api/routes/user/userGET.js @@ -11,8 +11,7 @@ class usersGET extends Route { user: { id: user.id, username: user.username, - isAdmin: user.isAdmin, - apiKey: user.apiKey + isAdmin: user.isAdmin } }); } diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js index e588c22..5875dbb 100644 --- a/src/api/routes/verifyGET.js +++ b/src/api/routes/verifyGET.js @@ -9,7 +9,6 @@ class verifyGET extends Route { const returnUser = { id: user.id, username: user.username, - apiKey: user.apiKey, isAdmin: user.isAdmin }; -- cgit v1.2.3 From 8e4f1b7838e3c43320f2e25e691c1808ae3c4089 Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 30 Sep 2019 07:06:22 +0000 Subject: feature: album links --- src/api/routes/albums/albumZipGET.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index b2c9fa7..d1d3e16 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -17,9 +17,13 @@ class albumGET extends Route { Make sure it exists and it's enabled */ const link = await db.table('links') - .where({ identifier, enabled: true }) + .where({ + identifier, + enabled: true, + enableDownload: true + }) .first(); - if (!link) return res.status(400).json({ message: 'The identifier supplied could not be found' }); + if (!link) return res.status(400).json({ message: 'The supplied identifier could not be found' }); /* Same with the album, just to make sure is not a deleted album and a leftover link -- cgit v1.2.3 From 0d36f0d69aaf10fad4608f630a8f7dfe263ea74c Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 30 Sep 2019 07:06:35 +0000 Subject: feature: tags logic --- src/api/routes/files/tagAddPOST.js | 27 +++++++++++++++++++++++++++ src/api/routes/files/tagDelPOST.js | 27 +++++++++++++++++++++++++++ src/api/routes/tags/tagPOST.js | 2 +- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/api/routes/files/tagAddPOST.js create mode 100644 src/api/routes/files/tagDelPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/files/tagAddPOST.js b/src/api/routes/files/tagAddPOST.js new file mode 100644 index 0000000..9d334d8 --- /dev/null +++ b/src/api/routes/files/tagAddPOST.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); + +class tagAddPOST extends Route { + constructor() { + super('/file/tag/add', 'post'); + } + + run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { fileId, tagNames } = req.body; + if (!fileId || !tagNames.length) return res.status(400).json({ message: 'No tags provided' }); + + tagNames.forEach(async tag => { + try { + await db.table('fileTags').insert({ fileId, tag }); + } catch (error) { + return super.error(res, error); + } + }); + + return res.json({ + message: 'Successfully added file to album' + }); + } +} + +module.exports = tagAddPOST; diff --git a/src/api/routes/files/tagDelPOST.js b/src/api/routes/files/tagDelPOST.js new file mode 100644 index 0000000..fd6bbd0 --- /dev/null +++ b/src/api/routes/files/tagDelPOST.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); + +class albumDelPOST extends Route { + constructor() { + super('/file/album/del', 'post'); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { fileId, albumId } = req.body; + if (!fileId || !albumId) return res.status(400).json({ message: 'No id provided' }); + + try { + await db.table('albumsFiles') + .where({ fileId, albumId }) + .delete(); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully removed file from album' + }); + } +} + +module.exports = albumDelPOST; diff --git a/src/api/routes/tags/tagPOST.js b/src/api/routes/tags/tagPOST.js index 0df36e1..489dac3 100644 --- a/src/api/routes/tags/tagPOST.js +++ b/src/api/routes/tags/tagPOST.js @@ -27,7 +27,7 @@ class tagPOST extends Route { editedAt: now }); - return res.json({ message: 'The album was created successfully' }); + return res.json({ message: 'The tag was created successfully' }); } } -- cgit v1.2.3 From 4db167ec43806e20cc40932a292efc2909e69328 Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 30 Sep 2019 07:24:37 +0000 Subject: Fix deletion of albums and links --- src/api/routes/albums/albumDELETE.js | 10 ++-------- src/api/routes/albums/albumPurgeDELETE.js | 29 +++++++++++++++++++++++++++++ src/api/routes/albums/link/linkDELETE.js | 4 ---- 3 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 src/api/routes/albums/albumPurgeDELETE.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index b43d046..2aa9942 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -3,11 +3,11 @@ const Util = require('../../utils/Util'); class albumDELETE extends Route { constructor() { - super('/album/:id/:purge*?', 'delete'); + super('/album/:id', 'delete'); } async run(req, res, db, user) { - const { id, purge } = req.params; + const { id } = req.params; if (!id) return res.status(400).json({ message: 'Invalid album ID supplied' }); /* @@ -17,12 +17,6 @@ class albumDELETE extends Route { if (!album) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); try { - /* - Should we also delete every file of that album? - */ - if (purge) { - await Util.deleteAllFilesFromAlbum(id); - } /* Delete the album */ diff --git a/src/api/routes/albums/albumPurgeDELETE.js b/src/api/routes/albums/albumPurgeDELETE.js new file mode 100644 index 0000000..5a67c8e --- /dev/null +++ b/src/api/routes/albums/albumPurgeDELETE.js @@ -0,0 +1,29 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class albumDELETE extends Route { + constructor() { + super('/album/:id/purge', 'delete'); + } + + async run(req, res, db, user) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid album ID supplied' }); + + /* + Check if the album exists + */ + const album = await db.table('albums').where({ id, userId: user.id }).first(); + if (!album) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + + try { + await Util.deleteAllFilesFromAlbum(id); + await db.table('albums').where({ id }).delete(); + return res.json({ message: 'The album was deleted successfully' }); + } catch (error) { + return super.error(res, error); + } + } +} + +module.exports = albumDELETE; diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js index 3ec4d9d..7adcaac 100644 --- a/src/api/routes/albums/link/linkDELETE.js +++ b/src/api/routes/albums/link/linkDELETE.js @@ -7,10 +7,6 @@ class linkDELETE extends Route { } async run(req, res, db) { - console.log('------------------------------'); - console.log('YES HI'); - console.log('------------------------------'); - console.log('WHO NEEDS FANCY DEBUGGING TOOLS ANYWAYS'); const { identifier } = req.params; if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); -- cgit v1.2.3 From a552aca8ab67535bf025c6f06f751a0b11ef2e9a Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 1 Oct 2019 14:08:43 -0300 Subject: chore: Remove unnecesary stuff --- src/api/routes/files/uploadPOST.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 4e6ac55..e4d13b3 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -17,7 +17,7 @@ const fs = require('fs'); class uploadPOST extends Route { constructor() { - super('/upload', 'post', { bypassAuth: true }); + super('/upload.....', 'post', { bypassAuth: true }); } async run(req, res, db) { -- cgit v1.2.3 From 579e1e754ab59a69925b5114641174aa70d18555 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 1 Oct 2019 14:11:16 -0300 Subject: feature: uploader with chunks support --- src/api/routes/uploads/chunksPOST.js | 76 +++++++++++++++++++++++++++++ src/api/routes/uploads/uploadPOST.js | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/api/routes/uploads/chunksPOST.js create mode 100644 src/api/routes/uploads/uploadPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js new file mode 100644 index 0000000..075b4cd --- /dev/null +++ b/src/api/routes/uploads/chunksPOST.js @@ -0,0 +1,76 @@ +const Route = require('../../structures/Route'); +const path = require('path'); +const Util = require('../../utils/Util'); +const jetpack = require('fs-jetpack'); +const randomstring = require('randomstring'); + +class uploadPOST extends Route { + constructor() { + super('/upload/chunks', 'post', { bypassAuth: true }); + } + + async run(req, res, db) { + const filename = Util.getUniqueFilename(randomstring.generate(32)); + // console.log('Files', req.body.files); + const info = { + size: req.body.files[0].size, + url: `${process.env.DOMAIN}/` + }; + + for (const chunk of req.body.files) { + const { uuid, count } = chunk; + // console.log('Chunk', chunk); + + const chunkOutput = path.join(__dirname, + '..', + '..', + '..', + '..', + process.env.UPLOAD_FOLDER, + 'chunks', + uuid); + const chunkDir = await jetpack.list(chunkOutput); + const ext = path.extname(chunkDir[0]); + const output = path.join(__dirname, + '..', + '..', + '..', + '..', + process.env.UPLOAD_FOLDER, + `${filename}${ext || ''}`); + chunkDir.sort(); + + // Save some data + info.name = `${filename}${ext || ''}`; + info.url += `${filename}${ext || ''}`; + + for (let i = 0; i < chunkDir.length; i++) { + const dir = path.join(__dirname, + '..', + '..', + '..', + '..', + process.env.UPLOAD_FOLDER, + 'chunks', + uuid, + chunkDir[i]); + const file = await jetpack.readAsync(dir, 'buffer'); + await jetpack.appendAsync(output, file); + } + await jetpack.removeAsync(chunkOutput); + } + + return res.send(201, { + message: 'Sucessfully merged the chunk(s).', + ...info + /* + name: `${filename}${ext || ''}`, + size: exists.size, + url: `${process.env.DOMAIN}/${exists.name}`, + deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}` + */ + }); + } +} + +module.exports = uploadPOST; diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js new file mode 100644 index 0000000..a461130 --- /dev/null +++ b/src/api/routes/uploads/uploadPOST.js @@ -0,0 +1,94 @@ +const Route = require('../../structures/Route'); +const path = require('path'); +const Util = require('../../utils/Util'); +const jetpack = require('fs-jetpack'); +const multer = require('multer'); +const upload = multer({ + storage: multer.memoryStorage(), + limits: { + fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000), + files: 1 + }, + fileFilter: (req, file, cb) => { + /* + if (options.blacklist.mimes.includes(file.mimetype)) { + return cb(new Error(`${file.mimetype} is a blacklisted filetype.`)); + } else if (options.blacklist.extensions.some(ext => path.extname(file.originalname).toLowerCase() === ext)) { + return cb(new Error(`${path.extname(file.originalname).toLowerCase()} is a blacklisted extension.`)); + } + */ + return cb(null, true); + } +}).array('files[]'); + +class uploadPOST extends Route { + constructor() { + super('/upload', 'post', { bypassAuth: true }); + } + + async run(req, res, db) { + const user = await Util.isAuthorized(req); + if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); + return upload(req, res, async err => { + if (err) console.error(err.message); + + const remappedKeys = this._remapKeys(req.body); + // const { uuid, chunkindex } = this._remapKeys(req.body); + let uploadedFile = {}; + for (const file of req.files) { + // console.log(file); + const ext = path.extname(file.originalname); + const hash = Util.generateFileHash(file.buffer); + const filename = Util.getUniqueFilename(file.originalname); + if (remappedKeys && remappedKeys.uuid) { + const chunkOutput = path.join(__dirname, + '..', + '..', + '..', + '..', + process.env.UPLOAD_FOLDER, + 'chunks', + remappedKeys.uuid, + `${remappedKeys.chunkindex.padStart(3, 0)}${ext || ''}`); + await jetpack.writeAsync(chunkOutput, file.buffer); + } else { + const output = path.join(__dirname, + '..', + '..', + '..', + '..', + process.env.UPLOAD_FOLDER, + filename); + await jetpack.writeAsync(output, file.buffer); + uploadedFile = { + name: filename, + hash, + size: file.buffer.length, + url: filename + }; + } + } + + if (!remappedKeys || !remappedKeys.uuid) Util.generateThumbnails(uploadedFile.name); + + return res.send(201, { + message: 'Sucessfully uploaded the file.', + ...uploadedFile + }); + }); + } + + _remapKeys(body) { + const keys = Object.keys(body); + if (keys.length) { + for (const key of keys) { + if (!/^dz/.test(key)) continue; + body[key.replace(/^dz/, '')] = body[key]; + delete body[key]; + } + return body; + } + } +} + +module.exports = uploadPOST; -- cgit v1.2.3 From c121bd42f38cc3bd47b68efd8cf70da158cbdf8d Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 12 Oct 2019 14:37:09 +0900 Subject: chore: upload fixes --- src/api/routes/files/filesGET.js | 1 + src/api/routes/uploads/chunksPOST.js | 2 +- src/api/routes/uploads/uploadPOST.js | 70 +++++++++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 6 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index ce288ff..f0779fd 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -33,6 +33,7 @@ class filesGET extends Route { For each file, create the public link to be able to display the file */ for (let file of files) { + console.log(file); file = Util.constructFilePublicLink(file); } diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js index 075b4cd..1c02bc7 100644 --- a/src/api/routes/uploads/chunksPOST.js +++ b/src/api/routes/uploads/chunksPOST.js @@ -60,7 +60,7 @@ class uploadPOST extends Route { await jetpack.removeAsync(chunkOutput); } - return res.send(201, { + return res.status(201).send({ message: 'Sucessfully merged the chunk(s).', ...info /* diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index a461130..06959f4 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -3,6 +3,7 @@ const path = require('path'); const Util = require('../../utils/Util'); const jetpack = require('fs-jetpack'); const multer = require('multer'); +const moment = require('moment'); const upload = multer({ storage: multer.memoryStorage(), limits: { @@ -29,14 +30,24 @@ class uploadPOST extends Route { async run(req, res, db) { const user = await Util.isAuthorized(req); if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); + return upload(req, res, async err => { if (err) console.error(err.message); - const remappedKeys = this._remapKeys(req.body); - // const { uuid, chunkindex } = this._remapKeys(req.body); + const albumId = req.body.albumid || req.headers.albumid; + if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); + if (albumId && user) { + const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); + if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); + } + let uploadedFile = {}; + let originalFile; + let insertedId; + + const remappedKeys = this._remapKeys(req.body); for (const file of req.files) { - // console.log(file); + originalFile = file; const ext = path.extname(file.originalname); const hash = Util.generateFileHash(file.buffer); const filename = Util.getUniqueFilename(file.originalname); @@ -69,15 +80,64 @@ class uploadPOST extends Route { } } - if (!remappedKeys || !remappedKeys.uuid) Util.generateThumbnails(uploadedFile.name); + if (!remappedKeys || !remappedKeys.uuid) { + Util.generateThumbnails(uploadedFile.name); + insertedId = await this.saveFileToDatabase(req, res, user, db, uploadedFile, originalFile); + if (!insertedId) return res.status(500).json({ message: 'There was an error saving the file.' }); + uploadedFile.deleteUrl = `${process.env.DOMAIN}/api/file/${insertedId[0]}`; + } - return res.send(201, { + return res.status(201).send({ message: 'Sucessfully uploaded the file.', ...uploadedFile }); }); } + async saveFileToDatabase(req, res, user, db, file, originalFile) { + /* + Save the upload information to the database + */ + const now = moment.utc().toDate(); + let insertedId = null; + try { + /* + This is so fucking dumb + */ + if (process.env.DB_CLIENT === 'sqlite3') { + insertedId = await db.table('files').insert({ + userId: user ? user.id : null, + name: file.name, + original: originalFile.originalname, + type: originalFile.mimetype || '', + size: file.size, + hash: file.hash, + ip: req.ip, + createdAt: now, + editedAt: now + }); + } else { + insertedId = await db.table('files').insert({ + userId: user ? user.id : null, + name: file.name, + original: originalFile.originalname, + type: originalFile.mimetype || '', + size: file.size, + hash: file.hash, + ip: req.ip, + createdAt: now, + editedAt: now + }, 'id'); + } + return insertedId; + } catch (error) { + console.error('There was an error saving the file to the database'); + console.error(error); + return null; + // return res.status(500).json({ message: 'There was an error uploading the file.' }); + } + } + _remapKeys(body) { const keys = Object.keys(body); if (keys.length) { -- cgit v1.2.3 From 459ab5433b9c3b60f43f9b2293189be8e29e0e84 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 12 Oct 2019 14:58:58 +0900 Subject: chore: remove exif strip support. After some thought, modifying uploaded files is not something I want to support. --- src/api/routes/files/uploadPOST.js | 8 -------- src/api/routes/service/configGET.js | 1 - 2 files changed, 9 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index e4d13b3..5c6bcb0 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -7,7 +7,6 @@ const jetpack = require('fs-jetpack'); const Busboy = require('busboy'); const fs = require('fs'); /* - TODO: Strip exif data if the owner/user configured it as such TODO: If source has transparency generate a png thumbnail, otherwise a jpg. TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover. TODO: If source is a video, generate a thumb of the first frame and save the video length. @@ -176,13 +175,6 @@ class uploadPOST extends Route { } } - /* - If exif removal has been force service-wide or requested by the user, remove it - */ - if (process.env.STRIP_EXIF == 'true') { // || user.settings.stripExif) { - // Util.removeExif(upload.filename); - } - /* Generate those thumbnails */ diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js index e12c57b..b653066 100644 --- a/src/api/routes/service/configGET.js +++ b/src/api/routes/service/configGET.js @@ -17,7 +17,6 @@ class configGET extends Route { albumLinkLength: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), generateThumbnails: process.env.GENERATE_THUMBNAILS == 'true' ? true : false, generateZips: process.env.GENERATE_ZIPS == 'true' ? true : false, - stripExif: process.env.STRIP_EXIF == 'true' ? true : false, publicMode: process.env.PUBLIC_MODE == 'true' ? true : false, enableAccounts: process.env.USER_ACCOUNTS == 'true' ? true : false } -- cgit v1.2.3 From 391ee68e4a67aec640e25bc3506f9e31c77e58f5 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 12 Oct 2019 15:47:25 +0900 Subject: chore: Upgrade buefy to newest version --- src/api/routes/files/filesGET.js | 1 - 1 file changed, 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index f0779fd..ce288ff 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -33,7 +33,6 @@ class filesGET extends Route { For each file, create the public link to be able to display the file */ for (let file of files) { - console.log(file); file = Util.constructFilePublicLink(file); } -- cgit v1.2.3 From e6eb13e5cd14cea7ed3d5f89af03d61d59311734 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 12 Oct 2019 17:52:49 +0900 Subject: feature: save uploaded files to album if specified --- src/api/routes/uploads/uploadPOST.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 06959f4..3411abc 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -31,19 +31,20 @@ class uploadPOST extends Route { const user = await Util.isAuthorized(req); if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); + const albumId = req.body.albumid || req.headers.albumid; + if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); + if (albumId && user) { + const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); + if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); + } + return upload(req, res, async err => { if (err) console.error(err.message); - const albumId = req.body.albumid || req.headers.albumid; - if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); - if (albumId && user) { - const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); - if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); - } - let uploadedFile = {}; let originalFile; let insertedId; + const now = moment.utc().toDate(); const remappedKeys = this._remapKeys(req.body); for (const file of req.files) { @@ -87,6 +88,19 @@ class uploadPOST extends Route { uploadedFile.deleteUrl = `${process.env.DOMAIN}/api/file/${insertedId[0]}`; } + /* + If the upload had an album specified we make sure to create the relation + and update the according timestamps.. + */ + if (albumId) { + try { + await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] }); + await db.table('albums').where('id', albumId).update('editedAt', now); + } catch (error) { + console.error(error); + } + } + return res.status(201).send({ message: 'Sucessfully uploaded the file.', ...uploadedFile -- cgit v1.2.3 From 2695d192ba253219f41dce259cc53070058cad91 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 12 Oct 2019 18:18:32 +0900 Subject: feature: prevent duplicated files - If the user uploads a file that already exists under their account, it will return the previous url - If an anonymous user uploads a file and already exists previously from another anon, return that url - Files are unique per account since they can be deleted, anonymous files can't --- src/api/routes/uploads/uploadPOST.js | 134 +++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 47 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 3411abc..d35b9fc 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -42,63 +42,66 @@ class uploadPOST extends Route { if (err) console.error(err.message); let uploadedFile = {}; - let originalFile; let insertedId; - const now = moment.utc().toDate(); const remappedKeys = this._remapKeys(req.body); - for (const file of req.files) { - originalFile = file; - const ext = path.extname(file.originalname); - const hash = Util.generateFileHash(file.buffer); - const filename = Util.getUniqueFilename(file.originalname); - if (remappedKeys && remappedKeys.uuid) { - const chunkOutput = path.join(__dirname, - '..', - '..', - '..', - '..', - process.env.UPLOAD_FOLDER, - 'chunks', - remappedKeys.uuid, - `${remappedKeys.chunkindex.padStart(3, 0)}${ext || ''}`); - await jetpack.writeAsync(chunkOutput, file.buffer); - } else { - const output = path.join(__dirname, - '..', - '..', - '..', - '..', - process.env.UPLOAD_FOLDER, - filename); - await jetpack.writeAsync(output, file.buffer); - uploadedFile = { - name: filename, - hash, - size: file.buffer.length, - url: filename - }; - } + const file = req.files[0]; + + const ext = path.extname(file.originalname); + const hash = Util.generateFileHash(file.buffer); + + const filename = Util.getUniqueFilename(file.originalname); + + /* + First let's get the hash of the file. This will be useful to check if the file + has already been upload by either the user or an anonymous user. + In case this is true, instead of uploading it again we retrieve the url + of the file that is already saved and thus don't store extra copies of the same file. + + For this we need to wait until we have a filename so that we can delete the uploaded file. + */ + const exists = await this.checkIfFileExists(db, user, hash); + if (exists) return this.fileExists(res, exists, filename); + + if (remappedKeys && remappedKeys.uuid) { + const chunkOutput = path.join(__dirname, + '..', + '..', + '..', + '..', + process.env.UPLOAD_FOLDER, + 'chunks', + remappedKeys.uuid, + `${remappedKeys.chunkindex.padStart(3, 0)}${ext || ''}`); + await jetpack.writeAsync(chunkOutput, file.buffer); + } else { + const output = path.join(__dirname, + '..', + '..', + '..', + '..', + process.env.UPLOAD_FOLDER, + filename); + await jetpack.writeAsync(output, file.buffer); + uploadedFile = { + name: filename, + hash, + size: file.buffer.length, + url: filename + }; } if (!remappedKeys || !remappedKeys.uuid) { Util.generateThumbnails(uploadedFile.name); - insertedId = await this.saveFileToDatabase(req, res, user, db, uploadedFile, originalFile); + insertedId = await this.saveFileToDatabase(req, res, user, db, uploadedFile, file); if (!insertedId) return res.status(500).json({ message: 'There was an error saving the file.' }); uploadedFile.deleteUrl = `${process.env.DOMAIN}/api/file/${insertedId[0]}`; - } - /* - If the upload had an album specified we make sure to create the relation - and update the according timestamps.. - */ - if (albumId) { - try { - await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] }); - await db.table('albums').where('id', albumId).update('editedAt', now); - } catch (error) { - console.error(error); - } + /* + If the upload had an album specified we make sure to create the relation + and update the according timestamps.. + */ + this.saveFileToAlbum(db, albumId, insertedId); } return res.status(201).send({ @@ -108,6 +111,43 @@ class uploadPOST extends Route { }); } + fileExists(res, exists, filename) { + res.json({ + message: 'Successfully uploaded the file.', + name: exists.name, + hash: exists.hash, + size: exists.size, + url: `${process.env.DOMAIN}/${exists.name}`, + deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`, + repeated: true + }); + + return Util.deleteFile(filename); + } + + async checkIfFileExists(db, user, hash) { + const exists = await db.table('files') + .where(function() { // eslint-disable-line func-names + if (user) this.where('userId', user.id); + else this.whereNull('userId'); + }) + .where({ hash }) + .first(); + return exists; + } + + async saveFileToAlbum(db, albumId, insertedId) { + if (!albumId) return; + + const now = moment.utc().toDate(); + try { + await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] }); + await db.table('albums').where('id', albumId).update('editedAt', now); + } catch (error) { + console.error(error); + } + } + async saveFileToDatabase(req, res, user, db, file, originalFile) { /* Save the upload information to the database -- cgit v1.2.3 From bca8fbcd839d2239e3f6f141f662fbbc74726835 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 12 Oct 2019 21:14:19 +0900 Subject: refactor: removed useless code, cleaned up, fixed permissions --- src/api/routes/albums/albumDELETE.js | 2 +- src/api/routes/albums/albumPurgeDELETE.js | 2 +- src/api/routes/albums/albumsGET.js | 2 ++ src/api/routes/albums/link/linkDELETE.js | 4 ++-- src/api/routes/albums/link/linkPOST.js | 3 +-- src/api/routes/baseGET.js | 13 ------------- src/api/routes/files/albumAddPOST.js | 8 +++++++- src/api/routes/files/albumDelPOST.js | 8 +++++++- src/api/routes/files/tagAddPOST.js | 6 +++++- src/api/routes/files/tagDelPOST.js | 27 --------------------------- src/api/routes/files/uploadPOST.js | 6 +++++- src/api/routes/verifyGET.js | 12 +++++------- 12 files changed, 36 insertions(+), 57 deletions(-) delete mode 100644 src/api/routes/baseGET.js delete mode 100644 src/api/routes/files/tagDelPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index 2aa9942..96698b4 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -14,7 +14,7 @@ class albumDELETE extends Route { Check if the album exists */ const album = await db.table('albums').where({ id, userId: user.id }).first(); - if (!album) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + if (!album) return res.status(400).json({ message: 'The album doesn\'t exist or doesn\'t belong to the user' }); try { /* diff --git a/src/api/routes/albums/albumPurgeDELETE.js b/src/api/routes/albums/albumPurgeDELETE.js index 5a67c8e..a63eafc 100644 --- a/src/api/routes/albums/albumPurgeDELETE.js +++ b/src/api/routes/albums/albumPurgeDELETE.js @@ -14,7 +14,7 @@ class albumDELETE extends Route { Check if the album exists */ const album = await db.table('albums').where({ id, userId: user.id }).first(); - if (!album) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + if (!album) return res.status(400).json({ message: 'The album doesn\'t exist or doesn\'t belong to the user' }); try { await Util.deleteAllFilesFromAlbum(id); diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index 3be1213..c61ad03 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -18,6 +18,8 @@ class albumsGET extends Route { .select('id', 'name', 'createdAt', 'editedAt'); for (const album of albums) { + // TODO: Optimize the shit out of this. + /* Fetch every public link the album has */ diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js index 7adcaac..904687f 100644 --- a/src/api/routes/albums/link/linkDELETE.js +++ b/src/api/routes/albums/link/linkDELETE.js @@ -6,13 +6,13 @@ class linkDELETE extends Route { super('/album/link/delete/:identifier', 'delete'); } - async run(req, res, db) { + async run(req, res, db, user) { const { identifier } = req.params; if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); try { const link = await db.table('links') - .where({ identifier }) + .where({ identifier, userId: user.id }) .first(); dump(link); diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 297348c..6009922 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -1,6 +1,5 @@ const Route = require('../../../structures/Route'); const Util = require('../../../utils/Util'); -const log = require('../../../utils/Log'); class linkPOST extends Route { constructor() { @@ -15,7 +14,7 @@ class linkPOST extends Route { /* Make sure the album exists */ - const exists = await db.table('albums').where('id', albumId).first(); + const exists = await db.table('albums').where({ id: albumId, userId: user.id }).first(); if (!exists) return res.status(400).json({ message: 'Album doesn\t exist' }); /* diff --git a/src/api/routes/baseGET.js b/src/api/routes/baseGET.js deleted file mode 100644 index a6c01ea..0000000 --- a/src/api/routes/baseGET.js +++ /dev/null @@ -1,13 +0,0 @@ -const Route = require('../structures/Route'); - -class verifyGET extends Route { - constructor() { - super('/', 'get', { bypassAuth: true }); - } - - run(req, res) { - return res.json({ message: 'Hai hai api desu.' }); - } -} - -module.exports = verifyGET; diff --git a/src/api/routes/files/albumAddPOST.js b/src/api/routes/files/albumAddPOST.js index fc4ee71..af39caa 100644 --- a/src/api/routes/files/albumAddPOST.js +++ b/src/api/routes/files/albumAddPOST.js @@ -5,11 +5,17 @@ class albumAddPOST extends Route { super('/file/album/add', 'post'); } - async run(req, res, db) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { fileId, albumId } = req.body; if (!fileId || !albumId) return res.status(400).json({ message: 'No id provided' }); + // Make sure both file and album belong to the user + const file = await db.table('files').where({ id: fileId, userId: user.id }).first(); + if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' }); + const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); + if (!album) return res.status(400).json({ message: 'Album doesn\'t exist.' }); + try { await db.table('albumsFiles') .insert({ fileId, albumId }); diff --git a/src/api/routes/files/albumDelPOST.js b/src/api/routes/files/albumDelPOST.js index fd6bbd0..9a4b87b 100644 --- a/src/api/routes/files/albumDelPOST.js +++ b/src/api/routes/files/albumDelPOST.js @@ -5,11 +5,17 @@ class albumDelPOST extends Route { super('/file/album/del', 'post'); } - async run(req, res, db) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { fileId, albumId } = req.body; if (!fileId || !albumId) return res.status(400).json({ message: 'No id provided' }); + // Make sure both file and album belong to the user + const file = await db.table('files').where({ id: fileId, userId: user.id }).first(); + if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' }); + const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); + if (!album) return res.status(400).json({ message: 'Album doesn\'t exist.' }); + try { await db.table('albumsFiles') .where({ fileId, albumId }) diff --git a/src/api/routes/files/tagAddPOST.js b/src/api/routes/files/tagAddPOST.js index 9d334d8..25467ab 100644 --- a/src/api/routes/files/tagAddPOST.js +++ b/src/api/routes/files/tagAddPOST.js @@ -5,11 +5,15 @@ class tagAddPOST extends Route { super('/file/tag/add', 'post'); } - run(req, res, db) { + async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { fileId, tagNames } = req.body; if (!fileId || !tagNames.length) return res.status(400).json({ message: 'No tags provided' }); + // Make sure the file belongs to the user + const file = await db.table('files').where({ id: fileId, userId: user.id }).first(); + if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' }); + tagNames.forEach(async tag => { try { await db.table('fileTags').insert({ fileId, tag }); diff --git a/src/api/routes/files/tagDelPOST.js b/src/api/routes/files/tagDelPOST.js deleted file mode 100644 index fd6bbd0..0000000 --- a/src/api/routes/files/tagDelPOST.js +++ /dev/null @@ -1,27 +0,0 @@ -const Route = require('../../structures/Route'); - -class albumDelPOST extends Route { - constructor() { - super('/file/album/del', 'post'); - } - - async run(req, res, db) { - if (!req.body) return res.status(400).json({ message: 'No body provided' }); - const { fileId, albumId } = req.body; - if (!fileId || !albumId) return res.status(400).json({ message: 'No id provided' }); - - try { - await db.table('albumsFiles') - .where({ fileId, albumId }) - .delete(); - } catch (error) { - return super.error(res, error); - } - - return res.json({ - message: 'Successfully removed file from album' - }); - } -} - -module.exports = albumDelPOST; diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js index 5c6bcb0..6996a6e 100644 --- a/src/api/routes/files/uploadPOST.js +++ b/src/api/routes/files/uploadPOST.js @@ -19,10 +19,14 @@ class uploadPOST extends Route { super('/upload.....', 'post', { bypassAuth: true }); } - async run(req, res, db) { + run(req, res) { + return res.status(201).send(); + + /* const user = await Util.isAuthorized(req); if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); return this.uploadFile(req, res, db, user); + */ } async processFile(req, res, db, user, file) { diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js index 5875dbb..2f370e8 100644 --- a/src/api/routes/verifyGET.js +++ b/src/api/routes/verifyGET.js @@ -6,15 +6,13 @@ class verifyGET extends Route { } run(req, res, db, user) { - const returnUser = { - id: user.id, - username: user.username, - isAdmin: user.isAdmin - }; - return res.json({ message: 'Successfully verified token', - user: returnUser + user: { + id: user.id, + username: user.username, + isAdmin: user.isAdmin + } }); } } -- cgit v1.2.3 From cba7bf8586f59a049f79aba586db201ac6f3530b Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 13 Oct 2019 02:53:45 +0900 Subject: This commit adds a bunch of features for admins: * banning IP * see files from other users if you are admin * be able to see details of an uploaded file and it's user * improved display of thumbnails for non-image files --- src/api/routes/admin/banIP.js | 25 +++++++++++++++++++++++++ src/api/routes/admin/unBanIP.js | 27 +++++++++++++++++++++++++++ src/api/routes/admin/userGET.js | 32 ++++++++++++++++++++++++++++++++ src/api/routes/files/fileGET.js | 29 +++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 src/api/routes/admin/banIP.js create mode 100644 src/api/routes/admin/unBanIP.js create mode 100644 src/api/routes/admin/userGET.js create mode 100644 src/api/routes/files/fileGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/admin/banIP.js b/src/api/routes/admin/banIP.js new file mode 100644 index 0000000..692880d --- /dev/null +++ b/src/api/routes/admin/banIP.js @@ -0,0 +1,25 @@ +const Route = require('../../structures/Route'); + +class banIP extends Route { + constructor() { + super('/admin/ban/ip', 'post', { adminOnly: true }); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { ip } = req.body; + if (!ip) return res.status(400).json({ message: 'No ip provided' }); + + try { + await db.table('bans').insert({ ip }); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully banned the ip' + }); + } +} + +module.exports = banIP; diff --git a/src/api/routes/admin/unBanIP.js b/src/api/routes/admin/unBanIP.js new file mode 100644 index 0000000..493834b --- /dev/null +++ b/src/api/routes/admin/unBanIP.js @@ -0,0 +1,27 @@ +const Route = require('../../structures/Route'); + +class unBanIP extends Route { + constructor() { + super('/admin/unban/ip', 'post', { adminOnly: true }); + } + + async run(req, res, db) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { ip } = req.body; + if (!ip) return res.status(400).json({ message: 'No ip provided' }); + + try { + await db.table('bans') + .where({ ip }) + .delete(); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully unbanned the ip' + }); + } +} + +module.exports = unBanIP; diff --git a/src/api/routes/admin/userGET.js b/src/api/routes/admin/userGET.js new file mode 100644 index 0000000..895a565 --- /dev/null +++ b/src/api/routes/admin/userGET.js @@ -0,0 +1,32 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class usersGET extends Route { + constructor() { + super('/admin/users/:id', 'get', { adminOnly: true }); + } + + async run(req, res, db) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid user ID supplied' }); + + try { + const user = await db.table('users').where({ id }).first(); + const files = await db.table('files').where({ userId: user.id }); + + for (let file of files) { + file = Util.constructFilePublicLink(file); + } + + return res.json({ + message: 'Successfully retrieved user', + user, + files + }); + } catch (error) { + return super.error(res, error); + } + } +} + +module.exports = usersGET; diff --git a/src/api/routes/files/fileGET.js b/src/api/routes/files/fileGET.js new file mode 100644 index 0000000..3bb8da4 --- /dev/null +++ b/src/api/routes/files/fileGET.js @@ -0,0 +1,29 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class filesGET extends Route { + constructor() { + super('/file/:id', 'get', { adminOnly: true }); + } + + async run(req, res, db) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); + + let file = await db.table('files').where({ id }).first(); + const user = await db.table('users').where({ id: file.userId }).first(); + file = Util.constructFilePublicLink(file); + + // Additional relevant data + const filesFromUser = await db.table('files').where({ userId: user.id }).select('id'); + user.fileCount = filesFromUser.length; + + return res.json({ + message: 'Successfully retrieved file', + file, + user + }); + } +} + +module.exports = filesGET; -- cgit v1.2.3 From de54e19d3a102cad6364a6f9f50dab48c2367683 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 10 May 2020 00:03:45 +0900 Subject: chore: remove the use of uuid --- src/api/routes/auth/registerPOST.js | 2 ++ src/api/routes/tags/tagPOST.js | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index feeb360..0500ff6 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -2,6 +2,7 @@ const Route = require('../../structures/Route'); const log = require('../../utils/Log'); const bcrypt = require('bcrypt'); const moment = require('moment'); +const uuidv4 = require('uuid/v4'); class registerPOST extends Route { constructor() { @@ -44,6 +45,7 @@ class registerPOST extends Route { */ const now = moment.utc().toDate(); await db.table('users').insert({ + uuid: uuidv4(), username, password: hash, passwordEditedAt: now, diff --git a/src/api/routes/tags/tagPOST.js b/src/api/routes/tags/tagPOST.js index 489dac3..b6ec395 100644 --- a/src/api/routes/tags/tagPOST.js +++ b/src/api/routes/tags/tagPOST.js @@ -1,6 +1,5 @@ const Route = require('../../structures/Route'); const moment = require('moment'); -const util = require('../../utils/Util'); class tagPOST extends Route { constructor() { @@ -21,7 +20,6 @@ class tagPOST extends Route { const now = moment.utc().toDate(); await db.table('tags').insert({ name, - uuid: util.uuid(), userId: user.id, createdAt: now, editedAt: now -- cgit v1.2.3 From 6da29eb7c1f9f39ca924330dc42400cdf0af16e1 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 10 May 2020 20:00:52 +0900 Subject: Sort files by newest --- src/api/routes/admin/userGET.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/admin/userGET.js b/src/api/routes/admin/userGET.js index 895a565..14a6c92 100644 --- a/src/api/routes/admin/userGET.js +++ b/src/api/routes/admin/userGET.js @@ -12,7 +12,9 @@ class usersGET extends Route { try { const user = await db.table('users').where({ id }).first(); - const files = await db.table('files').where({ userId: user.id }); + const files = await db.table('files') + .where({ userId: user.id }) + .orderBy('id', 'desc'); for (let file of files) { file = Util.constructFilePublicLink(file); -- cgit v1.2.3 From 4c52932426a3e91a205940a6ab08bfee3e23fadf Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 10 May 2020 20:02:48 +0900 Subject: Features: * Serve files during development * Own endpoint for fetching the albums of a file --- src/api/routes/files/filesAlbumsGET.js | 31 +++++++++++++++++++++++++++++++ src/api/routes/files/filesGET.js | 23 ++--------------------- 2 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 src/api/routes/files/filesAlbumsGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/files/filesAlbumsGET.js b/src/api/routes/files/filesAlbumsGET.js new file mode 100644 index 0000000..c834658 --- /dev/null +++ b/src/api/routes/files/filesAlbumsGET.js @@ -0,0 +1,31 @@ +const Route = require('../../structures/Route'); + +class filesGET extends Route { + constructor() { + super('/file/:id/albums', 'get'); + } + + async run(req, res, db, user) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); + + let albums = []; + let albumFiles = await db.table('albumsFiles') + .where('fileId', id) + .select('albumId'); + + if (albumFiles.length) { + albumFiles = albumFiles.map(a => a.albumId); + albums = await db.table('albums') + .whereIn('id', albumFiles) + .select('id', 'name'); + } + + return res.json({ + message: 'Successfully retrieved file albums', + albums + }); + } +} + +module.exports = filesGET; diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index ce288ff..f1a3a26 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -7,31 +7,12 @@ class filesGET extends Route { } async run(req, res, db, user) { - /* - Get all the files from the user - */ + // Get all the files from the user const files = await db.table('files') .where('userId', user.id) .orderBy('id', 'desc'); - for (const file of files) { - file.albums = []; - const albumFiles = await db.table('albumsFiles') - .where('fileId', file.id); - if (!albumFiles.length) continue; - - for (const albumFile of albumFiles) { - const album = await db.table('albums') - .where('id', albumFile.albumId) - .select('id', 'name') - .first(); - if (!album) continue; - file.albums.push(album); - } - } - /* - For each file, create the public link to be able to display the file - */ + // For each file, create the public link to be able to display the file for (let file of files) { file = Util.constructFilePublicLink(file); } -- cgit v1.2.3 From b27b4c47f79071d3aa336b8f9a6578103df56fe9 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 10 May 2020 21:22:25 +0900 Subject: feat: Proper deleting of albums --- src/api/routes/albums/albumDELETE.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index 96698b4..4e6640e 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -17,10 +17,18 @@ class albumDELETE extends Route { if (!album) return res.status(400).json({ message: 'The album doesn\'t exist or doesn\'t belong to the user' }); try { - /* - Delete the album - */ + // Delete the album await db.table('albums').where({ id }).delete(); + + // Delete the relation of any files attached to this album + await db.table('albumsFiles').where({ albumId: id }).delete(); + + // Delete the relation of any links attached to this album + await db.table('albumsLinks').where({ albumId: id }).delete(); + + // Delete any album links created for this album + await db.table('links').where({ albumId: id }).delete(); + return res.json({ message: 'The album was deleted successfully' }); } catch (error) { return super.error(res, error); -- cgit v1.2.3 From ec67bb808773bed7fa5c39bd696d8f635fff6c42 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 10 May 2020 22:44:21 +0900 Subject: fix: remove uuid from user registration --- src/api/routes/auth/registerPOST.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index 0500ff6..feeb360 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -2,7 +2,6 @@ const Route = require('../../structures/Route'); const log = require('../../utils/Log'); const bcrypt = require('bcrypt'); const moment = require('moment'); -const uuidv4 = require('uuid/v4'); class registerPOST extends Route { constructor() { @@ -45,7 +44,6 @@ class registerPOST extends Route { */ const now = moment.utc().toDate(); await db.table('users').insert({ - uuid: uuidv4(), username, password: hash, passwordEditedAt: now, -- cgit v1.2.3 From b886fda0793b8a26de58cd462acf6676a0a8e7ed Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 11 May 2020 00:19:10 +0900 Subject: chore: cleanup and todo --- src/api/routes/admin/fileGET.js | 29 ++++ src/api/routes/albums/albumZipGET.js | 1 + src/api/routes/albums/link/linkDELETE.js | 2 - src/api/routes/files/fileGET.js | 29 ---- src/api/routes/files/filesAlbumsGET.js | 3 + src/api/routes/files/uploadPOST.js | 286 ------------------------------- src/api/routes/uploads/uploadPOST.js | 15 ++ 7 files changed, 48 insertions(+), 317 deletions(-) create mode 100644 src/api/routes/admin/fileGET.js delete mode 100644 src/api/routes/files/fileGET.js delete mode 100644 src/api/routes/files/uploadPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/admin/fileGET.js b/src/api/routes/admin/fileGET.js new file mode 100644 index 0000000..3bb8da4 --- /dev/null +++ b/src/api/routes/admin/fileGET.js @@ -0,0 +1,29 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class filesGET extends Route { + constructor() { + super('/file/:id', 'get', { adminOnly: true }); + } + + async run(req, res, db) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); + + let file = await db.table('files').where({ id }).first(); + const user = await db.table('users').where({ id: file.userId }).first(); + file = Util.constructFilePublicLink(file); + + // Additional relevant data + const filesFromUser = await db.table('files').where({ userId: user.id }).select('id'); + user.fileCount = filesFromUser.length; + + return res.json({ + message: 'Successfully retrieved file', + file, + user + }); + } +} + +module.exports = filesGET; diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index d1d3e16..a6ef6fd 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -13,6 +13,7 @@ class albumGET extends Route { const { identifier } = req.params; if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); + // TODO: Do we really want to let anyone create a zip of an album? /* Make sure it exists and it's enabled */ diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js index 904687f..23db411 100644 --- a/src/api/routes/albums/link/linkDELETE.js +++ b/src/api/routes/albums/link/linkDELETE.js @@ -15,8 +15,6 @@ class linkDELETE extends Route { .where({ identifier, userId: user.id }) .first(); - dump(link); - if (!link) return res.status(400).json({ message: 'Identifier doesn\'t exist' }); await db.table('links') diff --git a/src/api/routes/files/fileGET.js b/src/api/routes/files/fileGET.js deleted file mode 100644 index 3bb8da4..0000000 --- a/src/api/routes/files/fileGET.js +++ /dev/null @@ -1,29 +0,0 @@ -const Route = require('../../structures/Route'); -const Util = require('../../utils/Util'); - -class filesGET extends Route { - constructor() { - super('/file/:id', 'get', { adminOnly: true }); - } - - async run(req, res, db) { - const { id } = req.params; - if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); - - let file = await db.table('files').where({ id }).first(); - const user = await db.table('users').where({ id: file.userId }).first(); - file = Util.constructFilePublicLink(file); - - // Additional relevant data - const filesFromUser = await db.table('files').where({ userId: user.id }).select('id'); - user.fileCount = filesFromUser.length; - - return res.json({ - message: 'Successfully retrieved file', - file, - user - }); - } -} - -module.exports = filesGET; diff --git a/src/api/routes/files/filesAlbumsGET.js b/src/api/routes/files/filesAlbumsGET.js index c834658..7f1190c 100644 --- a/src/api/routes/files/filesAlbumsGET.js +++ b/src/api/routes/files/filesAlbumsGET.js @@ -9,6 +9,9 @@ class filesGET extends Route { const { id } = req.params; if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); + const file = await db.table('files').where({ id, userId: user.id }).first(); + if (!file) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + let albums = []; let albumFiles = await db.table('albumsFiles') .where('fileId', id) diff --git a/src/api/routes/files/uploadPOST.js b/src/api/routes/files/uploadPOST.js deleted file mode 100644 index 6996a6e..0000000 --- a/src/api/routes/files/uploadPOST.js +++ /dev/null @@ -1,286 +0,0 @@ -const Route = require('../../structures/Route'); -const path = require('path'); -const Util = require('../../utils/Util'); -const moment = require('moment'); -const log = require('../../utils/Log'); -const jetpack = require('fs-jetpack'); -const Busboy = require('busboy'); -const fs = require('fs'); -/* - TODO: If source has transparency generate a png thumbnail, otherwise a jpg. - TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover. - TODO: If source is a video, generate a thumb of the first frame and save the video length. - - TODO: Think if its worth making a folder with the user uuid in uploads/ and upload the pictures there so that this way at least not every single file will be in 1 directory -*/ - -class uploadPOST extends Route { - constructor() { - super('/upload.....', 'post', { bypassAuth: true }); - } - - run(req, res) { - return res.status(201).send(); - - /* - const user = await Util.isAuthorized(req); - if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); - return this.uploadFile(req, res, db, user); - */ - } - - async processFile(req, res, db, user, file) { - /* - Check if the user is trying to upload to an album - */ - const albumId = req.body.albumid || req.headers.albumid; - if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); - if (albumId && user) { - const album = await db.table('albums').where({ id: albumId, userId: user.id }).first(); - if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); - } - - /* - if (!albumId) log.info('Incoming file'); - else log.info(`Incoming file for album ${albumId}`); - */ - - let upload = file.data; - /* - If it's a chunked upload but this is not the last part of the chunk, just green light. - Otherwise, put the file together and process it - */ - if (file.body.uuid) { - if (file.body.chunkindex < file.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if - /* - We got a chunk that is not the last part, send smoke signal that we received it. - */ - return res.json({ message: 'Successfully uploaded chunk' }); - } - /* - Seems we finally got the last part of a chunk upload - */ - const uploadsDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER); - const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', file.body.uuid); - const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' }); - const originalname = Util.getFilenameFromPath(chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.'))); - - const tempFile = { - filename: Util.getUniqueFilename(originalname), - originalname, - size: file.body.totalfilesize - }; - - for (const chunkFile of chunkFiles) { - try { - const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop - await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop - } catch (error) { - log.error(error); - } - } - - try { - await jetpack.removeAsync(chunkedFileDir); - } catch (error) { - log.error(error); - } - - upload = tempFile; - } - - /* - First let's get the hash of the file. This will be useful to check if the file - has already been upload by either the user or an anonymous user. - In case this is true, instead of uploading it again we retrieve the url - of the file that is already saved and thus don't store extra copies of the same file. - */ - const hash = await Util.getFileHash(upload.filename); // eslint-disable-line no-await-in-loop - const exists = await db.table('files') // eslint-disable-line no-await-in-loop - .where(function() { - if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this - else this.where('userId', user.id); // eslint-disable-line no-invalid-this - }) - .where({ hash }) - .first(); - - if (exists) { - res.json({ - message: 'Successfully uploaded file BUT IT EXISTED ALREADY', - name: exists.name, - size: exists.size, - url: `${process.env.DOMAIN}/${exists.name}`, - deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}` - }); - - return Util.deleteFile(upload.filename); - } - - /* - The file doesn't appear to exist yet for this user, so let's - store the details on the database. - */ - const now = moment.utc().toDate(); - let insertedId = null; - try { - /* - This is so fucking dumb - */ - if (process.env.DB_CLIENT === 'sqlite3') { - insertedId = await db.table('files').insert({ - userId: user ? user.id : null, - name: upload.filename, - original: upload.originalname, - type: upload.mimetype || '', - size: upload.size, - hash, - ip: req.ip, - createdAt: now, - editedAt: now - }); - } else { - insertedId = await db.table('files').insert({ - userId: user ? user.id : null, - name: upload.filename, - original: upload.originalname, - type: upload.mimetype || '', - size: upload.size, - hash, - ip: req.ip, - createdAt: now, - editedAt: now - }, 'id'); - } - } catch (error) { - log.error('There was an error saving the file to the database'); - log.error(error); - return res.status(500).json({ message: 'There was an error uploading the file.' }); - } - - res.json({ - message: 'Successfully uploaded file', - name: upload.filename, - size: upload.size, - url: `${process.env.DOMAIN}/${upload.filename}`, - deleteUrl: `${process.env.DOMAIN}/api/file/${insertedId[0]}` - }); - - /* - If the upload had an album specified we make sure to create the relation - and update the according timestamps.. - */ - if (albumId) { - try { - await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] }); - await db.table('albums').where('id', albumId).update('editedAt', now); - } catch (error) { - log.error('There was an error updating editedAt on an album'); - log.error(error); - } - } - - /* - Generate those thumbnails - */ - return Util.generateThumbnails(upload.filename); - } - - uploadFile(req, res, db, user) { - const busboy = new Busboy({ - headers: req.headers, - limits: { - fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000), - files: 1 - } - }); - - const fileToUpload = { - data: {}, - body: {} - }; - - /* - Note: For this to work on every case, whoever is uploading a chunk - should really send the body first and the file last. Otherwise lolisafe - may not catch the field on time and the chunk may end up being saved - as a standalone file, completely broken. - */ - busboy.on('field', (fieldname, val) => { - if (/^dz/.test(fieldname)) { - fileToUpload.body[fieldname.substring(2)] = val; - } else { - fileToUpload.body[fieldname] = val; - } - }); - - /* - Hey ther's a file! Let's upload it. - */ - busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { - let name; - let saveTo; - - /* - Let check whether the file is part of a chunk upload or if it's a standalone one. - If the former, we should store them separately and join all the pieces after we - receive the last one. - */ - const ext = path.extname(filename).toLowerCase(); - if (Util.isExtensionBlocked(ext)) return res.status(400).json({ message: 'This extension is not allowed.' }); - - if (fileToUpload.body.uuid) { - name = `${filename}.${fileToUpload.body.chunkindex}`; - const chunkDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', fileToUpload.body.uuid); - jetpack.dir(chunkDir); - saveTo = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', fileToUpload.body.uuid, name); - } else { - name = Util.getUniqueFilename(filename); - if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' }); - saveTo = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, name); - } - - /* - Let's save some metadata for the db. - */ - fileToUpload.data = { filename: name, originalname: filename, encoding, mimetype }; - const stream = fs.createWriteStream(saveTo); - - file.on('data', data => { - fileToUpload.data.size = data.length; - }); - - /* - The file that is being uploaded is bigger than the limit specified on the config file - and thus we should close the stream and delete the file. - */ - file.on('limit', () => { - file.unpipe(stream); - stream.end(); - jetpack.removeAsync(saveTo); - return res.status(400).json({ message: 'The file is too big.' }); - }); - - file.on('error', err => { - log.error('There was an error uploading a file'); - log.error(err); - return res.status(500).json({ message: 'There was an error uploading the file.' }); - }); - - /* - TODO: Does this even work?? - */ - return file.pipe(stream); - }); - - busboy.on('error', err => { - log.error('There was an error uploading a file'); - log.error(err); - return res.status(500).json({ message: 'There was an error uploading the file.' }); - }); - - busboy.on('finish', () => this.processFile(req, res, db, user, fileToUpload)); - req.pipe(busboy); - } -} - -module.exports = uploadPOST; diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index d35b9fc..d611175 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -11,6 +11,7 @@ const upload = multer({ files: 1 }, fileFilter: (req, file, cb) => { + // TODO: Enable blacklisting of files/extensions /* if (options.blacklist.mimes.includes(file.mimetype)) { return cb(new Error(`${file.mimetype} is a blacklisted filetype.`)); @@ -22,6 +23,20 @@ const upload = multer({ } }).array('files[]'); +/* + TODO: If source has transparency generate a png thumbnail, otherwise a jpg. + TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover on the frontend. + TODO: If source is a video, generate a thumb of the first frame and save the video length to the file? + Another possible solution would be to play a gif on hover that grabs a few chunks like youtube. + + TODO: Think if its worth making a folder with the user uuid in uploads/ and upload the pictures there so + that this way at least not every single file will be in 1 directory + + - Addendum to this: Now that the default behaviour is to serve files with node, we can actually pull this off. Before this, having files in + subfolders meant messing with nginx and the paths, but now it should be fairly easy to re-arrange the folder structure with express.static + I see great value in this, open to suggestions. +*/ + class uploadPOST extends Route { constructor() { super('/upload', 'post', { bypassAuth: true }); -- cgit v1.2.3 From 496477ebda3f6c347a9944e22daae447d15ebc31 Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 11 May 2020 00:57:56 +0900 Subject: Feature: enable apiKey access to uploads and album fetching for the uploader/sharex/3rd party --- src/api/routes/albums/albumsGET.js | 2 +- src/api/routes/uploads/chunksPOST.js | 5 ++++- src/api/routes/uploads/uploadPOST.js | 5 ++++- src/api/routes/user/apiKey.js | 6 ++---- 4 files changed, 11 insertions(+), 7 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index c61ad03..bbd3cae 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -69,7 +69,7 @@ class albumsGET extends Route { class albumsDropdownGET extends Route { constructor() { - super('/albums/dropdown', 'get'); + super('/albums/dropdown', 'get', { canApiKey: true }); } async run(req, res, db, user) { diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js index 1c02bc7..013c0d6 100644 --- a/src/api/routes/uploads/chunksPOST.js +++ b/src/api/routes/uploads/chunksPOST.js @@ -6,7 +6,10 @@ const randomstring = require('randomstring'); class uploadPOST extends Route { constructor() { - super('/upload/chunks', 'post', { bypassAuth: true }); + super('/upload/chunks', 'post', { + bypassAuth: true, + canApiKey: true + }); } async run(req, res, db) { diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index d611175..6c01dd3 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -39,7 +39,10 @@ const upload = multer({ class uploadPOST extends Route { constructor() { - super('/upload', 'post', { bypassAuth: true }); + super('/upload', 'post', { + bypassAuth: true, + canApiKey: true + }); } async run(req, res, db) { diff --git a/src/api/routes/user/apiKey.js b/src/api/routes/user/apiKey.js index f80d563..a87d98d 100644 --- a/src/api/routes/user/apiKey.js +++ b/src/api/routes/user/apiKey.js @@ -1,12 +1,11 @@ const Route = require('../../structures/Route'); const randomstring = require('randomstring'); const moment = require('moment'); -const bcrypt = require('bcrypt'); const { dump } = require('dumper.js'); class apiKeyPOST extends Route { constructor() { - super('/user/apikey/change', 'post', { noApiKey: true }); + super('/user/apikey/change', 'post'); } async run(req, res, db, user) { @@ -14,11 +13,10 @@ class apiKeyPOST extends Route { const apiKey = randomstring.generate(64); try { - const hash = await bcrypt.hash(apiKey, 10); await db.table('users') .where({ id: user.id }) .update({ - apiKey: hash, + apiKey, apiKeyEditedAt: now }); } catch (error) { -- cgit v1.2.3 From b526d8803696161961ffb9eb912cb4b83a3c9eff Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 25 Jun 2020 01:35:52 +0900 Subject: Optimize the queries fetching albums/files --- src/api/routes/albums/albumFullGET.js | 31 +++++-------------------------- src/api/routes/albums/albumsGET.js | 32 ++++++++++---------------------- src/api/routes/albums/link/linksGET.js | 22 ++++++++++++++++++++++ 3 files changed, 37 insertions(+), 48 deletions(-) create mode 100644 src/api/routes/albums/link/linksGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js index f92f9ae..93b56ce 100644 --- a/src/api/routes/albums/albumFullGET.js +++ b/src/api/routes/albums/albumFullGET.js @@ -13,33 +13,12 @@ class albumGET extends Route { const album = await db.table('albums').where({ id, userId: user.id }).first(); if (!album) return res.status(404).json({ message: 'Album not found' }); - /* - Grab the files in a very unoptimized way. (This should be a join between both tables) - */ - const fileList = await db.table('albumsFiles').where('albumId', id).select('fileId'); - const fileIds = fileList.map(el => el.fileId); - const files = await db.table('files') - .whereIn('id', fileIds) - .orderBy('id', 'desc'); + const files = await db.table('albumsFiles') + .where({ albumId: id }) + .join('files', 'albumsFiles.fileId', 'files.id') + .select('files.id', 'files.name') + .orderBy('files.id', 'desc'); - for (const file of files) { - file.albums = []; - const albumFiles = await db.table('albumsFiles') - .where('fileId', file.id); - if (!albumFiles.length) continue; - - for (const albumFile of albumFiles) { - const album = await db.table('albums') - .where('id', albumFile.albumId) - .select('id', 'name') - .first(); - if (!album) continue; - file.albums.push(album); - } - } - /* - For each file, create the public link to be able to display the file - */ for (let file of files) { file = Util.constructFilePublicLink(file); } diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index bbd3cae..bbaa518 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -13,49 +13,37 @@ class albumsGET extends Route { for anyone consuming the API outside of the lolisafe frontend. */ const albums = await db.table('albums') - .where('userId', user.id) - // .where('enabled', true) - .select('id', 'name', 'createdAt', 'editedAt'); + .where('albums.userId', user.id) + .select('id', 'name', 'editedAt'); for (const album of albums) { - // TODO: Optimize the shit out of this. + // TODO: Optimize the shit out of this. Ideally a JOIN that grabs all the needed stuff in 1 query instead of 3 - /* - Fetch every public link the album has - */ - const links = await db.table('links').where('albumId', album.id); // eslint-disable-line no-await-in-loop + // Fetch every public link the album has + // const links = await db.table('links').where('albumId', album.id); // eslint-disable-line no-await-in-loop - /* - Fetch the total amount of files each album has. - */ + // Fetch the total amount of files each album has. const fileCount = await db.table('albumsFiles') // eslint-disable-line no-await-in-loop .where('albumId', album.id) .count({ count: 'id' }); - /* - Fetch the file list from each album but limit it to 5 per album - */ + // Fetch the file list from each album but limit it to 5 per album const filesToFetch = await db.table('albumsFiles') // eslint-disable-line no-await-in-loop .where('albumId', album.id) .select('fileId') .orderBy('id', 'desc') .limit(5); - /* - Fetch the actual files - */ + // Fetch the actual files const files = await db.table('files') // eslint-disable-line no-await-in-loop .whereIn('id', filesToFetch.map(el => el.fileId)) - .select('id', 'name', 'hash', 'original', 'size', 'type', 'createdAt', 'editedAt'); + .select('id', 'name'); - /* - Fetch thumbnails and stuff - */ + // Fetch thumbnails and stuff for (let file of files) { file = Util.constructFilePublicLink(file); } - album.links = links; album.fileCount = fileCount[0].count; album.files = files; } diff --git a/src/api/routes/albums/link/linksGET.js b/src/api/routes/albums/link/linksGET.js new file mode 100644 index 0000000..edab49a --- /dev/null +++ b/src/api/routes/albums/link/linksGET.js @@ -0,0 +1,22 @@ +const Route = require('../../../structures/Route'); + +class linkPOST extends Route { + constructor() { + super('/album/:id/links', 'get'); + } + + async run(req, res, db, user) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid id supplied' }); + + const links = await db.table('links') + .where({ albumId: id, userId: user.id }); + + return res.json({ + message: 'Successfully retrieved links', + links + }); + } +} + +module.exports = linkPOST; -- cgit v1.2.3 From a9fe08f9e577de8df0f742a428214c42e532d04c Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 25 Jun 2020 01:36:04 +0900 Subject: Update error message --- src/api/routes/albums/link/linkDELETE.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js index 23db411..b02d0b4 100644 --- a/src/api/routes/albums/link/linkDELETE.js +++ b/src/api/routes/albums/link/linkDELETE.js @@ -15,7 +15,7 @@ class linkDELETE extends Route { .where({ identifier, userId: user.id }) .first(); - if (!link) return res.status(400).json({ message: 'Identifier doesn\'t exist' }); + if (!link) return res.status(400).json({ message: 'Identifier doesn\'t exist or doesnt\'t belong to the user' }); await db.table('links') .where({ id: link.id }) -- cgit v1.2.3 From f189ddf9e6e1dda0c8e56afd367bc378ee19b8fd Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 25 Jun 2020 02:05:48 +0900 Subject: Cleanup --- src/api/routes/albums/albumsGET.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index bbaa518..1a7db87 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -19,9 +19,6 @@ class albumsGET extends Route { for (const album of albums) { // TODO: Optimize the shit out of this. Ideally a JOIN that grabs all the needed stuff in 1 query instead of 3 - // Fetch every public link the album has - // const links = await db.table('links').where('albumId', album.id); // eslint-disable-line no-await-in-loop - // Fetch the total amount of files each album has. const fileCount = await db.table('albumsFiles') // eslint-disable-line no-await-in-loop .where('albumId', album.id) -- cgit v1.2.3 From d1340c26b5f72a45fb577ad7879addcb548dcf12 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 25 Jun 2020 02:06:00 +0900 Subject: Optimize album view --- src/api/routes/albums/albumGET.js | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index fae7dd1..1bf3630 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -10,38 +10,26 @@ class albumGET extends Route { const { identifier } = req.params; if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' }); - /* - Make sure it exists and it's enabled - */ + // Make sure it exists and it's enabled const link = await db.table('links').where({ identifier, enabled: true }).first(); if (!link) return res.status(404).json({ message: 'The album could not be found' }); - /* - Same with the album, just to make sure is not a deleted album and a leftover link - */ + // Same with the album, just to make sure is not a deleted album and a leftover link const album = await db.table('albums').where('id', link.albumId).first(); if (!album) return res.status(404).json({ message: 'Album not found' }); - /* - Grab the files in a very unoptimized way. (This should be a join between both tables) - */ - const fileList = await db.table('albumsFiles').where('albumId', link.albumId).select('fileId'); - const fileIds = fileList.map(el => el.fileId); - const files = await db.table('files') - .whereIn('id', fileIds) - .orderBy('id', 'desc') - .select('name'); - - /* - Create the links for each file - */ + const files = await db.table('albumsFiles') + .where({ albumId: link.albumId }) + .join('files', 'albumsFiles.fileId', 'files.id') + .select('files.name') + .orderBy('files.id', 'desc'); + + // Create the links for each file for (let file of files) { file = Util.constructFilePublicLink(file); } - /* - Add 1 more view to the link - */ + // Add 1 more view to the link await db.table('links').where({ identifier }).update('views', Number(link.views) + 1); return res.json({ -- cgit v1.2.3 From 520062508ccad88d49229e603fc4d2c0c0a118d3 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Mon, 29 Jun 2020 22:18:42 +0300 Subject: feat: backend pagination serverLoad++; userRamUsage--; --- src/api/routes/files/filesGET.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index f1a3a26..07dc1f7 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -7,10 +7,23 @@ class filesGET extends Route { } async run(req, res, db, user) { - // Get all the files from the user - const files = await db.table('files') + let count = 0; + + let files = db.table('files') .where('userId', user.id) - .orderBy('id', 'desc'); + .orderBy('createdAt', 'desc'); + + const { page, limit = 100 } = req.query; + if (page && page >= 0) { + files = await files.offset((page - 1) * limit).limit(limit); + + count = (await db.table('files') + .count('id as count') + .where('userId', user.id) + .first()).count; + } else { + count = files.length; + } // For each file, create the public link to be able to display the file for (let file of files) { @@ -19,7 +32,8 @@ class filesGET extends Route { return res.json({ message: 'Successfully retrieved files', - files + files, + count }); } } -- cgit v1.2.3 From e9ef148d7498b7068274a4141d5591cc8a64016e Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 1 Jul 2020 20:40:10 +0300 Subject: feat: backend pagination for albums --- src/api/routes/albums/albumFullGET.js | 22 ++++++++++++++++++++-- src/api/routes/files/filesGET.js | 13 ++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js index 93b56ce..cf434e4 100644 --- a/src/api/routes/albums/albumFullGET.js +++ b/src/api/routes/albums/albumFullGET.js @@ -13,12 +13,29 @@ class albumGET extends Route { const album = await db.table('albums').where({ id, userId: user.id }).first(); if (!album) return res.status(404).json({ message: 'Album not found' }); - const files = await db.table('albumsFiles') + let count = 0; + + let files = db.table('albumsFiles') .where({ albumId: id }) .join('files', 'albumsFiles.fileId', 'files.id') .select('files.id', 'files.name') .orderBy('files.id', 'desc'); + const { page, limit = 100 } = req.query; + if (page && page >= 0) { + files = await files.offset((page - 1) * limit).limit(limit); + + const dbRes = await db.table('albumsFiles') + .count('* as count') + .where({ albumId: id }) + .first(); + + count = dbRes.count; + } else { + files = await files; // execute the query + count = files.length; + } + for (let file of files) { file = Util.constructFilePublicLink(file); } @@ -26,7 +43,8 @@ class albumGET extends Route { return res.json({ message: 'Successfully retrieved album', name: album.name, - files + files, + count }); } } diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index 07dc1f7..9e90633 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -10,18 +10,21 @@ class filesGET extends Route { let count = 0; let files = db.table('files') - .where('userId', user.id) + .where({ userId: user.id }) .orderBy('createdAt', 'desc'); const { page, limit = 100 } = req.query; if (page && page >= 0) { files = await files.offset((page - 1) * limit).limit(limit); - count = (await db.table('files') - .count('id as count') - .where('userId', user.id) - .first()).count; + const dbRes = await db.table('files') + .count('* as count') + .where({ userId: user.id }) + .first(); + + count = dbRes.count; } else { + files = await files; // execute the query count = files.length; } -- cgit v1.2.3 From dd46f79550d8e7a2f7a0364cc0fb8e7a38ed4aba Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 2 Jul 2020 23:40:35 +0300 Subject: feat: return APIKey when fetching user --- src/api/routes/user/userGET.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/user/userGET.js b/src/api/routes/user/userGET.js index fe46fd4..7929aac 100644 --- a/src/api/routes/user/userGET.js +++ b/src/api/routes/user/userGET.js @@ -11,7 +11,8 @@ class usersGET extends Route { user: { id: user.id, username: user.username, - isAdmin: user.isAdmin + isAdmin: user.isAdmin, + apiKey: user.apiKey } }); } -- cgit v1.2.3 From b620aa981546cb42e19f64d5349a9372e3d0c269 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Sat, 4 Jul 2020 03:25:04 +0300 Subject: feat: refactor some of the queries to returned added/updated data --- src/api/routes/albums/albumFullGET.js | 13 +++++++++---- src/api/routes/albums/albumPOST.js | 17 ++++++++++++----- src/api/routes/albums/albumsGET.js | 25 +++++++++++++------------ src/api/routes/albums/link/linkEditPOST.js | 21 +++++++++++++-------- src/api/routes/albums/link/linkPOST.js | 24 +++++++++++++++++------- 5 files changed, 64 insertions(+), 36 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js index cf434e4..a5c9707 100644 --- a/src/api/routes/albums/albumFullGET.js +++ b/src/api/routes/albums/albumFullGET.js @@ -10,22 +10,27 @@ class albumGET extends Route { const { id } = req.params; if (!id) return res.status(400).json({ message: 'Invalid id supplied' }); - const album = await db.table('albums').where({ id, userId: user.id }).first(); + const album = await db + .table('albums') + .where({ id, userId: user.id }) + .first(); if (!album) return res.status(404).json({ message: 'Album not found' }); let count = 0; - let files = db.table('albumsFiles') + let files = db + .table('albumsFiles') .where({ albumId: id }) .join('files', 'albumsFiles.fileId', 'files.id') - .select('files.id', 'files.name') + .select('files.id', 'files.name', 'files.createdAt') .orderBy('files.id', 'desc'); const { page, limit = 100 } = req.query; if (page && page >= 0) { files = await files.offset((page - 1) * limit).limit(limit); - const dbRes = await db.table('albumsFiles') + const dbRes = await db + .table('albumsFiles') .count('* as count') .where({ albumId: id }) .first(); diff --git a/src/api/routes/albums/albumPOST.js b/src/api/routes/albums/albumPOST.js index 0d3a44c..841da3d 100644 --- a/src/api/routes/albums/albumPOST.js +++ b/src/api/routes/albums/albumPOST.js @@ -14,18 +14,25 @@ class albumPOST extends Route { /* Check that an album with that name doesn't exist yet */ - const album = await db.table('albums').where({ name, userId: user.id }).first(); - if (album) return res.status(401).json({ message: 'There\'s already an album with that name' }); + const album = await db + .table('albums') + .where({ name, userId: user.id }) + .first(); + if (album) return res.status(401).json({ message: "There's already an album with that name" }); const now = moment.utc().toDate(); - await db.table('albums').insert({ + const insertObj = { name, userId: user.id, createdAt: now, editedAt: now - }); + }; - return res.json({ message: 'The album was created successfully' }); + const dbRes = await db.table('albums').insert(insertObj); + + insertObj.id = dbRes.pop(); + + return res.json({ message: 'The album was created successfully', data: insertObj }); } } diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index 1a7db87..569128c 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -12,30 +12,30 @@ class albumsGET extends Route { of the album files for displaying on the dashboard. It's probably useless for anyone consuming the API outside of the lolisafe frontend. */ - const albums = await db.table('albums') + const albums = await db + .table('albums') .where('albums.userId', user.id) - .select('id', 'name', 'editedAt'); + .select('id', 'name', 'createdAt', 'editedAt') + .orderBy('createdAt', 'desc'); for (const album of albums) { // TODO: Optimize the shit out of this. Ideally a JOIN that grabs all the needed stuff in 1 query instead of 3 // Fetch the total amount of files each album has. - const fileCount = await db.table('albumsFiles') // eslint-disable-line no-await-in-loop + const fileCount = await db + .table('albumsFiles') // eslint-disable-line no-await-in-loop .where('albumId', album.id) .count({ count: 'id' }); // Fetch the file list from each album but limit it to 5 per album - const filesToFetch = await db.table('albumsFiles') // eslint-disable-line no-await-in-loop + const files = await db + .table('albumsFiles') // eslint-disable-line no-await-in-loop + .join('files', { 'files.id': 'albumsFiles.fileId' }) .where('albumId', album.id) - .select('fileId') - .orderBy('id', 'desc') + .select('files.id', 'files.name') + .orderBy('albumsFiles.id', 'desc') .limit(5); - // Fetch the actual files - const files = await db.table('files') // eslint-disable-line no-await-in-loop - .whereIn('id', filesToFetch.map(el => el.fileId)) - .select('id', 'name'); - // Fetch thumbnails and stuff for (let file of files) { file = Util.constructFilePublicLink(file); @@ -58,7 +58,8 @@ class albumsDropdownGET extends Route { } async run(req, res, db, user) { - const albums = await db.table('albums') + const albums = await db + .table('albums') .where('userId', user.id) .select('id', 'name'); return res.json({ diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index 6776b73..0c7233b 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -14,17 +14,22 @@ class linkEditPOST extends Route { /* Make sure the link exists */ - const link = await db.table('links').where({ identifier, userId: user.id }).first(); - if (!link) return res.status(400).json({ message: 'The link doesn\'t exist or doesn\'t belong to the user' }); + const link = await db + .table('links') + .where({ identifier, userId: user.id }) + .first(); + if (!link) return res.status(400).json({ message: "The link doesn't exist or doesn't belong to the user" }); try { - await db.table('links') + const updateObj = { + enableDownload: enableDownload || false, + expiresAt // This one should be null if not supplied + }; + await db + .table('links') .where({ identifier }) - .update({ - enableDownload: enableDownload || false, - expiresAt // This one should be null if not supplied - }); - return res.json({ message: 'Editing the link was successful' }); + .update(updateObj); + return res.json({ message: 'Editing the link was successful', data: updateObj }); } catch (error) { return super.error(res, error); } diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 6009922..7ecc5cb 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -14,23 +14,32 @@ class linkPOST extends Route { /* Make sure the album exists */ - const exists = await db.table('albums').where({ id: albumId, userId: user.id }).first(); + const exists = await db + .table('albums') + .where({ id: albumId, userId: user.id }) + .first(); if (!exists) return res.status(400).json({ message: 'Album doesn\t exist' }); /* Count the amount of links created for that album already and error out if max was reached */ - const count = await db.table('links').where('albumId', albumId).count({ count: 'id' }); - if (count[0].count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10)) return res.status(400).json({ message: 'Maximum links per album reached' }); + const count = await db + .table('links') + .where('albumId', albumId) + .count({ count: 'id' }) + .first(); + if (count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10)) + return res.status(400).json({ message: 'Maximum links per album reached' }); /* Try to allocate a new identifier on the db */ const identifier = await Util.getUniqueAlbumIdentifier(); - if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); + if (!identifier) + return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); try { - await db.table('links').insert({ + const insertObj = { identifier, userId: user.id, albumId, @@ -38,11 +47,12 @@ class linkPOST extends Route { enableDownload: true, expiresAt: null, views: 0 - }); + }; + await db.table('links').insert(insertObj); return res.json({ message: 'The link was created successfully', - identifier + data: insertObj }); } catch (error) { return super.error(res, error); -- cgit v1.2.3 From 04fdd63cee5327f49e5e11d5837a9031027c34ef Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Sun, 5 Jul 2020 04:17:09 +0300 Subject: feat: refactor single album page to use vuex --- src/api/routes/albums/albumGET.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index 1bf3630..f40750b 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -21,7 +21,7 @@ class albumGET extends Route { const files = await db.table('albumsFiles') .where({ albumId: link.albumId }) .join('files', 'albumsFiles.fileId', 'files.id') - .select('files.name') + .select('files.name', 'files.id') .orderBy('files.id', 'desc'); // Create the links for each file @@ -36,7 +36,7 @@ class albumGET extends Route { message: 'Successfully retrieved files', name: album.name, downloadEnabled: link.enableDownload, - files + files, }); } } -- cgit v1.2.3 From fb0bc57542a44dcc94149f393d8a4ff0c2e7902b Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Tue, 7 Jul 2020 02:02:59 +0300 Subject: feat: try fixing THE SHITTY WATERFALL --- src/api/routes/albums/albumFullGET.js | 3 ++- src/api/routes/albums/albumGET.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js index a5c9707..2c3a790 100644 --- a/src/api/routes/albums/albumFullGET.js +++ b/src/api/routes/albums/albumFullGET.js @@ -41,6 +41,7 @@ class albumGET extends Route { count = files.length; } + // eslint-disable-next-line no-restricted-syntax for (let file of files) { file = Util.constructFilePublicLink(file); } @@ -49,7 +50,7 @@ class albumGET extends Route { message: 'Successfully retrieved album', name: album.name, files, - count + count, }); } } diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index f40750b..81edc95 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -25,6 +25,7 @@ class albumGET extends Route { .orderBy('files.id', 'desc'); // Create the links for each file + // eslint-disable-next-line no-restricted-syntax for (let file of files) { file = Util.constructFilePublicLink(file); } -- cgit v1.2.3 From 1a8b6602e094289a4f477c33e432e0f5e1587b45 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 8 Jul 2020 03:13:51 +0300 Subject: refactor: change uploader component to use vuex --- src/api/routes/albums/albumsGET.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index 569128c..c9ab025 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ const Route = require('../../structures/Route'); const Util = require('../../utils/Util'); @@ -19,17 +20,15 @@ class albumsGET extends Route { .orderBy('createdAt', 'desc'); for (const album of albums) { - // TODO: Optimize the shit out of this. Ideally a JOIN that grabs all the needed stuff in 1 query instead of 3 - // Fetch the total amount of files each album has. - const fileCount = await db - .table('albumsFiles') // eslint-disable-line no-await-in-loop + const fileCount = await db // eslint-disable-line no-await-in-loop + .table('albumsFiles') .where('albumId', album.id) .count({ count: 'id' }); // Fetch the file list from each album but limit it to 5 per album - const files = await db - .table('albumsFiles') // eslint-disable-line no-await-in-loop + const files = await db // eslint-disable-line no-await-in-loop + .table('albumsFiles') .join('files', { 'files.id': 'albumsFiles.fileId' }) .where('albumId', album.id) .select('files.id', 'files.name') @@ -47,7 +46,7 @@ class albumsGET extends Route { return res.json({ message: 'Successfully retrieved albums', - albums + albums, }); } } @@ -64,7 +63,7 @@ class albumsDropdownGET extends Route { .select('id', 'name'); return res.json({ message: 'Successfully retrieved albums', - albums + albums, }); } } -- cgit v1.2.3 From b519b6ccb469e874c783b995ddf0ab6fabdb5a0e Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 8 Jul 2020 03:37:50 +0300 Subject: refactor: refactor grid to use vuex for every action --- src/api/routes/files/albumAddPOST.js | 3 ++- src/api/routes/files/albumDelPOST.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/albumAddPOST.js b/src/api/routes/files/albumAddPOST.js index af39caa..a88e636 100644 --- a/src/api/routes/files/albumAddPOST.js +++ b/src/api/routes/files/albumAddPOST.js @@ -24,7 +24,8 @@ class albumAddPOST extends Route { } return res.json({ - message: 'Successfully added file to album' + message: 'Successfully added file to album', + data: { fileId, album: { id: album.id, name: album.name } }, }); } } diff --git a/src/api/routes/files/albumDelPOST.js b/src/api/routes/files/albumDelPOST.js index 9a4b87b..6e4d576 100644 --- a/src/api/routes/files/albumDelPOST.js +++ b/src/api/routes/files/albumDelPOST.js @@ -25,7 +25,8 @@ class albumDelPOST extends Route { } return res.json({ - message: 'Successfully removed file from album' + message: 'Successfully removed file from album', + data: { fileId, album: { id: album.id, name: album.name } }, }); } } -- cgit v1.2.3 From ad852de51a0d2dd5d29c08838d5a430c58849e74 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 8 Jul 2020 04:00:12 +0300 Subject: chore: linter the entire project using the new rules --- src/api/routes/admin/banIP.js | 2 +- src/api/routes/admin/fileGET.js | 2 +- src/api/routes/admin/unBanIP.js | 2 +- src/api/routes/admin/userDemote.js | 2 +- src/api/routes/admin/userDisable.js | 2 +- src/api/routes/admin/userEnable.js | 2 +- src/api/routes/admin/userGET.js | 2 +- src/api/routes/admin/userPromote.js | 2 +- src/api/routes/admin/userPurge.js | 2 +- src/api/routes/admin/usersGET.js | 2 +- src/api/routes/albums/albumDELETE.js | 1 - src/api/routes/albums/albumPOST.js | 4 +-- src/api/routes/albums/albumZipGET.js | 10 +++---- src/api/routes/albums/link/linkDELETE.js | 3 +- src/api/routes/albums/link/linkEditPOST.js | 3 +- src/api/routes/albums/link/linkPOST.js | 10 +++---- src/api/routes/albums/link/linksGET.js | 2 +- src/api/routes/auth/loginPOST.js | 8 +++--- src/api/routes/auth/registerPOST.js | 8 +++--- src/api/routes/files/filesAlbumsGET.js | 4 +-- src/api/routes/files/filesGET.js | 2 +- src/api/routes/files/tagAddPOST.js | 5 ++-- src/api/routes/service/configGET.js | 10 +++---- src/api/routes/tags/tagPOST.js | 4 +-- src/api/routes/tags/tagsGET.js | 3 +- src/api/routes/uploads/chunksPOST.js | 14 +++++----- src/api/routes/uploads/uploadPOST.js | 44 ++++++++++++++++-------------- src/api/routes/user/apiKey.js | 6 ++-- src/api/routes/user/changePasswordPOST.js | 6 ++-- src/api/routes/user/userGET.js | 4 +-- src/api/routes/verifyGET.js | 4 +-- 31 files changed, 86 insertions(+), 89 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/admin/banIP.js b/src/api/routes/admin/banIP.js index 692880d..4dfe03c 100644 --- a/src/api/routes/admin/banIP.js +++ b/src/api/routes/admin/banIP.js @@ -17,7 +17,7 @@ class banIP extends Route { } return res.json({ - message: 'Successfully banned the ip' + message: 'Successfully banned the ip', }); } } diff --git a/src/api/routes/admin/fileGET.js b/src/api/routes/admin/fileGET.js index 3bb8da4..0d1b147 100644 --- a/src/api/routes/admin/fileGET.js +++ b/src/api/routes/admin/fileGET.js @@ -21,7 +21,7 @@ class filesGET extends Route { return res.json({ message: 'Successfully retrieved file', file, - user + user, }); } } diff --git a/src/api/routes/admin/unBanIP.js b/src/api/routes/admin/unBanIP.js index 493834b..725468c 100644 --- a/src/api/routes/admin/unBanIP.js +++ b/src/api/routes/admin/unBanIP.js @@ -19,7 +19,7 @@ class unBanIP extends Route { } return res.json({ - message: 'Successfully unbanned the ip' + message: 'Successfully unbanned the ip', }); } } diff --git a/src/api/routes/admin/userDemote.js b/src/api/routes/admin/userDemote.js index b430a48..3f6623d 100644 --- a/src/api/routes/admin/userDemote.js +++ b/src/api/routes/admin/userDemote.js @@ -20,7 +20,7 @@ class userDemote extends Route { } return res.json({ - message: 'Successfully demoted user' + message: 'Successfully demoted user', }); } } diff --git a/src/api/routes/admin/userDisable.js b/src/api/routes/admin/userDisable.js index e39c811..029e4af 100644 --- a/src/api/routes/admin/userDisable.js +++ b/src/api/routes/admin/userDisable.js @@ -20,7 +20,7 @@ class userDisable extends Route { } return res.json({ - message: 'Successfully disabled user' + message: 'Successfully disabled user', }); } } diff --git a/src/api/routes/admin/userEnable.js b/src/api/routes/admin/userEnable.js index cff622f..aca7a0b 100644 --- a/src/api/routes/admin/userEnable.js +++ b/src/api/routes/admin/userEnable.js @@ -20,7 +20,7 @@ class userEnable extends Route { } return res.json({ - message: 'Successfully enabled user' + message: 'Successfully enabled user', }); } } diff --git a/src/api/routes/admin/userGET.js b/src/api/routes/admin/userGET.js index 14a6c92..30c79f4 100644 --- a/src/api/routes/admin/userGET.js +++ b/src/api/routes/admin/userGET.js @@ -23,7 +23,7 @@ class usersGET extends Route { return res.json({ message: 'Successfully retrieved user', user, - files + files, }); } catch (error) { return super.error(res, error); diff --git a/src/api/routes/admin/userPromote.js b/src/api/routes/admin/userPromote.js index 4a5ed88..3e14cb7 100644 --- a/src/api/routes/admin/userPromote.js +++ b/src/api/routes/admin/userPromote.js @@ -20,7 +20,7 @@ class userPromote extends Route { } return res.json({ - message: 'Successfully promoted user' + message: 'Successfully promoted user', }); } } diff --git a/src/api/routes/admin/userPurge.js b/src/api/routes/admin/userPurge.js index 90f6ec9..8f61ff9 100644 --- a/src/api/routes/admin/userPurge.js +++ b/src/api/routes/admin/userPurge.js @@ -18,7 +18,7 @@ class userDemote extends Route { } return res.json({ - message: 'Successfully deleted the user\'s files' + message: 'Successfully deleted the user\'s files', }); } } diff --git a/src/api/routes/admin/usersGET.js b/src/api/routes/admin/usersGET.js index 52a707f..4e9b954 100644 --- a/src/api/routes/admin/usersGET.js +++ b/src/api/routes/admin/usersGET.js @@ -12,7 +12,7 @@ class usersGET extends Route { return res.json({ message: 'Successfully retrieved users', - users + users, }); } catch (error) { return super.error(res, error); diff --git a/src/api/routes/albums/albumDELETE.js b/src/api/routes/albums/albumDELETE.js index 4e6640e..f9c22e6 100644 --- a/src/api/routes/albums/albumDELETE.js +++ b/src/api/routes/albums/albumDELETE.js @@ -1,5 +1,4 @@ const Route = require('../../structures/Route'); -const Util = require('../../utils/Util'); class albumDELETE extends Route { constructor() { diff --git a/src/api/routes/albums/albumPOST.js b/src/api/routes/albums/albumPOST.js index 841da3d..94ee8a7 100644 --- a/src/api/routes/albums/albumPOST.js +++ b/src/api/routes/albums/albumPOST.js @@ -1,5 +1,5 @@ -const Route = require('../../structures/Route'); const moment = require('moment'); +const Route = require('../../structures/Route'); class albumPOST extends Route { constructor() { @@ -25,7 +25,7 @@ class albumPOST extends Route { name, userId: user.id, createdAt: now, - editedAt: now + editedAt: now, }; const dbRes = await db.table('albums').insert(insertObj); diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index a6ef6fd..bd74ef3 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -1,8 +1,8 @@ +const path = require('path'); +const jetpack = require('fs-jetpack'); const Route = require('../../structures/Route'); const Util = require('../../utils/Util'); const log = require('../../utils/Log'); -const path = require('path'); -const jetpack = require('fs-jetpack'); class albumGET extends Route { constructor() { @@ -21,7 +21,7 @@ class albumGET extends Route { .where({ identifier, enabled: true, - enableDownload: true + enableDownload: true, }) .first(); if (!link) return res.status(400).json({ message: 'The supplied identifier could not be found' }); @@ -64,11 +64,11 @@ class albumGET extends Route { /* Get the actual files */ - const fileIds = fileList.map(el => el.fileId); + const fileIds = fileList.map((el) => el.fileId); const files = await db.table('files') .whereIn('id', fileIds) .select('name'); - const filesToZip = files.map(el => el.name); + const filesToZip = files.map((el) => el.name); try { Util.createZip(filesToZip, album); diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js index b02d0b4..0381b50 100644 --- a/src/api/routes/albums/link/linkDELETE.js +++ b/src/api/routes/albums/link/linkDELETE.js @@ -1,5 +1,4 @@ const Route = require('../../../structures/Route'); -const { dump } = require('dumper.js'); class linkDELETE extends Route { constructor() { @@ -28,7 +27,7 @@ class linkDELETE extends Route { } return res.json({ - message: 'Successfully deleted link' + message: 'Successfully deleted link', }); } } diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index 0c7233b..4e0e0e1 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -1,5 +1,4 @@ const Route = require('../../../structures/Route'); -const log = require('../../../utils/Log'); class linkEditPOST extends Route { constructor() { @@ -23,7 +22,7 @@ class linkEditPOST extends Route { try { const updateObj = { enableDownload: enableDownload || false, - expiresAt // This one should be null if not supplied + expiresAt, // This one should be null if not supplied }; await db .table('links') diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 7ecc5cb..d58598a 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -28,15 +28,13 @@ class linkPOST extends Route { .where('albumId', albumId) .count({ count: 'id' }) .first(); - if (count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10)) - return res.status(400).json({ message: 'Maximum links per album reached' }); + if (count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10)) return res.status(400).json({ message: 'Maximum links per album reached' }); /* Try to allocate a new identifier on the db */ const identifier = await Util.getUniqueAlbumIdentifier(); - if (!identifier) - return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); + if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); try { const insertObj = { @@ -46,13 +44,13 @@ class linkPOST extends Route { enabled: true, enableDownload: true, expiresAt: null, - views: 0 + views: 0, }; await db.table('links').insert(insertObj); return res.json({ message: 'The link was created successfully', - data: insertObj + data: insertObj, }); } catch (error) { return super.error(res, error); diff --git a/src/api/routes/albums/link/linksGET.js b/src/api/routes/albums/link/linksGET.js index edab49a..4487c26 100644 --- a/src/api/routes/albums/link/linksGET.js +++ b/src/api/routes/albums/link/linksGET.js @@ -14,7 +14,7 @@ class linkPOST extends Route { return res.json({ message: 'Successfully retrieved links', - links + links, }); } } diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index 205737a..5c7730c 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -1,7 +1,7 @@ -const Route = require('../../structures/Route'); const bcrypt = require('bcrypt'); const moment = require('moment'); const JWT = require('jsonwebtoken'); +const Route = require('../../structures/Route'); class loginPOST extends Route { constructor() { @@ -36,7 +36,7 @@ class loginPOST extends Route { const jwt = JWT.sign({ iss: 'lolisafe', sub: user.id, - iat: moment.utc().valueOf() + iat: moment.utc().valueOf(), }, process.env.SECRET, { expiresIn: '30d' }); return res.json({ @@ -45,10 +45,10 @@ class loginPOST extends Route { id: user.id, username: user.username, apiKey: user.apiKey, - isAdmin: user.isAdmin + isAdmin: user.isAdmin, }, token: jwt, - apiKey: user.apiKey + apiKey: user.apiKey, }); } } diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index feeb360..e2ac018 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -1,7 +1,7 @@ -const Route = require('../../structures/Route'); -const log = require('../../utils/Log'); const bcrypt = require('bcrypt'); const moment = require('moment'); +const Route = require('../../structures/Route'); +const log = require('../../utils/Log'); class registerPOST extends Route { constructor() { @@ -9,7 +9,7 @@ class registerPOST extends Route { } async run(req, res, db) { - if (process.env.USER_ACCOUNTS == 'false') return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); + if (process.env.USER_ACCOUNTS === 'false') return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { username, password } = req.body; if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' }); @@ -50,7 +50,7 @@ class registerPOST extends Route { createdAt: now, editedAt: now, enabled: true, - isAdmin: false + isAdmin: false, }); return res.json({ message: 'The account was created successfully' }); } diff --git a/src/api/routes/files/filesAlbumsGET.js b/src/api/routes/files/filesAlbumsGET.js index 7f1190c..f5f2f3b 100644 --- a/src/api/routes/files/filesAlbumsGET.js +++ b/src/api/routes/files/filesAlbumsGET.js @@ -18,7 +18,7 @@ class filesGET extends Route { .select('albumId'); if (albumFiles.length) { - albumFiles = albumFiles.map(a => a.albumId); + albumFiles = albumFiles.map((a) => a.albumId); albums = await db.table('albums') .whereIn('id', albumFiles) .select('id', 'name'); @@ -26,7 +26,7 @@ class filesGET extends Route { return res.json({ message: 'Successfully retrieved file albums', - albums + albums, }); } } diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index 9e90633..ce1d788 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -36,7 +36,7 @@ class filesGET extends Route { return res.json({ message: 'Successfully retrieved files', files, - count + count, }); } } diff --git a/src/api/routes/files/tagAddPOST.js b/src/api/routes/files/tagAddPOST.js index 25467ab..07ecb18 100644 --- a/src/api/routes/files/tagAddPOST.js +++ b/src/api/routes/files/tagAddPOST.js @@ -14,7 +14,8 @@ class tagAddPOST extends Route { const file = await db.table('files').where({ id: fileId, userId: user.id }).first(); if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' }); - tagNames.forEach(async tag => { + // eslint-disable-next-line consistent-return + tagNames.forEach(async (tag) => { try { await db.table('fileTags').insert({ fileId, tag }); } catch (error) { @@ -23,7 +24,7 @@ class tagAddPOST extends Route { }); return res.json({ - message: 'Successfully added file to album' + message: 'Successfully added file to album', }); } } diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js index b653066..3c6a2f8 100644 --- a/src/api/routes/service/configGET.js +++ b/src/api/routes/service/configGET.js @@ -15,11 +15,11 @@ class configGET extends Route { maxUploadSize: parseInt(process.env.MAX_SIZE, 10), filenameLength: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10), albumLinkLength: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), - generateThumbnails: process.env.GENERATE_THUMBNAILS == 'true' ? true : false, - generateZips: process.env.GENERATE_ZIPS == 'true' ? true : false, - publicMode: process.env.PUBLIC_MODE == 'true' ? true : false, - enableAccounts: process.env.USER_ACCOUNTS == 'true' ? true : false - } + generateThumbnails: process.env.GENERATE_THUMBNAILS === 'true', + generateZips: process.env.GENERATE_ZIPS === 'true', + publicMode: process.env.PUBLIC_MODE === 'true', + enableAccounts: process.env.USER_ACCOUNTS === 'true', + }, }); } } diff --git a/src/api/routes/tags/tagPOST.js b/src/api/routes/tags/tagPOST.js index b6ec395..856e0d4 100644 --- a/src/api/routes/tags/tagPOST.js +++ b/src/api/routes/tags/tagPOST.js @@ -1,5 +1,5 @@ -const Route = require('../../structures/Route'); const moment = require('moment'); +const Route = require('../../structures/Route'); class tagPOST extends Route { constructor() { @@ -22,7 +22,7 @@ class tagPOST extends Route { name, userId: user.id, createdAt: now, - editedAt: now + editedAt: now, }); return res.json({ message: 'The tag was created successfully' }); diff --git a/src/api/routes/tags/tagsGET.js b/src/api/routes/tags/tagsGET.js index 871148e..848e08d 100644 --- a/src/api/routes/tags/tagsGET.js +++ b/src/api/routes/tags/tagsGET.js @@ -1,5 +1,4 @@ const Route = require('../../structures/Route'); -const Util = require('../../utils/Util'); class tagsGET extends Route { constructor() { @@ -20,7 +19,7 @@ class tagsGET extends Route { return res.json({ message: 'Successfully retrieved tags', - tags + tags, }); } catch (error) { return super.error(res, error); diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js index 013c0d6..a9baf55 100644 --- a/src/api/routes/uploads/chunksPOST.js +++ b/src/api/routes/uploads/chunksPOST.js @@ -1,27 +1,27 @@ -const Route = require('../../structures/Route'); const path = require('path'); -const Util = require('../../utils/Util'); const jetpack = require('fs-jetpack'); const randomstring = require('randomstring'); +const Util = require('../../utils/Util'); +const Route = require('../../structures/Route'); class uploadPOST extends Route { constructor() { super('/upload/chunks', 'post', { bypassAuth: true, - canApiKey: true + canApiKey: true, }); } - async run(req, res, db) { + async run(req, res) { const filename = Util.getUniqueFilename(randomstring.generate(32)); // console.log('Files', req.body.files); const info = { size: req.body.files[0].size, - url: `${process.env.DOMAIN}/` + url: `${process.env.DOMAIN}/`, }; for (const chunk of req.body.files) { - const { uuid, count } = chunk; + const { uuid } = chunk; // console.log('Chunk', chunk); const chunkOutput = path.join(__dirname, @@ -65,7 +65,7 @@ class uploadPOST extends Route { return res.status(201).send({ message: 'Sucessfully merged the chunk(s).', - ...info + ...info, /* name: `${filename}${ext || ''}`, size: exists.size, diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 6c01dd3..48fc592 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -1,17 +1,18 @@ -const Route = require('../../structures/Route'); const path = require('path'); -const Util = require('../../utils/Util'); const jetpack = require('fs-jetpack'); const multer = require('multer'); const moment = require('moment'); +const Util = require('../../utils/Util'); +const Route = require('../../structures/Route'); + const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000), - files: 1 + files: 1, }, - fileFilter: (req, file, cb) => { - // TODO: Enable blacklisting of files/extensions + fileFilter: (req, file, cb) => + // TODO: Enable blacklisting of files/extensions /* if (options.blacklist.mimes.includes(file.mimetype)) { return cb(new Error(`${file.mimetype} is a blacklisted filetype.`)); @@ -19,35 +20,34 @@ const upload = multer({ return cb(new Error(`${path.extname(file.originalname).toLowerCase()} is a blacklisted extension.`)); } */ - return cb(null, true); - } + cb(null, true) + , }).array('files[]'); /* TODO: If source has transparency generate a png thumbnail, otherwise a jpg. TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover on the frontend. - TODO: If source is a video, generate a thumb of the first frame and save the video length to the file? - Another possible solution would be to play a gif on hover that grabs a few chunks like youtube. TODO: Think if its worth making a folder with the user uuid in uploads/ and upload the pictures there so that this way at least not every single file will be in 1 directory - - Addendum to this: Now that the default behaviour is to serve files with node, we can actually pull this off. Before this, having files in - subfolders meant messing with nginx and the paths, but now it should be fairly easy to re-arrange the folder structure with express.static - I see great value in this, open to suggestions. + XXX: Now that the default behaviour is to serve files with node, we can actually pull this off. + Before this, having files in subfolders meant messing with nginx and the paths, + but now it should be fairly easy to re-arrange the folder structure with express.static + I see great value in this, open to suggestions. */ class uploadPOST extends Route { constructor() { super('/upload', 'post', { bypassAuth: true, - canApiKey: true + canApiKey: true, }); } async run(req, res, db) { const user = await Util.isAuthorized(req); - if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); + if (!user && process.env.PUBLIC_MODE === 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); const albumId = req.body.albumid || req.headers.albumid; if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); @@ -56,12 +56,13 @@ class uploadPOST extends Route { if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); } - return upload(req, res, async err => { + return upload(req, res, async (err) => { if (err) console.error(err.message); let uploadedFile = {}; let insertedId; + // eslint-disable-next-line no-underscore-dangle const remappedKeys = this._remapKeys(req.body); const file = req.files[0]; @@ -105,7 +106,7 @@ class uploadPOST extends Route { name: filename, hash, size: file.buffer.length, - url: filename + url: filename, }; } @@ -124,7 +125,7 @@ class uploadPOST extends Route { return res.status(201).send({ message: 'Sucessfully uploaded the file.', - ...uploadedFile + ...uploadedFile, }); }); } @@ -137,7 +138,7 @@ class uploadPOST extends Route { size: exists.size, url: `${process.env.DOMAIN}/${exists.name}`, deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`, - repeated: true + repeated: true, }); return Util.deleteFile(filename); @@ -145,7 +146,7 @@ class uploadPOST extends Route { async checkIfFileExists(db, user, hash) { const exists = await db.table('files') - .where(function() { // eslint-disable-line func-names + .where(function () { // eslint-disable-line func-names if (user) this.where('userId', user.id); else this.whereNull('userId'); }) @@ -186,7 +187,7 @@ class uploadPOST extends Route { hash: file.hash, ip: req.ip, createdAt: now, - editedAt: now + editedAt: now, }); } else { insertedId = await db.table('files').insert({ @@ -198,7 +199,7 @@ class uploadPOST extends Route { hash: file.hash, ip: req.ip, createdAt: now, - editedAt: now + editedAt: now, }, 'id'); } return insertedId; @@ -220,6 +221,7 @@ class uploadPOST extends Route { } return body; } + return keys; } } diff --git a/src/api/routes/user/apiKey.js b/src/api/routes/user/apiKey.js index a87d98d..a63f0c0 100644 --- a/src/api/routes/user/apiKey.js +++ b/src/api/routes/user/apiKey.js @@ -1,7 +1,7 @@ -const Route = require('../../structures/Route'); const randomstring = require('randomstring'); const moment = require('moment'); const { dump } = require('dumper.js'); +const Route = require('../../structures/Route'); class apiKeyPOST extends Route { constructor() { @@ -17,7 +17,7 @@ class apiKeyPOST extends Route { .where({ id: user.id }) .update({ apiKey, - apiKeyEditedAt: now + apiKeyEditedAt: now, }); } catch (error) { dump(error); @@ -26,7 +26,7 @@ class apiKeyPOST extends Route { return res.json({ message: 'Successfully created new api key', - apiKey + apiKey, }); } } diff --git a/src/api/routes/user/changePasswordPOST.js b/src/api/routes/user/changePasswordPOST.js index 9cd621e..1b3a27a 100644 --- a/src/api/routes/user/changePasswordPOST.js +++ b/src/api/routes/user/changePasswordPOST.js @@ -1,7 +1,7 @@ -const Route = require('../../structures/Route'); -const log = require('../../utils/Log'); const bcrypt = require('bcrypt'); const moment = require('moment'); +const Route = require('../../structures/Route'); +const log = require('../../utils/Log'); class changePasswordPOST extends Route { constructor() { @@ -36,7 +36,7 @@ class changePasswordPOST extends Route { const now = moment.utc().toDate(); await db.table('users').where('id', user.id).update({ password: hash, - passwordEditedAt: now + passwordEditedAt: now, }); return res.json({ message: 'The password was changed successfully' }); diff --git a/src/api/routes/user/userGET.js b/src/api/routes/user/userGET.js index 7929aac..6f179a9 100644 --- a/src/api/routes/user/userGET.js +++ b/src/api/routes/user/userGET.js @@ -12,8 +12,8 @@ class usersGET extends Route { id: user.id, username: user.username, isAdmin: user.isAdmin, - apiKey: user.apiKey - } + apiKey: user.apiKey, + }, }); } } diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js index 2f370e8..107c20a 100644 --- a/src/api/routes/verifyGET.js +++ b/src/api/routes/verifyGET.js @@ -11,8 +11,8 @@ class verifyGET extends Route { user: { id: user.id, username: user.username, - isAdmin: user.isAdmin - } + isAdmin: user.isAdmin, + }, }); } } -- cgit v1.2.3 From 6713eca9d4a4887dc8d7416dbdd8ec37de7bb2ed Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 8 Jul 2020 19:22:25 +0300 Subject: chore: add unique integrity checks to the database for many-to-many tables --- src/api/routes/uploads/uploadPOST.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 48fc592..99f5ee5 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -84,10 +84,7 @@ class uploadPOST extends Route { if (remappedKeys && remappedKeys.uuid) { const chunkOutput = path.join(__dirname, - '..', - '..', - '..', - '..', + '../../../../', process.env.UPLOAD_FOLDER, 'chunks', remappedKeys.uuid, @@ -95,10 +92,7 @@ class uploadPOST extends Route { await jetpack.writeAsync(chunkOutput, file.buffer); } else { const output = path.join(__dirname, - '..', - '..', - '..', - '..', + '../../../../', process.env.UPLOAD_FOLDER, filename); await jetpack.writeAsync(output, file.buffer); -- cgit v1.2.3 From 746a4546122be2ed79ad5858de6ce2c686f78ef0 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 9 Jul 2020 02:22:08 +0300 Subject: fix: stop leaking user's password and their apikey to admins --- src/api/routes/admin/userGET.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/admin/userGET.js b/src/api/routes/admin/userGET.js index 30c79f4..2fb80d1 100644 --- a/src/api/routes/admin/userGET.js +++ b/src/api/routes/admin/userGET.js @@ -11,7 +11,10 @@ class usersGET extends Route { if (!id) return res.status(400).json({ message: 'Invalid user ID supplied' }); try { - const user = await db.table('users').where({ id }).first(); + const user = await db.table('users') + .select('id, username, enabled, createdAt, editeadAt, apiKeyEditedAt, isAdmin') + .where({ id }) + .first(); const files = await db.table('files') .where({ userId: user.id }) .orderBy('id', 'desc'); -- cgit v1.2.3 From 7e78a03931173437cd4aec5454663ee3cc3aee23 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Fri, 10 Jul 2020 01:13:23 +0300 Subject: fix: stop leaking user passwords to admins AGAIN --- src/api/routes/admin/fileGET.js | 5 ++++- src/api/routes/admin/userGET.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/admin/fileGET.js b/src/api/routes/admin/fileGET.js index 0d1b147..239b128 100644 --- a/src/api/routes/admin/fileGET.js +++ b/src/api/routes/admin/fileGET.js @@ -11,7 +11,10 @@ class filesGET extends Route { if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); let file = await db.table('files').where({ id }).first(); - const user = await db.table('users').where({ id: file.userId }).first(); + const user = await db.table('users') + .select('id', 'username', 'enabled', 'createdAt', 'editedAt', 'apiKeyEditedAt', 'isAdmin') + .where({ id: file.userId }) + .first(); file = Util.constructFilePublicLink(file); // Additional relevant data diff --git a/src/api/routes/admin/userGET.js b/src/api/routes/admin/userGET.js index 2fb80d1..f5f2508 100644 --- a/src/api/routes/admin/userGET.js +++ b/src/api/routes/admin/userGET.js @@ -12,7 +12,7 @@ class usersGET extends Route { try { const user = await db.table('users') - .select('id, username, enabled, createdAt, editeadAt, apiKeyEditedAt, isAdmin') + .select('id', 'username', 'enabled', 'createdAt', 'editedAt', 'apiKeyEditedAt', 'isAdmin') .where({ id }) .first(); const files = await db.table('files') -- cgit v1.2.3 From a057f26896d98e8a99819b5de62c79cccb2ec023 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 18 Jul 2020 03:23:59 +0900 Subject: Fix url retrieval of uploaded file --- src/api/routes/uploads/uploadPOST.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/api/routes') diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 6c01dd3..4b84da6 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -122,6 +122,7 @@ class uploadPOST extends Route { this.saveFileToAlbum(db, albumId, insertedId); } + uploadedFile = Util.constructFilePublicLink(uploadedFile); return res.status(201).send({ message: 'Sucessfully uploaded the file.', ...uploadedFile @@ -130,6 +131,7 @@ class uploadPOST extends Route { } fileExists(res, exists, filename) { + exists = Util.constructFilePublicLink(exists); res.json({ message: 'Successfully uploaded the file.', name: exists.name, -- cgit v1.2.3 From 8ffa0ba075a9d2b7b7409f9d11581a5237e7fd89 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 18 Jul 2020 04:31:12 +0900 Subject: Add endpoint with version of the API --- src/api/routes/service/versionGET.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/api/routes/service/versionGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/service/versionGET.js b/src/api/routes/service/versionGET.js new file mode 100644 index 0000000..dfb994a --- /dev/null +++ b/src/api/routes/service/versionGET.js @@ -0,0 +1,15 @@ +const Route = require('../../structures/Route'); + +class versionGET extends Route { + constructor() { + super('/version', 'get', { bypassAuth: true }); + } + + run(req, res) { + return res.json({ + version: process.env.npm_package_version + }); + } +} + +module.exports = versionGET; -- cgit v1.2.3 From c93ddb09008c45942544b13bbb03319c367f9cd8 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Sun, 19 Jul 2020 22:27:11 +0300 Subject: feat: Start working on a new album/tags/image info modal --- src/api/routes/admin/fileGET.js | 2 +- src/api/routes/files/fileGET.js | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/api/routes/files/fileGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/admin/fileGET.js b/src/api/routes/admin/fileGET.js index 239b128..7e40659 100644 --- a/src/api/routes/admin/fileGET.js +++ b/src/api/routes/admin/fileGET.js @@ -3,7 +3,7 @@ const Util = require('../../utils/Util'); class filesGET extends Route { constructor() { - super('/file/:id', 'get', { adminOnly: true }); + super('/admin/file/:id', 'get', { adminOnly: true }); } async run(req, res, db) { diff --git a/src/api/routes/files/fileGET.js b/src/api/routes/files/fileGET.js new file mode 100644 index 0000000..e9ce90e --- /dev/null +++ b/src/api/routes/files/fileGET.js @@ -0,0 +1,46 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class fileGET extends Route { + constructor() { + super('/file/:id', 'get'); + } + + async run(req, res, db, user) { + const { id } = req.params; + if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' }); + + /* + Make sure the file exists + */ + let file = await db.table('files').where({ id, userId: user.id }).first(); + if (!file) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); + + file = Util.constructFilePublicLink(file); + + /* + Fetch the albums + */ + const albums = await db.table('albumsFiles') + .where('fileId', id) + .join('albums', 'albums.id', 'albumsFiles.albumId') + .select('albums.id', 'albums.name'); + + /* + Fetch the tags + */ + const tags = await db.table('fileTags') + .where('fileId', id) + .join('tags', 'tags.id', 'fileTags.id') + .select('tags.id', 'tags.uuid', 'tags.name'); + + return res.json({ + message: 'Successfully retrieved file', + file, + albums, + tags, + }); + } +} + +module.exports = fileGET; -- cgit v1.2.3 From a891cbc1fc58b5d12bdcc56bc72a790447df9891 Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 20 Jul 2020 09:17:13 +0900 Subject: Enable deleting files with the API key --- src/api/routes/files/fileDELETE.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/fileDELETE.js b/src/api/routes/files/fileDELETE.js index c659255..e467601 100644 --- a/src/api/routes/files/fileDELETE.js +++ b/src/api/routes/files/fileDELETE.js @@ -4,7 +4,7 @@ const log = require('../../utils/Log'); class fileDELETE extends Route { constructor() { - super('/file/:id', 'delete'); + super('/file/:id', 'delete', { canApiKey: true }); } async run(req, res, db, user) { -- cgit v1.2.3 From 04660dbce11ff42be2fb02cb93acdbd9b99ad8a0 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Mon, 20 Jul 2020 21:28:46 +0300 Subject: feat: add single tag adding to file fix: fix the batch tag adding to properly await for a response, and use the proper column while adding the tags --- src/api/routes/files/tagAddBatchPOST.js | 40 +++++++++++++++++++++++++++++++++ src/api/routes/files/tagAddPOST.js | 26 ++++++++++++--------- 2 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 src/api/routes/files/tagAddBatchPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/files/tagAddBatchPOST.js b/src/api/routes/files/tagAddBatchPOST.js new file mode 100644 index 0000000..67b1b46 --- /dev/null +++ b/src/api/routes/files/tagAddBatchPOST.js @@ -0,0 +1,40 @@ +const Route = require('../../structures/Route'); + +class tagAddPOST extends Route { + constructor() { + super('/file/tag/addBatch', 'post'); + } + + async run(req, res, db, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { fileId, tagNames } = req.body; + if (!fileId || !tagNames.length) return res.status(400).json({ message: 'No tags provided' }); + + // Make sure the file belongs to the user + const file = await db.table('files').where({ id: fileId, userId: user.id }).first(); + if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' }); + + const errors = {}; + const addedTags = []; + for await (const tagName of tagNames) { + try { + const tag = await db.table('tags').where({ name: tagName, userId: user.id }).first(); + if (!tag) throw new Error('Tag doesn\'t exist in the database'); + await db.table('fileTags').insert({ fileId, tagId: tag.id }); + + addedTags.push(tag); + } catch (e) { + errors[tagName] = e.message; + } + } + + return res.json({ + message: 'Successfully added tags to file', + data: addedTags, + errors, + }); + // eslint-disable-next-line consistent-return + } +} + +module.exports = tagAddPOST; diff --git a/src/api/routes/files/tagAddPOST.js b/src/api/routes/files/tagAddPOST.js index 07ecb18..3434f24 100644 --- a/src/api/routes/files/tagAddPOST.js +++ b/src/api/routes/files/tagAddPOST.js @@ -7,25 +7,29 @@ class tagAddPOST extends Route { async run(req, res, db, user) { if (!req.body) return res.status(400).json({ message: 'No body provided' }); - const { fileId, tagNames } = req.body; - if (!fileId || !tagNames.length) return res.status(400).json({ message: 'No tags provided' }); + + const { fileId, tagName } = req.body; + if (!fileId || !tagName.length) return res.status(400).json({ message: 'No tag provided' }); // Make sure the file belongs to the user const file = await db.table('files').where({ id: fileId, userId: user.id }).first(); if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' }); - // eslint-disable-next-line consistent-return - tagNames.forEach(async (tag) => { - try { - await db.table('fileTags').insert({ fileId, tag }); - } catch (error) { - return super.error(res, error); - } - }); + // Make sure user has a tag like that + const tag = await db.table('tags').where({ name: tagName, userId: user.id }).first(); + if (!tag) return res.status(400).json({ message: 'Tag doesn\'t exist. ' }); + + try { + await db.table('fileTags').insert({ fileId, tagId: tag.id }); + } catch (error) { + return super.error(res, error); + } return res.json({ - message: 'Successfully added file to album', + message: 'Successfully added tag to file', + data: tagName, }); + // eslint-disable-next-line consistent-return } } -- cgit v1.2.3 From c5b165b4953e910d6af71636604c1cdef7467a76 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Mon, 20 Jul 2020 21:43:23 +0300 Subject: fix: join tags by the proper key --- src/api/routes/files/fileGET.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/fileGET.js b/src/api/routes/files/fileGET.js index e9ce90e..0a6f2de 100644 --- a/src/api/routes/files/fileGET.js +++ b/src/api/routes/files/fileGET.js @@ -31,7 +31,7 @@ class fileGET extends Route { */ const tags = await db.table('fileTags') .where('fileId', id) - .join('tags', 'tags.id', 'fileTags.id') + .join('tags', 'tags.id', 'fileTags.tagId') .select('tags.id', 'tags.uuid', 'tags.name'); return res.json({ -- cgit v1.2.3 From 9de50b26f1868217a547737741863c7ee6e760b8 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Mon, 20 Jul 2020 22:40:00 +0300 Subject: feat: add tag deletion from images --- src/api/routes/files/tagDelPOST.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/api/routes/files/tagDelPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/files/tagDelPOST.js b/src/api/routes/files/tagDelPOST.js new file mode 100644 index 0000000..4d45493 --- /dev/null +++ b/src/api/routes/files/tagDelPOST.js @@ -0,0 +1,38 @@ +const Route = require('../../structures/Route'); + +class tagDelPost extends Route { + constructor() { + super('/file/tag/del', 'post'); + } + + async run(req, res, db, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + + const { fileId, tagName } = req.body; + if (!fileId || !tagName.length) return res.status(400).json({ message: 'No tag provided' }); + + // Make sure the file belongs to the user + const file = await db.table('files').where({ id: fileId, userId: user.id }).first(); + if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' }); + + // Make sure user has a tag like that + const tag = await db.table('tags').where({ name: tagName, userId: user.id }).first(); + if (!tag) return res.status(400).json({ message: 'Tag doesn\'t exist. ' }); + + try { + await db.table('fileTags') + .where({ fileId, tagId: tag.id }) + .delete(); + } catch (error) { + return super.error(res, error); + } + + return res.json({ + message: 'Successfully removed tag from file', + data: { fileId, tag }, + }); + // eslint-disable-next-line consistent-return + } +} + +module.exports = tagDelPost; -- cgit v1.2.3 From fe314a742f14f55883a0fcc8deeca6a918a5ccd6 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Mon, 20 Jul 2020 22:40:31 +0300 Subject: fix: return the edited/changed/delete entity from API --- src/api/routes/files/tagAddBatchPOST.js | 6 +++--- src/api/routes/files/tagAddPOST.js | 2 +- src/api/routes/tags/tagDELETE.js | 2 +- src/api/routes/tags/tagPOST.js | 10 +++++++--- 4 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/files/tagAddBatchPOST.js b/src/api/routes/files/tagAddBatchPOST.js index 67b1b46..5091a81 100644 --- a/src/api/routes/files/tagAddBatchPOST.js +++ b/src/api/routes/files/tagAddBatchPOST.js @@ -1,6 +1,6 @@ const Route = require('../../structures/Route'); -class tagAddPOST extends Route { +class tagAddBatchPOST extends Route { constructor() { super('/file/tag/addBatch', 'post'); } @@ -30,11 +30,11 @@ class tagAddPOST extends Route { return res.json({ message: 'Successfully added tags to file', - data: addedTags, + data: { fileId, tags: addedTags }, errors, }); // eslint-disable-next-line consistent-return } } -module.exports = tagAddPOST; +module.exports = tagAddBatchPOST; diff --git a/src/api/routes/files/tagAddPOST.js b/src/api/routes/files/tagAddPOST.js index 3434f24..654dceb 100644 --- a/src/api/routes/files/tagAddPOST.js +++ b/src/api/routes/files/tagAddPOST.js @@ -27,7 +27,7 @@ class tagAddPOST extends Route { return res.json({ message: 'Successfully added tag to file', - data: tagName, + data: { fileId, tag }, }); // eslint-disable-next-line consistent-return } diff --git a/src/api/routes/tags/tagDELETE.js b/src/api/routes/tags/tagDELETE.js index c03ca64..cf74029 100644 --- a/src/api/routes/tags/tagDELETE.js +++ b/src/api/routes/tags/tagDELETE.js @@ -27,7 +27,7 @@ class tagDELETE extends Route { Delete the tag */ await db.table('tags').where({ id }).delete(); - return res.json({ message: 'The tag was deleted successfully' }); + return res.json({ message: 'The tag was deleted successfully', data: tag }); } catch (error) { return super.error(res, error); } diff --git a/src/api/routes/tags/tagPOST.js b/src/api/routes/tags/tagPOST.js index 856e0d4..5038b91 100644 --- a/src/api/routes/tags/tagPOST.js +++ b/src/api/routes/tags/tagPOST.js @@ -18,14 +18,18 @@ class tagPOST extends Route { if (tag) return res.status(401).json({ message: 'There\'s already a tag with that name' }); const now = moment.utc().toDate(); - await db.table('tags').insert({ + const insertObj = { name, userId: user.id, createdAt: now, editedAt: now, - }); + }; - return res.json({ message: 'The tag was created successfully' }); + const dbRes = await db.table('tags').insert(insertObj); + + insertObj.id = dbRes.pop(); + + return res.json({ message: 'The tag was created successfully', data: insertObj }); } } -- cgit v1.2.3 From 78c6fa14e61f518906521b8cd7c6f81da67dbb8d Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 22 Jul 2020 02:11:05 +0300 Subject: feat: add experimental search parser --- src/api/routes/search/searchGET.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/api/routes/search/searchGET.js (limited to 'src/api/routes') diff --git a/src/api/routes/search/searchGET.js b/src/api/routes/search/searchGET.js new file mode 100644 index 0000000..ae73d27 --- /dev/null +++ b/src/api/routes/search/searchGET.js @@ -0,0 +1,25 @@ +const searchQuery = require('search-query-parser'); +const chrono = require('chrono-node'); +const Route = require('../../structures/Route'); + +const options = { keywords: ['album', 'tag', 'user', 'before', 'after'], offsets: false }; +class configGET extends Route { + constructor() { + super('/search/:q', 'get', { bypassAuth: true }); + } + + run(req, res) { + const { q } = req.params; + const parsed = searchQuery.parse(q, options); + + if (parsed.before) { + parsed.before = chrono.parse(parsed.before); + } + if (parsed.after) { + parsed.after = chrono.parse(parsed.after); + } + return res.json(parsed); + } +} + +module.exports = configGET; -- cgit v1.2.3 From c88d08330f239a897e9f24cb32b25759680619b8 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 23 Jul 2020 04:09:01 +0300 Subject: feat: add experimental query to sql generator for searching --- src/api/routes/search/searchGET.js | 58 +++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 10 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/search/searchGET.js b/src/api/routes/search/searchGET.js index ae73d27..b8757fa 100644 --- a/src/api/routes/search/searchGET.js +++ b/src/api/routes/search/searchGET.js @@ -1,24 +1,62 @@ const searchQuery = require('search-query-parser'); -const chrono = require('chrono-node'); + const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +const queryHelper = require('../../utils/QueryHelper'); + +const options = { + keywords: ['album', 'tag', 'before', 'after', 'file'], + offsets: false, + alwaysArray: true, + tokenize: true, +}; -const options = { keywords: ['album', 'tag', 'user', 'before', 'after'], offsets: false }; class configGET extends Route { constructor() { - super('/search/:q', 'get', { bypassAuth: true }); + super('/search/', 'get'); } - run(req, res) { - const { q } = req.params; + async run(req, res, db, user) { + let count = 0; + + const { q } = req.query; const parsed = searchQuery.parse(q, options); - if (parsed.before) { - parsed.before = chrono.parse(parsed.before); + let files = db.table('files') + .select('*') + .where({ 'files.userId': user.id }) + .orderBy('files.createdAt', 'desc'); + + files = queryHelper.processQuery(db, files, parsed); + + const query = files.toString(); + const { page, limit = 100 } = req.query; + + if (page && page >= 0) { + let dbRes = files.clone(); // clone the query to attach a count to it later on + files = await files.offset((page - 1) * limit).limit(limit); + + dbRes = await dbRes.count('* as count').first(); + + count = dbRes.count; + } else { + files = await files; // execute the query + count = files.length; } - if (parsed.after) { - parsed.after = chrono.parse(parsed.after); + + // For each file, create the public link to be able to display the file + for (let file of files) { + file = Util.constructFilePublicLink(file); } - return res.json(parsed); + + return res.json({ + message: 'Successfully retrieved files', + query, + parsed, + files, + count, + }); } } -- cgit v1.2.3 From 151c360740aac5733759888220d91a1d3713b6e1 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Fri, 2 Oct 2020 22:16:34 +0300 Subject: feat: allow administrators to create custom links for albums --- src/api/routes/albums/link/linkPOST.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index d58598a..ba247b5 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -30,11 +30,28 @@ class linkPOST extends Route { .first(); if (count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10)) return res.status(400).json({ message: 'Maximum links per album reached' }); - /* - Try to allocate a new identifier on the db - */ - const identifier = await Util.getUniqueAlbumIdentifier(); - if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); + let { identifier } = req.body; + if (identifier) { + if (!user.isAdmin) return res.status(401).json({ message: 'Only administrators can create custom links' }); + + if (!(/^[a-zA-Z0-9-_]+$/.test(identifier))) return res.status(400).json({ message: 'Only alphanumeric, dashes, and underscore characters are allowed' }); + + /* + Make sure that the id doesn't already exists in the database + */ + const idExists = await db + .table('links') + .where({ identifier }) + .first(); + + if (idExists) return res.status(400).json({ message: 'Album with this identifier already exists' }); + } else { + /* + Try to allocate a new identifier in the database + */ + identifier = await Util.getUniqueAlbumIdentifier(); + if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' }); + } try { const insertObj = { -- cgit v1.2.3 From 13825ddae6f41fdf2697f451cff6c8af0240c2e8 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 24 Dec 2020 10:21:19 +0200 Subject: chore: update lock files --- src/api/routes/uploads/chunksPOST.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js index a9baf55..789a5e7 100644 --- a/src/api/routes/uploads/chunksPOST.js +++ b/src/api/routes/uploads/chunksPOST.js @@ -25,20 +25,14 @@ class uploadPOST extends Route { // console.log('Chunk', chunk); const chunkOutput = path.join(__dirname, - '..', - '..', - '..', - '..', + '../../../../', process.env.UPLOAD_FOLDER, 'chunks', uuid); const chunkDir = await jetpack.list(chunkOutput); const ext = path.extname(chunkDir[0]); const output = path.join(__dirname, - '..', - '..', - '..', - '..', + '../../../../', process.env.UPLOAD_FOLDER, `${filename}${ext || ''}`); chunkDir.sort(); @@ -49,10 +43,7 @@ class uploadPOST extends Route { for (let i = 0; i < chunkDir.length; i++) { const dir = path.join(__dirname, - '..', - '..', - '..', - '..', + '../../../../', process.env.UPLOAD_FOLDER, 'chunks', uuid, -- cgit v1.2.3 From 90001c2df56d58e69fd199a518ae7f3e4ed327fc Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 24 Dec 2020 10:40:50 +0200 Subject: chore: remove trailing commas --- src/api/routes/admin/banIP.js | 2 +- src/api/routes/admin/fileGET.js | 2 +- src/api/routes/admin/unBanIP.js | 2 +- src/api/routes/admin/userDemote.js | 2 +- src/api/routes/admin/userDisable.js | 2 +- src/api/routes/admin/userEnable.js | 2 +- src/api/routes/admin/userGET.js | 2 +- src/api/routes/admin/userPromote.js | 2 +- src/api/routes/admin/userPurge.js | 2 +- src/api/routes/admin/usersGET.js | 2 +- src/api/routes/albums/albumFullGET.js | 2 +- src/api/routes/albums/albumGET.js | 2 +- src/api/routes/albums/albumPOST.js | 2 +- src/api/routes/albums/albumZipGET.js | 6 +++--- src/api/routes/albums/albumsGET.js | 4 ++-- src/api/routes/albums/link/linkDELETE.js | 2 +- src/api/routes/albums/link/linkEditPOST.js | 2 +- src/api/routes/albums/link/linkPOST.js | 4 ++-- src/api/routes/albums/link/linksGET.js | 2 +- src/api/routes/auth/loginPOST.js | 6 +++--- src/api/routes/auth/registerPOST.js | 2 +- src/api/routes/files/albumAddPOST.js | 2 +- src/api/routes/files/albumDelPOST.js | 2 +- src/api/routes/files/fileGET.js | 2 +- src/api/routes/files/filesAlbumsGET.js | 2 +- src/api/routes/files/filesGET.js | 2 +- src/api/routes/files/tagAddBatchPOST.js | 2 +- src/api/routes/files/tagAddPOST.js | 2 +- src/api/routes/files/tagDelPOST.js | 2 +- src/api/routes/search/searchGET.js | 4 ++-- src/api/routes/service/configGET.js | 4 ++-- src/api/routes/tags/tagPOST.js | 2 +- src/api/routes/tags/tagsGET.js | 2 +- src/api/routes/uploads/chunksPOST.js | 6 +++--- src/api/routes/uploads/uploadPOST.js | 16 ++++++++-------- src/api/routes/user/apiKey.js | 4 ++-- src/api/routes/user/changePasswordPOST.js | 2 +- src/api/routes/user/userGET.js | 4 ++-- src/api/routes/verifyGET.js | 4 ++-- 39 files changed, 59 insertions(+), 59 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/admin/banIP.js b/src/api/routes/admin/banIP.js index 4dfe03c..692880d 100644 --- a/src/api/routes/admin/banIP.js +++ b/src/api/routes/admin/banIP.js @@ -17,7 +17,7 @@ class banIP extends Route { } return res.json({ - message: 'Successfully banned the ip', + message: 'Successfully banned the ip' }); } } diff --git a/src/api/routes/admin/fileGET.js b/src/api/routes/admin/fileGET.js index 7e40659..9605da4 100644 --- a/src/api/routes/admin/fileGET.js +++ b/src/api/routes/admin/fileGET.js @@ -24,7 +24,7 @@ class filesGET extends Route { return res.json({ message: 'Successfully retrieved file', file, - user, + user }); } } diff --git a/src/api/routes/admin/unBanIP.js b/src/api/routes/admin/unBanIP.js index 725468c..493834b 100644 --- a/src/api/routes/admin/unBanIP.js +++ b/src/api/routes/admin/unBanIP.js @@ -19,7 +19,7 @@ class unBanIP extends Route { } return res.json({ - message: 'Successfully unbanned the ip', + message: 'Successfully unbanned the ip' }); } } diff --git a/src/api/routes/admin/userDemote.js b/src/api/routes/admin/userDemote.js index 3f6623d..b430a48 100644 --- a/src/api/routes/admin/userDemote.js +++ b/src/api/routes/admin/userDemote.js @@ -20,7 +20,7 @@ class userDemote extends Route { } return res.json({ - message: 'Successfully demoted user', + message: 'Successfully demoted user' }); } } diff --git a/src/api/routes/admin/userDisable.js b/src/api/routes/admin/userDisable.js index 029e4af..e39c811 100644 --- a/src/api/routes/admin/userDisable.js +++ b/src/api/routes/admin/userDisable.js @@ -20,7 +20,7 @@ class userDisable extends Route { } return res.json({ - message: 'Successfully disabled user', + message: 'Successfully disabled user' }); } } diff --git a/src/api/routes/admin/userEnable.js b/src/api/routes/admin/userEnable.js index aca7a0b..cff622f 100644 --- a/src/api/routes/admin/userEnable.js +++ b/src/api/routes/admin/userEnable.js @@ -20,7 +20,7 @@ class userEnable extends Route { } return res.json({ - message: 'Successfully enabled user', + message: 'Successfully enabled user' }); } } diff --git a/src/api/routes/admin/userGET.js b/src/api/routes/admin/userGET.js index f5f2508..48c6e9b 100644 --- a/src/api/routes/admin/userGET.js +++ b/src/api/routes/admin/userGET.js @@ -26,7 +26,7 @@ class usersGET extends Route { return res.json({ message: 'Successfully retrieved user', user, - files, + files }); } catch (error) { return super.error(res, error); diff --git a/src/api/routes/admin/userPromote.js b/src/api/routes/admin/userPromote.js index 3e14cb7..4a5ed88 100644 --- a/src/api/routes/admin/userPromote.js +++ b/src/api/routes/admin/userPromote.js @@ -20,7 +20,7 @@ class userPromote extends Route { } return res.json({ - message: 'Successfully promoted user', + message: 'Successfully promoted user' }); } } diff --git a/src/api/routes/admin/userPurge.js b/src/api/routes/admin/userPurge.js index 8f61ff9..90f6ec9 100644 --- a/src/api/routes/admin/userPurge.js +++ b/src/api/routes/admin/userPurge.js @@ -18,7 +18,7 @@ class userDemote extends Route { } return res.json({ - message: 'Successfully deleted the user\'s files', + message: 'Successfully deleted the user\'s files' }); } } diff --git a/src/api/routes/admin/usersGET.js b/src/api/routes/admin/usersGET.js index 4e9b954..52a707f 100644 --- a/src/api/routes/admin/usersGET.js +++ b/src/api/routes/admin/usersGET.js @@ -12,7 +12,7 @@ class usersGET extends Route { return res.json({ message: 'Successfully retrieved users', - users, + users }); } catch (error) { return super.error(res, error); diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js index 2c3a790..d25fe15 100644 --- a/src/api/routes/albums/albumFullGET.js +++ b/src/api/routes/albums/albumFullGET.js @@ -50,7 +50,7 @@ class albumGET extends Route { message: 'Successfully retrieved album', name: album.name, files, - count, + count }); } } diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index 81edc95..950a1fd 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -37,7 +37,7 @@ class albumGET extends Route { message: 'Successfully retrieved files', name: album.name, downloadEnabled: link.enableDownload, - files, + files }); } } diff --git a/src/api/routes/albums/albumPOST.js b/src/api/routes/albums/albumPOST.js index 94ee8a7..52352a1 100644 --- a/src/api/routes/albums/albumPOST.js +++ b/src/api/routes/albums/albumPOST.js @@ -25,7 +25,7 @@ class albumPOST extends Route { name, userId: user.id, createdAt: now, - editedAt: now, + editedAt: now }; const dbRes = await db.table('albums').insert(insertObj); diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index bd74ef3..cf1f6f8 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -21,7 +21,7 @@ class albumGET extends Route { .where({ identifier, enabled: true, - enableDownload: true, + enableDownload: true }) .first(); if (!link) return res.status(400).json({ message: 'The supplied identifier could not be found' }); @@ -38,7 +38,7 @@ class albumGET extends Route { If the date when the album was zipped is greater than the album's last edit, we just send the zip to the user */ if (album.zippedAt > album.editedAt) { - const filePath = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); + const filePath = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); const exists = await jetpack.existsAsync(filePath); /* Make sure the file exists just in case, and if not, continue to it's generation. @@ -76,7 +76,7 @@ class albumGET extends Route { .where('id', link.albumId) .update('zippedAt', db.fn.now()); - const filePath = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); + const filePath = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); const fileName = `lolisafe-${identifier}.zip`; return res.download(filePath, fileName); } catch (error) { diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index c9ab025..93a23e3 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -46,7 +46,7 @@ class albumsGET extends Route { return res.json({ message: 'Successfully retrieved albums', - albums, + albums }); } } @@ -63,7 +63,7 @@ class albumsDropdownGET extends Route { .select('id', 'name'); return res.json({ message: 'Successfully retrieved albums', - albums, + albums }); } } diff --git a/src/api/routes/albums/link/linkDELETE.js b/src/api/routes/albums/link/linkDELETE.js index 0381b50..1af704e 100644 --- a/src/api/routes/albums/link/linkDELETE.js +++ b/src/api/routes/albums/link/linkDELETE.js @@ -27,7 +27,7 @@ class linkDELETE extends Route { } return res.json({ - message: 'Successfully deleted link', + message: 'Successfully deleted link' }); } } diff --git a/src/api/routes/albums/link/linkEditPOST.js b/src/api/routes/albums/link/linkEditPOST.js index 4e0e0e1..97122a2 100644 --- a/src/api/routes/albums/link/linkEditPOST.js +++ b/src/api/routes/albums/link/linkEditPOST.js @@ -22,7 +22,7 @@ class linkEditPOST extends Route { try { const updateObj = { enableDownload: enableDownload || false, - expiresAt, // This one should be null if not supplied + expiresAt // This one should be null if not supplied }; await db .table('links') diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index ba247b5..28e9dfe 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -61,13 +61,13 @@ class linkPOST extends Route { enabled: true, enableDownload: true, expiresAt: null, - views: 0, + views: 0 }; await db.table('links').insert(insertObj); return res.json({ message: 'The link was created successfully', - data: insertObj, + data: insertObj }); } catch (error) { return super.error(res, error); diff --git a/src/api/routes/albums/link/linksGET.js b/src/api/routes/albums/link/linksGET.js index 4487c26..edab49a 100644 --- a/src/api/routes/albums/link/linksGET.js +++ b/src/api/routes/albums/link/linksGET.js @@ -14,7 +14,7 @@ class linkPOST extends Route { return res.json({ message: 'Successfully retrieved links', - links, + links }); } } diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index 5c7730c..71867f0 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -36,7 +36,7 @@ class loginPOST extends Route { const jwt = JWT.sign({ iss: 'lolisafe', sub: user.id, - iat: moment.utc().valueOf(), + iat: moment.utc().valueOf() }, process.env.SECRET, { expiresIn: '30d' }); return res.json({ @@ -45,10 +45,10 @@ class loginPOST extends Route { id: user.id, username: user.username, apiKey: user.apiKey, - isAdmin: user.isAdmin, + isAdmin: user.isAdmin }, token: jwt, - apiKey: user.apiKey, + apiKey: user.apiKey }); } } diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index e2ac018..1cf3630 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -50,7 +50,7 @@ class registerPOST extends Route { createdAt: now, editedAt: now, enabled: true, - isAdmin: false, + isAdmin: false }); return res.json({ message: 'The account was created successfully' }); } diff --git a/src/api/routes/files/albumAddPOST.js b/src/api/routes/files/albumAddPOST.js index a88e636..7b8acf7 100644 --- a/src/api/routes/files/albumAddPOST.js +++ b/src/api/routes/files/albumAddPOST.js @@ -25,7 +25,7 @@ class albumAddPOST extends Route { return res.json({ message: 'Successfully added file to album', - data: { fileId, album: { id: album.id, name: album.name } }, + data: { fileId, album: { id: album.id, name: album.name } } }); } } diff --git a/src/api/routes/files/albumDelPOST.js b/src/api/routes/files/albumDelPOST.js index 6e4d576..8304163 100644 --- a/src/api/routes/files/albumDelPOST.js +++ b/src/api/routes/files/albumDelPOST.js @@ -26,7 +26,7 @@ class albumDelPOST extends Route { return res.json({ message: 'Successfully removed file from album', - data: { fileId, album: { id: album.id, name: album.name } }, + data: { fileId, album: { id: album.id, name: album.name } } }); } } diff --git a/src/api/routes/files/fileGET.js b/src/api/routes/files/fileGET.js index 0a6f2de..9ec6f22 100644 --- a/src/api/routes/files/fileGET.js +++ b/src/api/routes/files/fileGET.js @@ -38,7 +38,7 @@ class fileGET extends Route { message: 'Successfully retrieved file', file, albums, - tags, + tags }); } } diff --git a/src/api/routes/files/filesAlbumsGET.js b/src/api/routes/files/filesAlbumsGET.js index f5f2f3b..90aa654 100644 --- a/src/api/routes/files/filesAlbumsGET.js +++ b/src/api/routes/files/filesAlbumsGET.js @@ -26,7 +26,7 @@ class filesGET extends Route { return res.json({ message: 'Successfully retrieved file albums', - albums, + albums }); } } diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index ce1d788..9e90633 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -36,7 +36,7 @@ class filesGET extends Route { return res.json({ message: 'Successfully retrieved files', files, - count, + count }); } } diff --git a/src/api/routes/files/tagAddBatchPOST.js b/src/api/routes/files/tagAddBatchPOST.js index 5091a81..679945d 100644 --- a/src/api/routes/files/tagAddBatchPOST.js +++ b/src/api/routes/files/tagAddBatchPOST.js @@ -31,7 +31,7 @@ class tagAddBatchPOST extends Route { return res.json({ message: 'Successfully added tags to file', data: { fileId, tags: addedTags }, - errors, + errors }); // eslint-disable-next-line consistent-return } diff --git a/src/api/routes/files/tagAddPOST.js b/src/api/routes/files/tagAddPOST.js index 654dceb..2bbfa07 100644 --- a/src/api/routes/files/tagAddPOST.js +++ b/src/api/routes/files/tagAddPOST.js @@ -27,7 +27,7 @@ class tagAddPOST extends Route { return res.json({ message: 'Successfully added tag to file', - data: { fileId, tag }, + data: { fileId, tag } }); // eslint-disable-next-line consistent-return } diff --git a/src/api/routes/files/tagDelPOST.js b/src/api/routes/files/tagDelPOST.js index 4d45493..ac0bfe4 100644 --- a/src/api/routes/files/tagDelPOST.js +++ b/src/api/routes/files/tagDelPOST.js @@ -29,7 +29,7 @@ class tagDelPost extends Route { return res.json({ message: 'Successfully removed tag from file', - data: { fileId, tag }, + data: { fileId, tag } }); // eslint-disable-next-line consistent-return } diff --git a/src/api/routes/search/searchGET.js b/src/api/routes/search/searchGET.js index b8757fa..40107d8 100644 --- a/src/api/routes/search/searchGET.js +++ b/src/api/routes/search/searchGET.js @@ -9,7 +9,7 @@ const options = { keywords: ['album', 'tag', 'before', 'after', 'file'], offsets: false, alwaysArray: true, - tokenize: true, + tokenize: true }; class configGET extends Route { @@ -55,7 +55,7 @@ class configGET extends Route { query, parsed, files, - count, + count }); } } diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js index 3c6a2f8..bc91a7e 100644 --- a/src/api/routes/service/configGET.js +++ b/src/api/routes/service/configGET.js @@ -18,8 +18,8 @@ class configGET extends Route { generateThumbnails: process.env.GENERATE_THUMBNAILS === 'true', generateZips: process.env.GENERATE_ZIPS === 'true', publicMode: process.env.PUBLIC_MODE === 'true', - enableAccounts: process.env.USER_ACCOUNTS === 'true', - }, + enableAccounts: process.env.USER_ACCOUNTS === 'true' + } }); } } diff --git a/src/api/routes/tags/tagPOST.js b/src/api/routes/tags/tagPOST.js index 5038b91..89b296d 100644 --- a/src/api/routes/tags/tagPOST.js +++ b/src/api/routes/tags/tagPOST.js @@ -22,7 +22,7 @@ class tagPOST extends Route { name, userId: user.id, createdAt: now, - editedAt: now, + editedAt: now }; const dbRes = await db.table('tags').insert(insertObj); diff --git a/src/api/routes/tags/tagsGET.js b/src/api/routes/tags/tagsGET.js index 848e08d..329d789 100644 --- a/src/api/routes/tags/tagsGET.js +++ b/src/api/routes/tags/tagsGET.js @@ -19,7 +19,7 @@ class tagsGET extends Route { return res.json({ message: 'Successfully retrieved tags', - tags, + tags }); } catch (error) { return super.error(res, error); diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js index 789a5e7..061cfb0 100644 --- a/src/api/routes/uploads/chunksPOST.js +++ b/src/api/routes/uploads/chunksPOST.js @@ -8,7 +8,7 @@ class uploadPOST extends Route { constructor() { super('/upload/chunks', 'post', { bypassAuth: true, - canApiKey: true, + canApiKey: true }); } @@ -17,7 +17,7 @@ class uploadPOST extends Route { // console.log('Files', req.body.files); const info = { size: req.body.files[0].size, - url: `${process.env.DOMAIN}/`, + url: `${process.env.DOMAIN}/` }; for (const chunk of req.body.files) { @@ -56,7 +56,7 @@ class uploadPOST extends Route { return res.status(201).send({ message: 'Sucessfully merged the chunk(s).', - ...info, + ...info /* name: `${filename}${ext || ''}`, size: exists.size, diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 3e67293..567862a 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -9,7 +9,7 @@ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000), - files: 1, + files: 1 }, fileFilter: (req, file, cb) => // TODO: Enable blacklisting of files/extensions @@ -21,7 +21,7 @@ const upload = multer({ } */ cb(null, true) - , + }).array('files[]'); /* @@ -41,7 +41,7 @@ class uploadPOST extends Route { constructor() { super('/upload', 'post', { bypassAuth: true, - canApiKey: true, + canApiKey: true }); } @@ -100,7 +100,7 @@ class uploadPOST extends Route { name: filename, hash, size: file.buffer.length, - url: filename, + url: filename }; } @@ -120,7 +120,7 @@ class uploadPOST extends Route { uploadedFile = Util.constructFilePublicLink(uploadedFile); return res.status(201).send({ message: 'Sucessfully uploaded the file.', - ...uploadedFile, + ...uploadedFile }); }); } @@ -134,7 +134,7 @@ class uploadPOST extends Route { size: exists.size, url: `${process.env.DOMAIN}/${exists.name}`, deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`, - repeated: true, + repeated: true }); return Util.deleteFile(filename); @@ -183,7 +183,7 @@ class uploadPOST extends Route { hash: file.hash, ip: req.ip, createdAt: now, - editedAt: now, + editedAt: now }); } else { insertedId = await db.table('files').insert({ @@ -195,7 +195,7 @@ class uploadPOST extends Route { hash: file.hash, ip: req.ip, createdAt: now, - editedAt: now, + editedAt: now }, 'id'); } return insertedId; diff --git a/src/api/routes/user/apiKey.js b/src/api/routes/user/apiKey.js index a63f0c0..653c56a 100644 --- a/src/api/routes/user/apiKey.js +++ b/src/api/routes/user/apiKey.js @@ -17,7 +17,7 @@ class apiKeyPOST extends Route { .where({ id: user.id }) .update({ apiKey, - apiKeyEditedAt: now, + apiKeyEditedAt: now }); } catch (error) { dump(error); @@ -26,7 +26,7 @@ class apiKeyPOST extends Route { return res.json({ message: 'Successfully created new api key', - apiKey, + apiKey }); } } diff --git a/src/api/routes/user/changePasswordPOST.js b/src/api/routes/user/changePasswordPOST.js index 1b3a27a..82bce40 100644 --- a/src/api/routes/user/changePasswordPOST.js +++ b/src/api/routes/user/changePasswordPOST.js @@ -36,7 +36,7 @@ class changePasswordPOST extends Route { const now = moment.utc().toDate(); await db.table('users').where('id', user.id).update({ password: hash, - passwordEditedAt: now, + passwordEditedAt: now }); return res.json({ message: 'The password was changed successfully' }); diff --git a/src/api/routes/user/userGET.js b/src/api/routes/user/userGET.js index 6f179a9..7929aac 100644 --- a/src/api/routes/user/userGET.js +++ b/src/api/routes/user/userGET.js @@ -12,8 +12,8 @@ class usersGET extends Route { id: user.id, username: user.username, isAdmin: user.isAdmin, - apiKey: user.apiKey, - }, + apiKey: user.apiKey + } }); } } diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js index 107c20a..2f370e8 100644 --- a/src/api/routes/verifyGET.js +++ b/src/api/routes/verifyGET.js @@ -11,8 +11,8 @@ class verifyGET extends Route { user: { id: user.id, username: user.username, - isAdmin: user.isAdmin, - }, + isAdmin: user.isAdmin + } }); } } -- cgit v1.2.3 From fb2c27086f570fec60f4d52dcc9ca80e53186293 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 24 Dec 2020 23:45:16 +0900 Subject: Fix ESLint rules once and for all --- src/api/routes/albums/albumZipGET.js | 4 ++-- src/api/routes/files/filesAlbumsGET.js | 2 +- src/api/routes/uploads/uploadPOST.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index cf1f6f8..26da2ba 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -64,11 +64,11 @@ class albumGET extends Route { /* Get the actual files */ - const fileIds = fileList.map((el) => el.fileId); + const fileIds = fileList.map(el => el.fileId); const files = await db.table('files') .whereIn('id', fileIds) .select('name'); - const filesToZip = files.map((el) => el.name); + const filesToZip = files.map(el => el.name); try { Util.createZip(filesToZip, album); diff --git a/src/api/routes/files/filesAlbumsGET.js b/src/api/routes/files/filesAlbumsGET.js index 90aa654..7f1190c 100644 --- a/src/api/routes/files/filesAlbumsGET.js +++ b/src/api/routes/files/filesAlbumsGET.js @@ -18,7 +18,7 @@ class filesGET extends Route { .select('albumId'); if (albumFiles.length) { - albumFiles = albumFiles.map((a) => a.albumId); + albumFiles = albumFiles.map(a => a.albumId); albums = await db.table('albums') .whereIn('id', albumFiles) .select('id', 'name'); diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 567862a..5458d48 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -56,7 +56,7 @@ class uploadPOST extends Route { if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' }); } - return upload(req, res, async (err) => { + return upload(req, res, async err => { if (err) console.error(err.message); let uploadedFile = {}; @@ -142,7 +142,7 @@ class uploadPOST extends Route { async checkIfFileExists(db, user, hash) { const exists = await db.table('files') - .where(function () { // eslint-disable-line func-names + .where(function() { // eslint-disable-line func-names if (user) this.where('userId', user.id); else this.whereNull('userId'); }) -- cgit v1.2.3 From ec2f9e0d989792c1760b48e063467cf6e59c580a Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 25 Dec 2020 20:45:22 +0900 Subject: Rebrand --- src/api/routes/albums/albumZipGET.js | 4 ++-- src/api/routes/albums/albumsGET.js | 2 +- src/api/routes/auth/loginPOST.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index 26da2ba..0722f80 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -44,7 +44,7 @@ class albumGET extends Route { Make sure the file exists just in case, and if not, continue to it's generation. */ if (exists) { - const fileName = `lolisafe-${identifier}.zip`; + const fileName = `chibisafe-${identifier}.zip`; return res.download(filePath, fileName); } } @@ -77,7 +77,7 @@ class albumGET extends Route { .update('zippedAt', db.fn.now()); const filePath = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); - const fileName = `lolisafe-${identifier}.zip`; + const fileName = `chibisafe-${identifier}.zip`; return res.download(filePath, fileName); } catch (error) { log.error(error); diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index 93a23e3..8d238a9 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -11,7 +11,7 @@ class albumsGET extends Route { /* Let's fetch the albums. This route will only return a small portion of the album files for displaying on the dashboard. It's probably useless - for anyone consuming the API outside of the lolisafe frontend. + for anyone consuming the API outside of the chibisafe frontend. */ const albums = await db .table('albums') diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index 71867f0..373252b 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -34,7 +34,7 @@ class loginPOST extends Route { Create the jwt with some data */ const jwt = JWT.sign({ - iss: 'lolisafe', + iss: 'chibisafe', sub: user.id, iat: moment.utc().valueOf() }, process.env.SECRET, { expiresIn: '30d' }); -- cgit v1.2.3 From 726f47f301795dccebb75ac90e7ce15480693288 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 27 Dec 2020 01:59:38 +0900 Subject: chore: use instance name for album download --- src/api/routes/albums/albumZipGET.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index 0722f80..c560cff 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -44,7 +44,7 @@ class albumGET extends Route { Make sure the file exists just in case, and if not, continue to it's generation. */ if (exists) { - const fileName = `chibisafe-${identifier}.zip`; + const fileName = `${process.env.SERVICE_NAME}-${identifier}.zip`; return res.download(filePath, fileName); } } @@ -77,7 +77,7 @@ class albumGET extends Route { .update('zippedAt', db.fn.now()); const filePath = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); - const fileName = `chibisafe-${identifier}.zip`; + const fileName = `${process.env.SERVICE_NAME}-${identifier}.zip`; return res.download(filePath, fileName); } catch (error) { log.error(error); -- cgit v1.2.3 From e97fee48441717f3b508ac855339d0fb8210be53 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 27 Dec 2020 04:27:56 +0900 Subject: Fixes chunked uploads not being saved to albums or thumbnails --- src/api/routes/uploads/chunksPOST.js | 21 ++++++++----- src/api/routes/uploads/uploadPOST.js | 61 ++---------------------------------- 2 files changed, 16 insertions(+), 66 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js index 061cfb0..ee95227 100644 --- a/src/api/routes/uploads/chunksPOST.js +++ b/src/api/routes/uploads/chunksPOST.js @@ -12,7 +12,10 @@ class uploadPOST extends Route { }); } - async run(req, res) { + async run(req, res, db) { + const user = await Util.isAuthorized(req); + if (!user && process.env.PUBLIC_MODE === 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); + const filename = Util.getUniqueFilename(randomstring.generate(32)); // console.log('Files', req.body.files); const info = { @@ -40,6 +43,7 @@ class uploadPOST extends Route { // Save some data info.name = `${filename}${ext || ''}`; info.url += `${filename}${ext || ''}`; + info.data = chunk; for (let i = 0; i < chunkDir.length; i++) { const dir = path.join(__dirname, @@ -54,15 +58,18 @@ class uploadPOST extends Route { await jetpack.removeAsync(chunkOutput); } + Util.generateThumbnails(info.name); + const insertedId = await Util.saveFileToDatabase(req, res, user, db, info, { + originalname: info.data.original, mimetype: info.data.type + }); + if (!insertedId) return res.status(500).json({ message: 'There was an error saving the file.' }); + info.deleteUrl = `${process.env.DOMAIN}/api/file/${insertedId[0]}`; + Util.saveFileToAlbum(db, req.headers.albumid, insertedId); + delete info.chunk; + return res.status(201).send({ message: 'Sucessfully merged the chunk(s).', ...info - /* - name: `${filename}${ext || ''}`, - size: exists.size, - url: `${process.env.DOMAIN}/${exists.name}`, - deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}` - */ }); } } diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 5458d48..5d04da1 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -106,7 +106,7 @@ class uploadPOST extends Route { if (!remappedKeys || !remappedKeys.uuid) { Util.generateThumbnails(uploadedFile.name); - insertedId = await this.saveFileToDatabase(req, res, user, db, uploadedFile, file); + insertedId = await Util.saveFileToDatabase(req, res, user, db, uploadedFile, file); if (!insertedId) return res.status(500).json({ message: 'There was an error saving the file.' }); uploadedFile.deleteUrl = `${process.env.DOMAIN}/api/file/${insertedId[0]}`; @@ -114,7 +114,7 @@ class uploadPOST extends Route { If the upload had an album specified we make sure to create the relation and update the according timestamps.. */ - this.saveFileToAlbum(db, albumId, insertedId); + Util.saveFileToAlbum(db, albumId, insertedId); } uploadedFile = Util.constructFilePublicLink(uploadedFile); @@ -151,62 +151,6 @@ class uploadPOST extends Route { return exists; } - async saveFileToAlbum(db, albumId, insertedId) { - if (!albumId) return; - - const now = moment.utc().toDate(); - try { - await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] }); - await db.table('albums').where('id', albumId).update('editedAt', now); - } catch (error) { - console.error(error); - } - } - - async saveFileToDatabase(req, res, user, db, file, originalFile) { - /* - Save the upload information to the database - */ - const now = moment.utc().toDate(); - let insertedId = null; - try { - /* - This is so fucking dumb - */ - if (process.env.DB_CLIENT === 'sqlite3') { - insertedId = await db.table('files').insert({ - userId: user ? user.id : null, - name: file.name, - original: originalFile.originalname, - type: originalFile.mimetype || '', - size: file.size, - hash: file.hash, - ip: req.ip, - createdAt: now, - editedAt: now - }); - } else { - insertedId = await db.table('files').insert({ - userId: user ? user.id : null, - name: file.name, - original: originalFile.originalname, - type: originalFile.mimetype || '', - size: file.size, - hash: file.hash, - ip: req.ip, - createdAt: now, - editedAt: now - }, 'id'); - } - return insertedId; - } catch (error) { - console.error('There was an error saving the file to the database'); - console.error(error); - return null; - // return res.status(500).json({ message: 'There was an error uploading the file.' }); - } - } - _remapKeys(body) { const keys = Object.keys(body); if (keys.length) { @@ -217,7 +161,6 @@ class uploadPOST extends Route { } return body; } - return keys; } } -- cgit v1.2.3 From aa7d2453171b3a596a1be6676eaf39cc93fe178f Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 27 Dec 2020 04:48:03 +0900 Subject: feat: Add hash checking for chunked uploads --- src/api/routes/uploads/chunksPOST.js | 22 ++++++++++++++++++++++ src/api/routes/uploads/uploadPOST.js | 13 +------------ 2 files changed, 23 insertions(+), 12 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js index ee95227..9cf7338 100644 --- a/src/api/routes/uploads/chunksPOST.js +++ b/src/api/routes/uploads/chunksPOST.js @@ -58,6 +58,28 @@ class uploadPOST extends Route { await jetpack.removeAsync(chunkOutput); } + /* + If a file with the same hash and user is found, delete this + uploaded copy and return a link to the original + */ + info.hash = await Util.getFileHash(info.name); + let existingFile = await Util.checkIfFileExists(db, user, info.hash); + if (existingFile) { + existingFile = Util.constructFilePublicLink(existingFile); + res.json({ + message: 'Successfully uploaded the file.', + name: existingFile.name, + hash: existingFile.hash, + size: existingFile.size, + url: `${process.env.DOMAIN}/${existingFile.name}`, + deleteUrl: `${process.env.DOMAIN}/api/file/${existingFile.id}`, + repeated: true + }); + + return Util.deleteFile(info.name); + } + + // Otherwise generate thumbs and do the rest Util.generateThumbnails(info.name); const insertedId = await Util.saveFileToDatabase(req, res, user, db, info, { originalname: info.data.original, mimetype: info.data.type diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 5d04da1..449999e 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -79,7 +79,7 @@ class uploadPOST extends Route { For this we need to wait until we have a filename so that we can delete the uploaded file. */ - const exists = await this.checkIfFileExists(db, user, hash); + const exists = await Util.checkIfFileExists(db, user, hash); if (exists) return this.fileExists(res, exists, filename); if (remappedKeys && remappedKeys.uuid) { @@ -140,17 +140,6 @@ class uploadPOST extends Route { return Util.deleteFile(filename); } - async checkIfFileExists(db, user, hash) { - const exists = await db.table('files') - .where(function() { // eslint-disable-line func-names - if (user) this.where('userId', user.id); - else this.whereNull('userId'); - }) - .where({ hash }) - .first(); - return exists; - } - _remapKeys(body) { const keys = Object.keys(body); if (keys.length) { -- cgit v1.2.3 From edb3bed98864e34695a5ae0093c414a2b578073a Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 28 Dec 2020 00:10:59 +0900 Subject: feat: Add warning to nsfw albums --- src/api/routes/albums/albumEditPOST.js | 33 +++++++++++++++++++++++++++++++++ src/api/routes/albums/albumGET.js | 1 + src/api/routes/albums/albumsGET.js | 2 +- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/api/routes/albums/albumEditPOST.js (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumEditPOST.js b/src/api/routes/albums/albumEditPOST.js new file mode 100644 index 0000000..f104cc1 --- /dev/null +++ b/src/api/routes/albums/albumEditPOST.js @@ -0,0 +1,33 @@ +const Route = require('../../structures/Route'); + +class albumEditPOST extends Route { + constructor() { + super('/album/edit', 'post'); + } + + async run(req, res, db, user) { + if (!req.body) return res.status(400).json({ message: 'No body provided' }); + const { id, name, nsfw } = req.body; + if (!id) return res.status(400).json({ message: 'Invalid album identifier supplied' }); + + + const album = await db.table('albums').where({ id, userId: user.id }).first(); + if (!album) return res.status(400).json({ message: 'The album doesn\'t exist or doesn\'t belong to the user' }); + + try { + const updateObj = { + name: name ? name : album.name, + nsfw: nsfw === true ? true : nsfw === false ? false : album.nsfw + }; + await db + .table('albums') + .where({ id }) + .update(updateObj); + return res.json({ message: 'Editing the album was successful', data: updateObj }); + } catch (error) { + return super.error(res, error); + } + } +} + +module.exports = albumEditPOST; diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index 950a1fd..c9f6763 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -37,6 +37,7 @@ class albumGET extends Route { message: 'Successfully retrieved files', name: album.name, downloadEnabled: link.enableDownload, + isNsfw: album.nsfw, files }); } diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index 8d238a9..3c18d8f 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -16,7 +16,7 @@ class albumsGET extends Route { const albums = await db .table('albums') .where('albums.userId', user.id) - .select('id', 'name', 'createdAt', 'editedAt') + .select('id', 'name', 'nsfw', 'createdAt', 'editedAt') .orderBy('createdAt', 'desc'); for (const album of albums) { -- cgit v1.2.3 From 13058d99d658c0920ce75b79d6b24df18a873ea9 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Sun, 27 Dec 2020 18:18:06 +0200 Subject: fix: nsfw album toggle doesn't propagate the changes properly fix: add nsfw flag to the booleanFields in knex postProcessResponse --- src/api/routes/albums/albumEditPOST.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes') diff --git a/src/api/routes/albums/albumEditPOST.js b/src/api/routes/albums/albumEditPOST.js index f104cc1..1022bbd 100644 --- a/src/api/routes/albums/albumEditPOST.js +++ b/src/api/routes/albums/albumEditPOST.js @@ -16,7 +16,7 @@ class albumEditPOST extends Route { try { const updateObj = { - name: name ? name : album.name, + name: name || album.name, nsfw: nsfw === true ? true : nsfw === false ? false : album.nsfw }; await db -- cgit v1.2.3