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/files/fileDELETE.js | 32 +++++ src/api/routes/files/filesGET.js | 25 ++++ src/api/routes/files/uploadPOST.js | 276 +++++++++++++++++++++++++++++++++++++ 3 files changed, 333 insertions(+) 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 (limited to 'src/api/routes/files') 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; -- 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/files/uploadPOST.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/api/routes/files') 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 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/files') 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 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/files/fileDELETE.js | 13 ++++++++----- src/api/routes/files/filesGET.js | 8 ++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) (limited to 'src/api/routes/files') 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/files') 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 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/files') 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/files') 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 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/files/fileDELETE.js | 4 +- src/api/routes/files/filesGET.js | 4 +- src/api/routes/files/uploadPOST.js | 90 +++++++++++++++++++------------------- 3 files changed, 46 insertions(+), 52 deletions(-) (limited to 'src/api/routes/files') 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); } } -- 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/files') 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/files/uploadPOST.js | 44 +++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 13 deletions(-) (limited to 'src/api/routes/files') 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/files') 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 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/files') 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 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/files') 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/files') 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/files') 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/files/uploadPOST.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api/routes/files') 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 } }); -- 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/files') 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 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/files') 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 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 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/api/routes/files/tagAddPOST.js create mode 100644 src/api/routes/files/tagDelPOST.js (limited to 'src/api/routes/files') 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; -- 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/files') 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 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 + 1 file changed, 1 insertion(+) (limited to 'src/api/routes/files') 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); } -- 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 -------- 1 file changed, 8 deletions(-) (limited to 'src/api/routes/files') 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 */ -- 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/files') 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 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/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 +++++- 5 files changed, 24 insertions(+), 31 deletions(-) delete mode 100644 src/api/routes/files/tagDelPOST.js (limited to 'src/api/routes/files') 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) { -- 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/files/fileGET.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/api/routes/files/fileGET.js (limited to 'src/api/routes/files') 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 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/files') 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 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/files/fileGET.js | 29 ---- src/api/routes/files/filesAlbumsGET.js | 3 + src/api/routes/files/uploadPOST.js | 286 --------------------------------- 3 files changed, 3 insertions(+), 315 deletions(-) delete mode 100644 src/api/routes/files/fileGET.js delete mode 100644 src/api/routes/files/uploadPOST.js (limited to 'src/api/routes/files') 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; -- 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/files') 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/files/filesGET.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src/api/routes/files') 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 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/files') 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/files/filesAlbumsGET.js | 4 ++-- src/api/routes/files/filesGET.js | 2 +- src/api/routes/files/tagAddPOST.js | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src/api/routes/files') 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', }); } } -- 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/files/fileGET.js | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/api/routes/files/fileGET.js (limited to 'src/api/routes/files') 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/files') 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/files') 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/files') 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/files') 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 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/api/routes/files') 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 } -- 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/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 +- 8 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src/api/routes/files') 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 } -- 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/files/filesAlbumsGET.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/routes/files') 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'); -- cgit v1.2.3