diff options
Diffstat (limited to 'src/api/utils/Util.js')
| -rw-r--r-- | src/api/utils/Util.js | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js new file mode 100644 index 0000000..e52fac2 --- /dev/null +++ b/src/api/utils/Util.js @@ -0,0 +1,296 @@ +/* eslint-disable no-await-in-loop */ +const jetpack = require('fs-jetpack'); +const randomstring = require('randomstring'); +const path = require('path'); +const JWT = require('jsonwebtoken'); +const db = require('knex')({ + client: process.env.DB_CLIENT, + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE, + filename: path.join(__dirname, '../../../database/database.sqlite') + }, + useNullAsDefault: process.env.DB_CLIENT === 'sqlite' +}); +const moment = require('moment'); +const crypto = require('crypto'); +const Zip = require('adm-zip'); +const uuidv4 = require('uuid/v4'); + +const log = require('./Log'); +const ThumbUtil = require('./ThumbUtil'); + +const blockedExtensions = process.env.BLOCKED_EXTENSIONS.split(','); + +class Util { + static uploadPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER); + + static uuid() { + return uuidv4(); + } + + static isExtensionBlocked(extension) { + return blockedExtensions.includes(extension); + } + + static constructFilePublicLink(file) { + /* + TODO: This wont work without a reverse proxy serving both + the site and the API under the same domain. Pls fix. + */ + file.url = `${process.env.DOMAIN}/${file.name}`; + const { thumb, preview } = ThumbUtil.getFileThumbnail(file.name) || {}; + if (thumb) { + file.thumb = `${process.env.DOMAIN}/thumbs/${thumb}`; + file.thumbSquare = `${process.env.DOMAIN}/thumbs/square/${thumb}`; + file.preview = preview && `${process.env.DOMAIN}/thumbs/preview/${preview}`; + } + return file; + } + + static getUniqueFilename(name) { + const retry = (i = 0) => { + const filename = randomstring.generate({ + length: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10), + capitalization: 'lowercase' + }) + path.extname(name).toLowerCase(); + + // TODO: Change this to look for the file in the db instead of in the filesystem + const exists = jetpack.exists(path.join(Util.uploadPath, filename)); + if (!exists) return filename; + if (i < 5) return retry(i + 1); + log.error('Couldnt allocate identifier for file'); + return null; + }; + return retry(); + } + + static getUniqueAlbumIdentifier() { + const retry = async (i = 0) => { + const identifier = randomstring.generate({ + length: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), + capitalization: 'lowercase' + }); + const exists = await db + .table('links') + .where({ identifier }) + .first(); + if (!exists) return identifier; + /* + It's funny but if you do i++ the asignment never gets done resulting in an infinite loop + */ + if (i < 5) return retry(i + 1); + log.error('Couldnt allocate identifier for album'); + return null; + }; + return retry(); + } + + static async getFileHash(filename) { + const file = await jetpack.readAsync(path.join(Util.uploadPath, filename), 'buffer'); + if (!file) { + log.error(`There was an error reading the file < ${filename} > for hashing`); + return null; + } + + const hash = crypto.createHash('md5'); + hash.update(file, 'utf8'); + return hash.digest('hex'); + } + + static generateFileHash(data) { + const hash = crypto + .createHash('md5') + .update(data) + .digest('hex'); + return hash; + } + + static async checkIfFileExists(db, user, hash) { + const exists = await db.table('files') + .where(function() { // eslint-disable-line func-names + if (user) this.where('userId', user.id); + else this.whereNull('userId'); + }) + .where({ hash }) + .first(); + return exists; + } + + static getFilenameFromPath(fullPath) { + return fullPath.replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape + } + + static async deleteFile(filename, deleteFromDB = false) { + const thumbName = ThumbUtil.getFileThumbnail(filename); + try { + await jetpack.removeAsync(path.join(Util.uploadPath, filename)); + await ThumbUtil.removeThumbs(thumbName); + + if (deleteFromDB) { + await db + .table('files') + .where('name', filename) + .delete(); + } + } catch (error) { + log.error(`There was an error removing the file < ${filename} >`); + log.error(error); + } + } + + static async deleteAllFilesFromAlbum(id) { + try { + const fileAlbums = await db.table('albumsFiles').where({ albumId: id }); + for (const fileAlbum of fileAlbums) { + const file = await db + .table('files') + .where({ id: fileAlbum.fileId }) + .first(); + + if (!file) continue; + + await this.deleteFile(file.name, true); + } + } catch (error) { + log.error(error); + } + } + + static async deleteAllFilesFromUser(id) { + try { + const files = await db.table('files').where({ userId: id }); + for (const file of files) { + await this.deleteFile(file.name, true); + } + } catch (error) { + log.error(error); + } + } + + static async deleteAllFilesFromTag(id) { + try { + const fileTags = await db.table('fileTags').where({ tagId: id }); + for (const fileTag of fileTags) { + const file = await db + .table('files') + .where({ id: fileTag.fileId }) + .first(); + if (!file) continue; + await this.deleteFile(file.name, true); + } + } catch (error) { + log.error(error); + } + } + + static async isAuthorized(req) { + if (req.headers.token) { + const user = await db.table('users').where({ apiKey: req.headers.token }).first(); + if (!user || !user.enabled) return false; + return user; + } + + if (!req.headers.authorization) return false; + const token = req.headers.authorization.split(' ')[1]; + if (!token) return false; + + return JWT.verify(token, process.env.SECRET, async (error, decoded) => { + if (error) { + log.error(error); + return false; + } + const id = decoded ? decoded.sub : ''; + const iat = decoded ? decoded.iat : ''; + + const user = await db + .table('users') + .where({ id }) + .first(); + if (!user || !user.enabled) return false; + if (iat && iat < moment(user.passwordEditedAt).format('x')) return false; + + return user; + }); + } + + static createZip(files, album) { + try { + const zip = new Zip(); + for (const file of files) { + zip.addLocalFile(path.join(Util.uploadPath, file)); + } + zip.writeZip( + path.join( + __dirname, + '../../../', + process.env.UPLOAD_FOLDER, + 'zips', + `${album.userId}-${album.id}.zip` + ) + ); + } catch (error) { + log.error(error); + } + } + + static generateThumbnails = ThumbUtil.generateThumbnails; + static async saveFileToDatabase(req, res, user, db, file, originalFile) { + /* + Save the upload information to the database + */ + const now = moment.utc().toDate(); + let insertedId = null; + try { + /* + This is so fucking dumb + */ + if (process.env.DB_CLIENT === 'sqlite3') { + insertedId = await db.table('files').insert({ + userId: user ? user.id : null, + name: file.name, + original: originalFile.originalname, + type: originalFile.mimetype || '', + size: file.size, + hash: file.hash, + ip: req.ip, + createdAt: now, + editedAt: now + }); + } else { + insertedId = await db.table('files').insert({ + userId: user ? user.id : null, + name: file.name, + original: originalFile.originalname, + type: originalFile.mimetype || '', + size: file.size, + hash: file.hash, + ip: req.ip, + createdAt: now, + editedAt: now + }, 'id'); + } + return insertedId; + } catch (error) { + console.error('There was an error saving the file to the database'); + console.error(error); + return null; + } + } + + static async saveFileToAlbum(db, albumId, insertedId) { + if (!albumId) return; + + const now = moment.utc().toDate(); + try { + await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] }); + await db.table('albums').where('id', albumId).update('editedAt', now); + } catch (error) { + console.error(error); + } + } +} + +module.exports = Util; |