297 lines
7.6 KiB
JavaScript
297 lines
7.6 KiB
JavaScript
/* 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;
|