diff --git a/api/src/models/Track.js b/api/src/models/Track.js index 6a5809e..8e16293 100644 --- a/api/src/models/Track.js +++ b/api/src/models/Track.js @@ -1,3 +1,4 @@ +const crypto = require('crypto'); const mongoose = require('mongoose'); const _ = require('lodash'); const uniqueValidator = require('mongoose-unique-validator'); @@ -82,6 +83,12 @@ const schema = new mongoose.Schema( }, }, + // A hash of the original file's contents. Nobody can upload the same track twice. + originalFileHash: { + type: String, + required: true, + }, + // Where the files are stored, relative to a group directory like // TRACKS_DIR or PROCESSING_DIR. filePath: String, @@ -94,6 +101,8 @@ const schema = new mongoose.Schema( { timestamps: true }, ); +schema.index({ author: 1, originalFileHash: 1 }, { unique: true }); + schema.plugin(uniqueValidator, { message: 'is already taken' }); schema.pre('validate', async function (next) { @@ -184,6 +193,19 @@ class Track extends mongoose.Model { await fs.promises.writeFile(this.getOriginalFilePath(), fileBody); } + async validateFileBodyUniqueness(fileBody) { + // Generate hash + const hash = crypto.createHash('sha512').update(fileBody).digest('hex'); + + const existingTracks = await Track.find({ originalFileHash: hash, author: this.author }); + if (existingTracks.length === 0 || (existingTracks.length === 1 && existingTracks[0]._id.equals(this._id))) { + this.originalFileHash = hash; + return; + } + + throw new Error('Track file already uploaded.'); + } + /** * Marks this track as needing processing. * diff --git a/api/src/routes/api/tracks.js b/api/src/routes/api/tracks.js index 1b5631e..50bb5de 100644 --- a/api/src/routes/api/tracks.js +++ b/api/src/routes/api/tracks.js @@ -194,6 +194,7 @@ router.post( track.slugify(); if (fileBody) { + await track.validateFileBodyUniqueness(fileBody) track.uploadedByUserAgent = normalizeUserAgent(req.headers['user-agent']); track.originalFileName = fileInfo.body ? fileInfo.body.filename : track.slug + '.csv'; await track.writeToOriginalFile(fileBody) @@ -256,6 +257,7 @@ router.put( } if (fileBody) { + await track.validateFileBodyUniqueness(fileBody) track.originalFileName = fileInfo.body ? fileInfo.body.filename : track.slug + '.csv'; track.uploadedByUserAgent = normalizeUserAgent(req.headers['user-agent']); await track.writeToOriginalFile(fileBody)