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 }