Skip to content

Commit ff9bf3d

Browse files
committed
add code for uploading music
add code for uploading movies
1 parent e3a5004 commit ff9bf3d

File tree

2 files changed

+313
-150
lines changed

2 files changed

+313
-150
lines changed

api/upload.js

Lines changed: 144 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,11 @@ const express = require('express');
33
const multer = require('multer');
44
const bencode = require('bencode');
55
const crypto = require('crypto');
6-
const { ulid } = require('ulid');
76
const db = require('./../db');
7+
const validator = require('./validator');
88

99
const app = express.Router();
1010

11-
const verificationTypes = {
12-
group: {
13-
album: {
14-
required: ['artist', 'album', 'original_release_year'],
15-
allowed: ['artist', 'album', 'original_release_year'],
16-
errorMessage: 'Album group is missing information.'
17-
}
18-
},
19-
release: {
20-
album: {
21-
required: ['release_type', 'format', 'bitrate', 'media'],
22-
allowed: ['release_type', 'format', 'bitrate', 'media'],
23-
errorMessage: 'Album is missing information.'
24-
}
25-
}
26-
};
27-
2811
/**
2912
* Saves torrent file to disk
3013
*
@@ -69,7 +52,7 @@ const hashTorrent = (torrentBuffer) => {
6952
* @param torrent - torrent object
7053
* @return {Object} torrent object with buffer and hash attached
7154
*/
72-
const processTorrent = async (torrent) => {
55+
const processTorrent = (torrent) => {
7356
const decodedTorrent = bencode.decode(torrent.buffer);
7457
delete decodedTorrent.announce;
7558

@@ -86,137 +69,153 @@ const processTorrent = async (torrent) => {
8669
return torrent;
8770
};
8871

89-
/**
90-
* Verifies an Object
91-
*
92-
* Object verified against an element of verificationTypes
93-
* Removes all extraneous fields on the input Object
94-
* Throws error if required fields are missing
95-
*
96-
* @param input - Object to be verified
97-
* @param type - Field of the verificationTypes object
98-
* @return {Promise<Map<torrent>>}
99-
*/
100-
const verify = async (input, type) => {
101-
const { required } = type;
102-
const { allowed } = type;
103-
const errMsg = type.errorMessage || 'Missing required fields.';
104-
105-
const verified = new Map();
106-
Object.entries(input).forEach((entry) => {
107-
const key = entry[0];
108-
const value = entry[1];
109-
110-
if (allowed.includes(key)) {
111-
verified.set(key, value);
112-
}
113-
});
72+
const torrentUpload = upload.fields([{ name: 'torrent', maxCount: 1 }]);
11473

115-
await Promise.all(
116-
required.map(async (field) => {
117-
if (!verified.has(field)) {
118-
throw new Error(errMsg);
74+
app.post('/upload', torrentUpload, async (req, res) => {
75+
const release = JSON.parse(req.body.release);
76+
const client = await db.pool.connect();
77+
78+
if (release.torrentType === 'music') {
79+
const musicReleaseTypes = (await db.getMusicReleaseTypes()).rows;
80+
const musicQualities = (await db.getMusicQualities()).rows;
81+
try {
82+
await validator.validateMusic({
83+
release,
84+
musicReleaseTypes,
85+
musicQualities
86+
});
87+
88+
// valid if doesn't throw
89+
await Promise.all(release.artists.map(validator.validateArtist));
90+
91+
await db.beginTransaction(client);
92+
93+
let musicId = release.music;
94+
// insert music if necessary
95+
if (release.music instanceof Object) {
96+
// music comes with at least 1 associated artist
97+
// insert the artist(s) if they are not ids
98+
const artistsToInsert = release.artists.filter((artist) =>
99+
Object.prototype.hasOwnProperty.call(artist, 'name')
100+
);
101+
const artistIds = release.artists.filter((artist) =>
102+
Object.prototype.hasOwnProperty.call(artist, 'id')
103+
);
104+
const insertedArtists = await db.insertArtists(artistsToInsert, client);
105+
const artistsToLink = [...artistIds, ...insertedArtists];
106+
107+
// insert music and assign it to musicId to be used to create the music_release
108+
const insertMusicRes = await db.insertMusic(
109+
{
110+
music: release.music,
111+
artistsToLink
112+
},
113+
client
114+
);
115+
musicId = insertMusicRes.rows[0].id;
119116
}
120-
})
121-
);
122-
123-
return verified;
124-
};
125-
126-
const upTo = (to, startAt = 1) => {
127-
const upToArray = [];
128-
for (let i = startAt; i <= to; i++) {
129-
upToArray.push(`$${i}`);
130-
}
131-
return upToArray.toString();
132-
};
133-
134-
/**
135-
* Gets the group_id that the input torrent should use
136-
*
137-
* Either uses the user provided group number or creates a new group if the
138-
* user provides one, or creates a new group from the user provided data
139-
*
140-
* @param {Object} group - group user input
141-
* @return {Promise<number>} The group number associated with a torrent
142-
*/
143-
const getGroup = async (group) => {
144-
if (typeof group === 'number') {
145-
return group;
146-
}
147-
148-
const verifiedGroup = verify(group, verificationTypes.group.album);
149-
const keys = verifiedGroup.keys();
150-
const values = verifiedGroup.values();
151-
const length = verifiedGroup.size;
152-
153-
const res = await db.query(
154-
`insert into groups (${[...keys].toString()}) values (${upTo(
155-
length + 1
156-
)}) returning id`,
157-
[ulid(), ...values]
158-
);
159-
group = res.rows[0].id;
160-
return group;
161-
};
162-
163-
/**
164-
* Stores a release to the database
165-
*
166-
* Verifies the releaseInfo and adds all of the metadata that is obtained from
167-
* the other functions
168-
*
169-
* @async
170-
* @param {Object} torrent - slightly modified user inputted torrent
171-
* @param {number} group - group (user inputted id or new group id)
172-
* @param {Object} releaseInfo - extra user provided data
173-
* @return {Promise<Object>} The data associated with a persisted torrent
174-
*/
175-
const store = async (torrent, group, releaseInfo) => {
176-
const verifiedRelease = await verify(
177-
releaseInfo,
178-
verificationTypes.release.album
179-
);
180-
// Add more information to the verified user input
181-
verifiedRelease.set('group_id', group);
182-
verifiedRelease.set('file_size', torrent.totalFileSize);
183-
verifiedRelease.set('original_file_name', torrent.originalname);
184-
verifiedRelease.set('file_path', torrent.path);
185-
verifiedRelease.set('files', JSON.stringify(torrent.fileList));
186-
187-
const keys = verifiedRelease.keys();
188-
const values = verifiedRelease.values();
189-
const length = verifiedRelease.size;
190-
191-
const res = await db.query(
192-
`insert into torrents (${[...keys].toString()}) values (${upTo(
193-
length + 1
194-
)}) returning id, group_id`,
195-
[ulid(), ...values]
196-
);
197-
return {
198-
groupId: res.rows[0].group_id,
199-
torrentId: res.rows[0].id
200-
};
201-
};
202117

203-
const torrentUpload = upload.fields([{ name: 'torrent', maxCount: 1 }]);
118+
const processedTorrent = processTorrent(req.files.torrent[0]);
119+
120+
// insert torrent
121+
const torrentRes = await db.insertTorrent(
122+
{
123+
fileSize: processedTorrent.totalFileSize,
124+
originalFileName: processedTorrent.originalname,
125+
filePath: '/',
126+
files: processedTorrent.fileList,
127+
uploaderId: '01D91F1JSSW4X5DNKSVPD2J3EM'
128+
},
129+
client
130+
);
131+
132+
const torrentId = torrentRes.rows[0].id;
133+
134+
// insert music release
135+
await db.insertMusicRelease(
136+
{ musicId, releaseInfo: release.info, torrentId },
137+
client
138+
);
139+
140+
await db.commitTransaction(client);
141+
} catch (e) {
142+
await db.rollbackTransaction(client);
143+
console.log(e);
144+
return res.status(400).json({
145+
error: e
146+
});
147+
} finally {
148+
client.release();
149+
}
150+
} else if (release.torrentType === 'movie') {
151+
try {
152+
await validator.validateMovie({
153+
release
154+
});
155+
156+
await db.beginTransaction(client);
157+
158+
let movieId = release.movie;
159+
if (release.movie instanceof Object) {
160+
const movieRes = await db.insertMovie(
161+
{
162+
name: release.movie.name,
163+
description: release.movie.description,
164+
year: release.movie.year
165+
},
166+
client
167+
);
168+
169+
movieId = movieRes.rows[0].id;
170+
}
204171

205-
app.post('/upload', torrentUpload, async (req, res) => {
206-
const incomingTorrent = req.files.torrent[0];
207-
const incomingGroup = JSON.parse(req.body.group);
208-
const releaseInfo = JSON.parse(req.body.info);
209-
210-
try {
211-
const processedTorrent = processTorrent(incomingTorrent);
212-
const torrentWithPath = await saveToDisk(processedTorrent);
213-
const group = await getGroup(incomingGroup);
214-
const persistedTorrent = await store(torrentWithPath, group, releaseInfo);
215-
res.status(200).send(persistedTorrent);
216-
} catch (e) {
217-
console.log(e);
218-
res.sendStatus(500);
172+
const processedTorrent = processTorrent(req.files.torrent[0]);
173+
174+
// insert torrent
175+
const torrentRes = await db.insertTorrent(
176+
{
177+
fileSize: processedTorrent.totalFileSize,
178+
originalFileName: processedTorrent.originalname,
179+
filePath: '/',
180+
files: processedTorrent.fileList,
181+
uploaderId: '01D91F1JSSW4X5DNKSVPD2J3EM'
182+
},
183+
client
184+
);
185+
186+
const torrentId = torrentRes.rows[0].id;
187+
188+
await db.insertVideoRelease(
189+
{
190+
videoId: movieId,
191+
quality: release.info.quality,
192+
title: release.info.title,
193+
description: release.info.description,
194+
torrentId
195+
},
196+
client
197+
);
198+
199+
await db.commitTransaction(client);
200+
} catch (e) {
201+
await db.rollbackTransaction(client);
202+
console.log(e);
203+
return res.status(400).json({
204+
error: e
205+
});
206+
} finally {
207+
client.release();
208+
}
209+
} else if (release.torrentType === 'tv') {
210+
} else if (release.torrentType === 'anime') {
211+
} else if (
212+
release.torrentType === 'software' ||
213+
release.torrentType === 'video-game'
214+
) {
215+
} else {
216+
return res.sendStatus(400);
219217
}
218+
res.sendStatus(200);
220219
});
221220

222221
module.exports = app;

0 commit comments

Comments
 (0)