v3.0.0/src/api/utils/Util.js

244 lines
7.7 KiB
JavaScript
Raw Normal View History

2018-09-16 05:55:41 +02:00
const jetpack = require('fs-jetpack');
const randomstring = require('randomstring');
const path = require('path');
const JWT = require('jsonwebtoken');
2019-02-19 15:52:24 +01:00
const db = require('knex')({
client: process.env.DB_CLIENT,
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
2019-02-21 15:05:56 +01:00
password: process.env.DB_PASSWORD,
2019-02-21 16:00:07 +01:00
database: process.env.DB_DATABASE,
2019-02-22 07:07:37 +01:00
filename: path.join(__dirname, '..', '..', '..', 'database.sqlite')
2019-02-22 16:45:45 +01:00
},
useNullAsDefault: process.env.DB_CLIENT === 'sqlite' ? true : false
2019-02-19 15:52:24 +01:00
});
2018-09-16 05:55:41 +02:00
const moment = require('moment');
const log = require('../utils/Log');
const crypto = require('crypto');
const sharp = require('sharp');
const ffmpeg = require('fluent-ffmpeg');
2018-09-18 08:34:00 +02:00
const Zip = require('adm-zip');
2019-03-14 15:13:51 +01:00
const uuidv4 = require('uuid/v4');
2018-09-16 05:55:41 +02:00
const imageExtensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png', '.webp'];
const videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov'];
2019-02-19 15:52:24 +01:00
const blockedExtensions = process.env.BLOCKED_EXTENSIONS.split(',');
2018-09-16 05:55:41 +02:00
class Util {
2019-03-14 15:13:51 +01:00
static uuid() {
return uuidv4();
}
2018-09-16 05:55:41 +02:00
static isExtensionBlocked(extension) {
2019-02-19 15:52:24 +01:00
return blockedExtensions.includes(extension);
2018-09-16 05:55:41 +02:00
}
static generateThumbnails(filename) {
const ext = path.extname(filename).toLowerCase();
const output = `${filename.slice(0, -ext.length)}.png`;
if (imageExtensions.includes(ext)) return this.generateThumbnailForImage(filename, output);
if (videoExtensions.includes(ext)) return this.generateThumbnailForVideo(filename);
return null;
}
static async generateThumbnailForImage(filename, output) {
2019-02-19 15:52:24 +01:00
const file = await jetpack.readAsync(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, filename), 'buffer');
2018-09-16 05:55:41 +02:00
await sharp(file)
.resize(64, 64)
.toFormat('png')
2019-02-19 15:52:24 +01:00
.toFile(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, 'thumbs', 'square', output));
2018-09-16 05:55:41 +02:00
await sharp(file)
.resize(225, null)
.toFormat('png')
2019-02-19 15:52:24 +01:00
.toFile(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, 'thumbs', output));
2018-09-16 05:55:41 +02:00
}
static generateThumbnailForVideo(filename) {
2019-02-19 15:52:24 +01:00
ffmpeg(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, filename))
2018-09-16 05:55:41 +02:00
.thumbnail({
timestamps: [0],
filename: '%b.png',
2019-02-19 15:52:24 +01:00
folder: path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, 'thumbs', 'square'),
2018-09-16 05:55:41 +02:00
size: '64x64'
})
.on('error', error => log.error(error.message));
2019-02-19 15:52:24 +01:00
ffmpeg(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, filename))
2018-09-16 05:55:41 +02:00
.thumbnail({
timestamps: [0],
filename: '%b.png',
2019-02-19 15:52:24 +01:00
folder: path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, 'thumbs'),
2018-09-16 05:55:41 +02:00
size: '150x?'
})
.on('error', error => log.error(error.message));
}
static getFileThumbnail(filename) {
2019-10-12 07:37:09 +02:00
if (!filename) return null;
2018-09-16 05:55:41 +02:00
const ext = path.extname(filename).toLowerCase();
if (!imageExtensions.includes(ext) && !videoExtensions.includes(ext)) return null;
return `${filename.slice(0, -ext.length)}.png`;
}
static constructFilePublicLink(file) {
2019-02-28 15:51:59 +01:00
/*
TODO: This wont work without a reverse proxy serving both
the site and the API under the same domain. Pls fix.
*/
2019-02-19 15:52:24 +01:00
file.url = `${process.env.DOMAIN}/${file.name}`;
2018-09-16 05:55:41 +02:00
const thumb = this.getFileThumbnail(file.name);
if (thumb) {
2019-02-19 15:52:24 +01:00
file.thumb = `${process.env.DOMAIN}/thumbs/${thumb}`;
file.thumbSquare = `${process.env.DOMAIN}/thumbs/square/${thumb}`;
2018-09-16 05:55:41 +02:00
}
return file;
}
static getUniqueFilename(name) {
const retry = (i = 0) => {
const filename = randomstring.generate({
2019-03-01 18:08:11 +01:00
length: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10),
2018-09-16 05:55:41 +02:00
capitalization: 'lowercase'
}) + path.extname(name).toLowerCase();
// TODO: Change this to look for the file in the db instead of in the filesystem
2019-02-19 15:52:24 +01:00
const exists = jetpack.exists(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, filename));
2018-09-16 05:55:41 +02:00
if (!exists) return filename;
2019-02-19 15:52:24 +01:00
if (i < 5) return retry(i + 1);
log.error('Couldnt allocate identifier for file');
2018-09-16 05:55:41 +02:00
return null;
};
return retry();
}
static getUniqueAlbumIdentifier() {
const retry = async (i = 0) => {
const identifier = randomstring.generate({
2019-03-01 18:08:11 +01:00
length: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10),
2018-09-16 05:55:41 +02:00
capitalization: 'lowercase'
});
const exists = await db.table('links').where({ identifier }).first();
if (!exists) return identifier;
2018-09-16 22:53:26 +02:00
/*
It's funny but if you do i++ the asignment never gets done resulting in an infinite loop
*/
2019-02-19 15:52:24 +01:00
if (i < 5) return retry(i + 1);
2018-09-16 22:53:26 +02:00
log.error('Couldnt allocate identifier for album');
2018-09-16 05:55:41 +02:00
return null;
};
return retry();
}
static async getFileHash(filename) {
2019-02-19 15:52:24 +01:00
const file = await jetpack.readAsync(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, filename), 'buffer');
2018-09-16 05:55:41 +02:00
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');
}
2019-10-01 19:11:16 +02:00
static generateFileHash(data) {
2020-05-09 16:56:35 +02:00
const hash = crypto.createHash('md5').update(data).digest('hex');
2019-10-01 19:11:16 +02:00
return hash;
}
2018-09-16 05:55:41 +02:00
static getFilenameFromPath(fullPath) {
return fullPath.replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
}
static async deleteFile(filename, deleteFromDB = false) {
2018-09-18 06:44:58 +02:00
const thumbName = this.getFileThumbnail(filename);
2018-09-16 05:55:41 +02:00
try {
2019-02-19 15:52:24 +01:00
await jetpack.removeAsync(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, filename));
await jetpack.removeAsync(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, 'thumbs', thumbName));
await jetpack.removeAsync(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, 'thumbs', 'square', thumbName));
2018-09-16 05:55:41 +02:00
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 {
2019-03-14 15:13:51 +01:00
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);
2018-09-16 05:55:41 +02:00
}
} catch (error) {
log.error(error);
2019-02-28 15:51:59 +01:00
}
}
static async deleteAllFilesFromUser(id) {
try {
const files = await db.table('files').where({ userId: id });
for (const file of files) {
2019-03-14 15:13:51 +01:00
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);
2019-02-28 15:51:59 +01:00
}
} catch (error) {
log.error(error);
2018-09-16 05:55:41 +02:00
}
}
static isAuthorized(req) {
if (!req.headers.authorization) return false;
const token = req.headers.authorization.split(' ')[1];
if (!token) return false;
2019-02-19 15:52:24 +01:00
return JWT.verify(token, process.env.SECRET, async (error, decoded) => {
2018-09-16 05:55:41 +02:00
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;
});
}
2018-09-18 08:34:00 +02:00
static createZip(files, album) {
try {
const zip = new Zip();
for (const file of files) {
2019-02-19 15:52:24 +01:00
zip.addLocalFile(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, file));
2018-09-18 08:34:00 +02:00
}
2019-02-19 15:52:24 +01:00
zip.writeZip(path.join(__dirname, '..', '..', '..', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`));
2018-09-18 08:34:00 +02:00
} catch (error) {
log.error(error);
}
}
2018-09-16 05:55:41 +02:00
}
module.exports = Util;