Mark van Renswoude
10322bc26f
- Grid style more suited for large file listings - Multi-select allows individual files to be deleted
385 lines
7.5 KiB
JavaScript
385 lines
7.5 KiB
JavaScript
const map = require('lodash/map');
|
|
const filter = require('lodash/filter');
|
|
const intersectionWith = require('lodash/intersectionWith');
|
|
const pullAllWith = require('lodash/pullAllWith');
|
|
const resolvePath = require('resolve-path');
|
|
const fs = require('mz/fs');
|
|
const async = require('async');
|
|
|
|
|
|
class Upload
|
|
{
|
|
constructor(values)
|
|
{
|
|
var self = this;
|
|
|
|
self.id = values.id || values._id || null;
|
|
self.userId = values.userId || null;
|
|
self.codeId = values.codeId || null;
|
|
self.created = values.created || new Date();
|
|
self.expirationDate = values.expirationDate || null;
|
|
self.files = values.files || [];
|
|
}
|
|
}
|
|
|
|
|
|
class UploadRepository
|
|
{
|
|
constructor(store)
|
|
{
|
|
var self = this;
|
|
self.store = store;
|
|
}
|
|
|
|
|
|
init()
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
// Initialize database if empty
|
|
self.store.count({}, (err, count) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
if (count == 0)
|
|
self.addUser('admin', null, 'test', null);
|
|
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
insert(upload)
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
let insertUpload = {
|
|
created: upload.created || new Date(),
|
|
userId: upload.userId,
|
|
codeId: upload.codeId,
|
|
expirationDate: upload.expirationDate,
|
|
files: map(filter(upload.files,
|
|
(file) => file.hasOwnProperty('id') && file.hasOwnProperty('name')),
|
|
(file) => { return { id: file.id, name: file.name, size: file.size } })
|
|
};
|
|
|
|
if (insertUpload.files.length)
|
|
{
|
|
self.store.insert(insertUpload, (err, dbUpload) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
resolve(dbUpload._id);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
reject();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
list(userId)
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
self.store.find(userId != null ? { userId: userId } : {}, (err, docs) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
resolve(docs.map((dbUpload) =>
|
|
{
|
|
return new Upload(dbUpload);
|
|
}));
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
listForCode(codeId)
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
self.store.find({ codeId: codeId }, (err, docs) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
resolve(docs.map((dbUpload) =>
|
|
{
|
|
return new Upload(dbUpload);
|
|
}));
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
get(uploadId)
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
self.store.findOne({ _id: uploadId }, (err, doc) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
resolve(doc !== null ? new Upload(doc) : null);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
move(codeId, userId)
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
self.store.update({ codeId: codeId }, {
|
|
$set: {
|
|
userId: userId
|
|
}
|
|
}, {
|
|
multi: true
|
|
},
|
|
(err, numAffected) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
if (numAffected == 0)
|
|
{
|
|
reject();
|
|
}
|
|
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
delete(uploadId)
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
self.store.findOne({ _id: uploadId }, (err, doc) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
if (doc === null)
|
|
{
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
let upload = new Upload(doc);
|
|
|
|
self.store.remove({ _id: uploadId }, async (err, numRemoved) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
await self.deleteOrphanedFiles(upload.files);
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
async deleteOrphanedFiles(files)
|
|
{
|
|
var self = this;
|
|
|
|
await Promise.all(files.map(async (file) =>
|
|
{
|
|
if (!file.id) return;
|
|
if (!(await self.fileExists(file.id)))
|
|
{
|
|
var fullpath = resolvePath(config.fileUpload.path, file.id);
|
|
try
|
|
{
|
|
await fs.unlink(fullpath);
|
|
}
|
|
catch (err)
|
|
{
|
|
console.log('Failed to delete ' + fullpath);
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
|
|
|
|
deleteFiles(uploadId, fileIds)
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
self.store.findOne({ _id: uploadId }, (err, doc) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
if (doc === null)
|
|
{
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
let upload = new Upload(doc);
|
|
let deletedFiles = intersectionWith(upload.files, fileIds, (arrVal, othVal) => { return arrVal.id == othVal; });
|
|
|
|
if (deletedFiles.length == 0)
|
|
{
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
|
|
pullAllWith(upload.files, fileIds, (arrVal, othVal) => { return arrVal.id == othVal; });
|
|
if (upload.files.length == 0)
|
|
{
|
|
// Remove entire upload
|
|
self.store.remove({ _id: uploadId }, async (err, numRemoved) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
await self.deleteOrphanedFiles(deletedFiles);
|
|
resolve();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Update file list
|
|
self.store.update({ _id: uploadId }, {
|
|
$set: {
|
|
files: map(filter(upload.files,
|
|
(file) => file.hasOwnProperty('id') && file.hasOwnProperty('name')),
|
|
(file) => { return { id: file.id, name: file.name, size: file.size } })
|
|
},
|
|
},
|
|
async (err, numRemoved) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
await self.deleteOrphanedFiles(deletedFiles);
|
|
resolve();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
fileExists(fileId)
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
self.store.findOne({ 'files.id': fileId }, (err, doc) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
resolve(doc !== null);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
deleteExpired()
|
|
{
|
|
var self = this;
|
|
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
let now = new Date();
|
|
|
|
self.store.find({ $where: function() { return this.expirationDate !== null && this.expirationDate < now }}, (err, docs) =>
|
|
{
|
|
if (err)
|
|
{
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
async.eachOfSeries(docs,
|
|
async (doc) =>
|
|
{
|
|
console.log('Expired upload: ' + doc._id);
|
|
await self.delete(doc._id);
|
|
},
|
|
(err) =>
|
|
{
|
|
if (err)
|
|
reject(err);
|
|
else
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
UploadRepository
|
|
} |