aboutsummaryrefslogtreecommitdiff
path: root/src/api/routes/uploads
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/routes/uploads')
-rw-r--r--src/api/routes/uploads/chunksPOST.js99
-rw-r--r--src/api/routes/uploads/uploadPOST.js156
2 files changed, 255 insertions, 0 deletions
diff --git a/src/api/routes/uploads/chunksPOST.js b/src/api/routes/uploads/chunksPOST.js
new file mode 100644
index 0000000..9cf7338
--- /dev/null
+++ b/src/api/routes/uploads/chunksPOST.js
@@ -0,0 +1,99 @@
+const path = require('path');
+const jetpack = require('fs-jetpack');
+const randomstring = require('randomstring');
+const Util = require('../../utils/Util');
+const Route = require('../../structures/Route');
+
+class uploadPOST extends Route {
+ constructor() {
+ super('/upload/chunks', 'post', {
+ bypassAuth: true,
+ canApiKey: true
+ });
+ }
+
+ async run(req, res, db) {
+ const user = await Util.isAuthorized(req);
+ if (!user && process.env.PUBLIC_MODE === 'false') return res.status(401).json({ message: 'Not authorized to use this resource' });
+
+ const filename = Util.getUniqueFilename(randomstring.generate(32));
+ // console.log('Files', req.body.files);
+ const info = {
+ size: req.body.files[0].size,
+ url: `${process.env.DOMAIN}/`
+ };
+
+ for (const chunk of req.body.files) {
+ const { uuid } = chunk;
+ // console.log('Chunk', chunk);
+
+ const chunkOutput = path.join(__dirname,
+ '../../../../',
+ process.env.UPLOAD_FOLDER,
+ 'chunks',
+ uuid);
+ const chunkDir = await jetpack.list(chunkOutput);
+ const ext = path.extname(chunkDir[0]);
+ const output = path.join(__dirname,
+ '../../../../',
+ process.env.UPLOAD_FOLDER,
+ `${filename}${ext || ''}`);
+ chunkDir.sort();
+
+ // Save some data
+ info.name = `${filename}${ext || ''}`;
+ info.url += `${filename}${ext || ''}`;
+ info.data = chunk;
+
+ for (let i = 0; i < chunkDir.length; i++) {
+ const dir = path.join(__dirname,
+ '../../../../',
+ process.env.UPLOAD_FOLDER,
+ 'chunks',
+ uuid,
+ chunkDir[i]);
+ const file = await jetpack.readAsync(dir, 'buffer');
+ await jetpack.appendAsync(output, file);
+ }
+ await jetpack.removeAsync(chunkOutput);
+ }
+
+ /*
+ If a file with the same hash and user is found, delete this
+ uploaded copy and return a link to the original
+ */
+ info.hash = await Util.getFileHash(info.name);
+ let existingFile = await Util.checkIfFileExists(db, user, info.hash);
+ if (existingFile) {
+ existingFile = Util.constructFilePublicLink(existingFile);
+ res.json({
+ message: 'Successfully uploaded the file.',
+ name: existingFile.name,
+ hash: existingFile.hash,
+ size: existingFile.size,
+ url: `${process.env.DOMAIN}/${existingFile.name}`,
+ deleteUrl: `${process.env.DOMAIN}/api/file/${existingFile.id}`,
+ repeated: true
+ });
+
+ return Util.deleteFile(info.name);
+ }
+
+ // Otherwise generate thumbs and do the rest
+ Util.generateThumbnails(info.name);
+ const insertedId = await Util.saveFileToDatabase(req, res, user, db, info, {
+ originalname: info.data.original, mimetype: info.data.type
+ });
+ if (!insertedId) return res.status(500).json({ message: 'There was an error saving the file.' });
+ info.deleteUrl = `${process.env.DOMAIN}/api/file/${insertedId[0]}`;
+ Util.saveFileToAlbum(db, req.headers.albumid, insertedId);
+ delete info.chunk;
+
+ return res.status(201).send({
+ message: 'Sucessfully merged the chunk(s).',
+ ...info
+ });
+ }
+}
+
+module.exports = uploadPOST;
diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js
new file mode 100644
index 0000000..449999e
--- /dev/null
+++ b/src/api/routes/uploads/uploadPOST.js
@@ -0,0 +1,156 @@
+const path = require('path');
+const jetpack = require('fs-jetpack');
+const multer = require('multer');
+const moment = require('moment');
+const Util = require('../../utils/Util');
+const Route = require('../../structures/Route');
+
+const upload = multer({
+ storage: multer.memoryStorage(),
+ limits: {
+ fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000),
+ files: 1
+ },
+ fileFilter: (req, file, cb) =>
+ // TODO: Enable blacklisting of files/extensions
+ /*
+ if (options.blacklist.mimes.includes(file.mimetype)) {
+ return cb(new Error(`${file.mimetype} is a blacklisted filetype.`));
+ } else if (options.blacklist.extensions.some(ext => path.extname(file.originalname).toLowerCase() === ext)) {
+ return cb(new Error(`${path.extname(file.originalname).toLowerCase()} is a blacklisted extension.`));
+ }
+ */
+ cb(null, true)
+
+}).array('files[]');
+
+/*
+ TODO: If source has transparency generate a png thumbnail, otherwise a jpg.
+ TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover on the frontend.
+
+ TODO: 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
+
+ XXX: Now that the default behaviour is to serve files with node, we can actually pull this off.
+ Before this, having files in subfolders meant messing with nginx and the paths,
+ but now it should be fairly easy to re-arrange the folder structure with express.static
+ I see great value in this, open to suggestions.
+*/
+
+class uploadPOST extends Route {
+ constructor() {
+ super('/upload', 'post', {
+ bypassAuth: true,
+ canApiKey: true
+ });
+ }
+
+ async run(req, res, db) {
+ const user = await Util.isAuthorized(req);
+ if (!user && process.env.PUBLIC_MODE === 'false') return res.status(401).json({ message: 'Not authorized to use this resource' });
+
+ const albumId = req.body.albumid || req.headers.albumid;
+ if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' });
+ if (albumId && user) {
+ const album = await db.table('albums').where({ id: albumId, userId: user.id }).first();
+ if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' });
+ }
+
+ return upload(req, res, async err => {
+ if (err) console.error(err.message);
+
+ let uploadedFile = {};
+ let insertedId;
+
+ // eslint-disable-next-line no-underscore-dangle
+ const remappedKeys = this._remapKeys(req.body);
+ const file = req.files[0];
+
+ const ext = path.extname(file.originalname);
+ const hash = Util.generateFileHash(file.buffer);
+
+ const filename = Util.getUniqueFilename(file.originalname);
+
+ /*
+ First let's get the hash of the file. This will be useful to check if the file
+ has already been upload by either the user or an anonymous user.
+ In case this is true, instead of uploading it again we retrieve the url
+ of the file that is already saved and thus don't store extra copies of the same file.
+
+ For this we need to wait until we have a filename so that we can delete the uploaded file.
+ */
+ const exists = await Util.checkIfFileExists(db, user, hash);
+ if (exists) return this.fileExists(res, exists, filename);
+
+ if (remappedKeys && remappedKeys.uuid) {
+ const chunkOutput = path.join(__dirname,
+ '../../../../',
+ process.env.UPLOAD_FOLDER,
+ 'chunks',
+ remappedKeys.uuid,
+ `${remappedKeys.chunkindex.padStart(3, 0)}${ext || ''}`);
+ await jetpack.writeAsync(chunkOutput, file.buffer);
+ } else {
+ const output = path.join(__dirname,
+ '../../../../',
+ process.env.UPLOAD_FOLDER,
+ filename);
+ await jetpack.writeAsync(output, file.buffer);
+ uploadedFile = {
+ name: filename,
+ hash,
+ size: file.buffer.length,
+ url: filename
+ };
+ }
+
+ if (!remappedKeys || !remappedKeys.uuid) {
+ Util.generateThumbnails(uploadedFile.name);
+ insertedId = await Util.saveFileToDatabase(req, res, user, db, uploadedFile, file);
+ if (!insertedId) return res.status(500).json({ message: 'There was an error saving the file.' });
+ uploadedFile.deleteUrl = `${process.env.DOMAIN}/api/file/${insertedId[0]}`;
+
+ /*
+ If the upload had an album specified we make sure to create the relation
+ and update the according timestamps..
+ */
+ Util.saveFileToAlbum(db, albumId, insertedId);
+ }
+
+ uploadedFile = Util.constructFilePublicLink(uploadedFile);
+ return res.status(201).send({
+ message: 'Sucessfully uploaded the file.',
+ ...uploadedFile
+ });
+ });
+ }
+
+ fileExists(res, exists, filename) {
+ exists = Util.constructFilePublicLink(exists);
+ res.json({
+ message: 'Successfully uploaded the file.',
+ name: exists.name,
+ hash: exists.hash,
+ size: exists.size,
+ url: `${process.env.DOMAIN}/${exists.name}`,
+ deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`,
+ repeated: true
+ });
+
+ return Util.deleteFile(filename);
+ }
+
+ _remapKeys(body) {
+ const keys = Object.keys(body);
+ if (keys.length) {
+ for (const key of keys) {
+ if (!/^dz/.test(key)) continue;
+ body[key.replace(/^dz/, '')] = body[key];
+ delete body[key];
+ }
+ return body;
+ }
+ }
+}
+
+module.exports = uploadPOST;