You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
91 lines
3.1 KiB
JavaScript
91 lines
3.1 KiB
JavaScript
'use strict';
|
|
|
|
const path = require('path');
|
|
const nconf = require('nconf');
|
|
const winston = require('winston');
|
|
const crypto = require('crypto');
|
|
|
|
const db = require('../database');
|
|
const posts = require('../posts');
|
|
const file = require('../file');
|
|
const batch = require('../batch');
|
|
|
|
const md5 = filename => crypto.createHash('md5').update(filename).digest('hex');
|
|
const _getFullPath = relativePath => path.resolve(nconf.get('upload_path'), relativePath);
|
|
const _validatePath = async (relativePaths) => {
|
|
if (typeof relativePaths === 'string') {
|
|
relativePaths = [relativePaths];
|
|
} else if (!Array.isArray(relativePaths)) {
|
|
throw new Error(`[[error:wrong-parameter-type, relativePaths, ${typeof relativePaths}, array]]`);
|
|
}
|
|
|
|
const fullPaths = relativePaths.map(path => _getFullPath(path));
|
|
const exists = await Promise.all(fullPaths.map(async fullPath => file.exists(fullPath)));
|
|
|
|
if (!fullPaths.every(fullPath => fullPath.startsWith(nconf.get('upload_path'))) || !exists.every(Boolean)) {
|
|
throw new Error('[[error:invalid-path]]');
|
|
}
|
|
};
|
|
|
|
module.exports = function (User) {
|
|
User.associateUpload = async (uid, relativePath) => {
|
|
await _validatePath(relativePath);
|
|
await Promise.all([
|
|
db.sortedSetAdd(`uid:${uid}:uploads`, Date.now(), relativePath),
|
|
db.setObjectField(`upload:${md5(relativePath)}`, 'uid', uid),
|
|
]);
|
|
};
|
|
|
|
User.deleteUpload = async function (callerUid, uid, uploadNames) {
|
|
if (typeof uploadNames === 'string') {
|
|
uploadNames = [uploadNames];
|
|
} else if (!Array.isArray(uploadNames)) {
|
|
throw new Error(`[[error:wrong-parameter-type, uploadNames, ${typeof uploadNames}, array]]`);
|
|
}
|
|
|
|
await _validatePath(uploadNames);
|
|
|
|
const [isUsersUpload, isAdminOrGlobalMod] = await Promise.all([
|
|
db.isSortedSetMembers(`uid:${callerUid}:uploads`, uploadNames),
|
|
User.isAdminOrGlobalMod(callerUid),
|
|
]);
|
|
if (!isAdminOrGlobalMod && !isUsersUpload.every(Boolean)) {
|
|
throw new Error('[[error:no-privileges]]');
|
|
}
|
|
|
|
await batch.processArray(uploadNames, async (uploadNames) => {
|
|
const fullPaths = uploadNames.map(path => _getFullPath(path));
|
|
|
|
await Promise.all(fullPaths.map(async (fullPath, idx) => {
|
|
winston.verbose(`[user/deleteUpload] Deleting ${uploadNames[idx]}`);
|
|
await Promise.all([
|
|
file.delete(fullPath),
|
|
file.delete(file.appendToFileName(fullPath, '-resized')),
|
|
]);
|
|
await Promise.all([
|
|
db.sortedSetRemove(`uid:${uid}:uploads`, uploadNames[idx]),
|
|
db.delete(`upload:${md5(uploadNames[idx])}`),
|
|
]);
|
|
}));
|
|
|
|
// Dissociate the upload from pids, if any
|
|
const pids = await db.getSortedSetsMembers(uploadNames.map(relativePath => `upload:${md5(relativePath)}:pids`));
|
|
await Promise.all(pids.map(async (pids, idx) => Promise.all(
|
|
pids.map(async pid => posts.uploads.dissociate(pid, uploadNames[idx]))
|
|
)));
|
|
}, { batch: 50 });
|
|
};
|
|
|
|
User.collateUploads = async function (uid, archive) {
|
|
await batch.processSortedSet(`uid:${uid}:uploads`, (files, next) => {
|
|
files.forEach((file) => {
|
|
archive.file(_getFullPath(file), {
|
|
name: path.basename(file),
|
|
});
|
|
});
|
|
|
|
setImmediate(next);
|
|
}, { batch: 100 });
|
|
};
|
|
};
|