Upload refactor #242
20
README.md
|
@ -36,17 +36,17 @@ spee.ch is a single-serving site that reads and publishes images and videos to a
|
|||
|
||||
#### POST
|
||||
* /api/publish
|
||||
* example: `curl -X POST -F 'name=MyPictureName' -F 'nsfw=false' -F 'file=@/path/to/my/picture.jpeg' https://spee.ch/api/publish`
|
||||
* example: `curl -X POST -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/publish`
|
||||
* Parameters:
|
||||
* name (string)
|
||||
* nsfw (boolean)
|
||||
* file (.mp4, .jpeg, .jpg, .gif, or .png)
|
||||
* license (string, optional)
|
||||
* title (string, optional)
|
||||
* description (string, optional)
|
||||
* thumbnail (string, optional) (for .mp4 uploads only)
|
||||
* channelName(string, optional)
|
||||
* channelPassword (string, optional)
|
||||
* `name`
|
||||
* `file` (.mp4, .jpeg, .jpg, .gif, or .png)
|
||||
* `nsfw` (optional)
|
||||
* `license` (optional)
|
||||
* `title` (optional)
|
||||
* `description` (optional)
|
||||
* `thumbnail` url to thumbnail image, for .mp4 uploads only (optional)
|
||||
* `channelName`(optional)
|
||||
* `channelPassword` (optional,; required if `channelName` is provided)
|
||||
|
||||
## bugs
|
||||
If you find a bug or experience a problem, please report your issue here on github and find us in the lbry slack!
|
||||
|
|
|
@ -2,12 +2,14 @@ const db = require('../models');
|
|||
const logger = require('winston');
|
||||
|
||||
module.exports = {
|
||||
authenticateChannelCredentials (channelName, userPassword) {
|
||||
authenticateChannelCredentials (skipAuth, channelName, userPassword) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!channelName) {
|
||||
resolve(true);
|
||||
// skip authentication if not needed
|
||||
if (skipAuth) {
|
||||
resolve(skipAuth);
|
||||
return;
|
||||
}
|
||||
// authentication
|
||||
const userName = channelName.substring(1);
|
||||
logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`);
|
||||
db.User
|
||||
|
@ -18,17 +20,23 @@ module.exports = {
|
|||
resolve(false);
|
||||
return;
|
||||
}
|
||||
if (!user.validPassword(userPassword, user.password)) {
|
||||
logger.debug('incorrect password');
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
logger.debug('user found:', user.dataValues);
|
||||
resolve(true);
|
||||
return user.comparePassword(userPassword, (passwordErr, isMatch) => {
|
||||
if (passwordErr) {
|
||||
logger.error('comparePassword error:', passwordErr);
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
if (!isMatch) {
|
||||
logger.debug('incorrect password');
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
logger.debug('...password was a match...');
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(error);
|
||||
reject();
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -13,17 +13,16 @@ module.exports = {
|
|||
.then(tx => {
|
||||
logger.info(`Successfully published ${fileName}`, tx);
|
||||
publishResults = tx;
|
||||
return db.Channel.findOne({where: {channelName: publishParams.channel_name}}); // note: should this be db.User ??
|
||||
})
|
||||
.then(channel => {
|
||||
let certificateId;
|
||||
if (channel) {
|
||||
certificateId = channel.channelClaimId;
|
||||
logger.debug('successfully found channel in Channel table');
|
||||
if (publishParams.channel_name) {
|
||||
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
|
||||
return db.Channel.findOne({where: {channelName: publishParams.channel_name}}).then(channel => { return channel.channelClaimId });
|
||||
} else {
|
||||
certificateId = null;
|
||||
logger.debug('channel for publish not found in Channel table');
|
||||
};
|
||||
logger.debug('this claim was published in channel: n/a');
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.then(certificateId => {
|
||||
logger.debug(`certificateId: ${certificateId}`);
|
||||
const fileRecord = {
|
||||
name : publishParams.name,
|
||||
claimId : publishResults.claim_id,
|
||||
|
|
|
@ -3,11 +3,12 @@ const { postToStats } = require('../controllers/statsController.js');
|
|||
|
||||
module.exports = {
|
||||
returnErrorMessageAndStatus: function (error) {
|
||||
let status;
|
||||
let message;
|
||||
let status, message;
|
||||
// check for daemon being turned off
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
status = 503;
|
||||
message = 'Connection refused. The daemon may not be running.';
|
||||
// check for errors from the deamon
|
||||
} else if (error.response) {
|
||||
status = error.response.status || 500;
|
||||
if (error.response.data) {
|
||||
|
@ -21,7 +22,13 @@ module.exports = {
|
|||
} else {
|
||||
message = error.response;
|
||||
}
|
||||
// check for spee.ch thrown errors
|
||||
} else if (error.message) {
|
||||
status = 400;
|
||||
message = error.message;
|
||||
// fallback for everything else
|
||||
} else {
|
||||
status = 400;
|
||||
message = error;
|
||||
}
|
||||
return [status, message];
|
||||
|
|
|
@ -18,11 +18,10 @@ module.exports = {
|
|||
throw new Error('no file with key of [file] found in request');
|
||||
}
|
||||
},
|
||||
validatePublishSubmission (file, claimName, nsfw) {
|
||||
validatePublishSubmission (file, claimName) {
|
||||
try {
|
||||
module.exports.validateFile(file);
|
||||
module.exports.validateClaimName(claimName);
|
||||
module.exports.validateNSFW(nsfw);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
@ -76,24 +75,6 @@ module.exports = {
|
|||
throw new Error('Only posts with a "Public Domain" or "Creative Commons" license are eligible for publishing through spee.ch');
|
||||
}
|
||||
},
|
||||
cleanseNSFW (nsfw) {
|
||||
switch (nsfw) {
|
||||
case true:
|
||||
case 'on':
|
||||
case 'true':
|
||||
case 1:
|
||||
case '1':
|
||||
return true;
|
||||
case false:
|
||||
case 'false':
|
||||
case 'off':
|
||||
case 0:
|
||||
case '0':
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
cleanseChannelName (channelName) {
|
||||
if (channelName) {
|
||||
if (channelName.indexOf('@') !== 0) {
|
||||
|
@ -102,12 +83,6 @@ module.exports = {
|
|||
}
|
||||
return channelName;
|
||||
},
|
||||
validateNSFW (nsfw) {
|
||||
if (nsfw === true || nsfw === false) {
|
||||
return;
|
||||
}
|
||||
throw new Error('NSFW must be set to either true or false');
|
||||
},
|
||||
createPublishParams (filePath, name, title, description, license, nsfw, thumbnail, channelName) {
|
||||
logger.debug(`Creating Publish Parameters`);
|
||||
// provide defaults for title
|
||||
|
|
|
@ -64,20 +64,12 @@ var publishFileFunctions = {
|
|||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
}
|
||||
},
|
||||
returnNullOrChannel: function () {
|
||||
const channelInput = document.getElementById('channel-name-select');
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
const radios = document.getElementsByName('anonymous-or-channel');
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
let anonymousOrInChannel;
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
// replace channelName with 'anonymous' if appropriate
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
for (let i = 0; i < radios.length; i++) {
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
if (radios[i].checked) {
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
anonymousOrInChannel = radios[i].value; // do whatever you want with the checked radio
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
break; // only one radio can be logically checked, don't check the rest
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
}
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
const channelRadio = document.getElementById('channel-radio');
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
if (channelRadio.checked) {
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
const channelInput = document.getElementById('channel-name-select');
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
return channelInput.value.trim();
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
}
|
||||
if (anonymousOrInChannel === 'anonymous') {
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
return null;
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
};
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
return channelInput.value.trim();
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
return null;
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
},
|
||||
createMetadata: function() {
|
||||
const nameInput = document.getElementById('claim-name-input');
|
||||
|
@ -89,13 +81,13 @@ var publishFileFunctions = {
|
|||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
|
||||
return {
|
||||
name: nameInput.value.trim(),
|
||||
channel: this.returnNullOrChannel(),
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
channelName: this.returnNullOrChannel(),
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
title: titleInput.value.trim(),
|
||||
description: descriptionInput.value.trim(),
|
||||
license: licenseInput.value.trim(),
|
||||
nsfw: nsfwInput.checked,
|
||||
type: stagedFiles[0].type,
|
||||
thumbnail: thumbnailInput.value.trim()
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
thumbnail: thumbnailInput.value.trim(),
|
||||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
||||
}
|
||||
},
|
||||
appendDataToFormData: function (file, metadata) {
|
||||
|
|
|||
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
Presumably Presumably `const`
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage. Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.
|
|
@ -4,7 +4,7 @@ const multipartMiddleware = multipart();
|
|||
const db = require('../models');
|
||||
const { publish } = require('../controllers/publishController.js');
|
||||
const { getClaimList, resolveUri } = require('../helpers/lbryApi.js');
|
||||
const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseNSFW, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js');
|
||||
const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js');
|
||||
const errorHandlers = require('../helpers/errorHandlers.js');
|
||||
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
|
||||
const { authenticateChannelCredentials } = require('../auth/authentication.js');
|
||||
|
@ -71,7 +71,9 @@ module.exports = (app) => {
|
|||
});
|
||||
});
|
||||
// route to run a publish request on the daemon
|
||||
app.post('/api/publish', multipartMiddleware, ({ body, files, ip, originalUrl }, res) => {
|
||||
app.post('/api/publish', multipartMiddleware, ({ body, files, ip, originalUrl, user }, res) => {
|
||||
logger.debug('api/publish body:', body);
|
||||
let file, fileName, filePath, fileType, name, nsfw, license, title, description, thumbnail, anonymous, skipAuth, channelName, channelPassword;
|
||||
// validate that mandatory parts of the request are present
|
||||
try {
|
||||
validateApiPublishRequest(body, files);
|
||||
|
@ -81,12 +83,12 @@ module.exports = (app) => {
|
|||
return;
|
||||
}
|
||||
// validate file, name, license, and nsfw
|
||||
const file = files.file;
|
||||
const fileName = file.name;
|
||||
const filePath = file.path;
|
||||
const fileType = file.type;
|
||||
const name = body.name;
|
||||
const nsfw = cleanseNSFW(body.nsfw); // cleanse nsfw input
|
||||
file = files.file;
|
||||
fileName = file.name;
|
||||
filePath = file.path;
|
||||
fileType = file.type;
|
||||
name = body.name;
|
||||
nsfw = (body.nsfw === 'true');
|
||||
try {
|
||||
validatePublishSubmission(file, name, nsfw);
|
||||
} catch (error) {
|
||||
|
@ -94,18 +96,36 @@ module.exports = (app) => {
|
|||
res.status(400).json({success: false, message: error.message});
|
||||
return;
|
||||
}
|
||||
logger.debug(`name: ${name}, nsfw: ${nsfw}`);
|
||||
// optional inputs
|
||||
const license = body.license || null;
|
||||
const title = body.title || null;
|
||||
const description = body.description || null;
|
||||
const thumbnail = body.thumbnail || null;
|
||||
let channelName = body.channelName || null;
|
||||
license = body.license || null;
|
||||
title = body.title || null;
|
||||
description = body.description || null;
|
||||
thumbnail = body.thumbnail || null;
|
||||
anonymous = (body.channelName === 'null') || (body.channelName === undefined);
|
||||
if (user) {
|
||||
channelName = user.channelName || null;
|
||||
} else {
|
||||
channelName = body.channelName || null;
|
||||
}
|
||||
channelPassword = body.channelPassword || null;
|
||||
skipAuth = false;
|
||||
// case 1: publish from spee.ch, client logged in
|
||||
if (user) {
|
||||
skipAuth = true;
|
||||
if (anonymous) {
|
||||
channelName = null;
|
||||
}
|
||||
// case 2: publish from api or spee.ch, client not logged in
|
||||
} else {
|
||||
if (anonymous) {
|
||||
skipAuth = true;
|
||||
channelName = null;
|
||||
}
|
||||
}
|
||||
channelName = cleanseChannelName(channelName);
|
||||
const channelPassword = body.channelPassword || null;
|
||||
logger.debug(`license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}" nsfw: "${nsfw}"`);
|
||||
logger.debug(`name: ${name}, license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}" nsfw: "${nsfw}"`);
|
||||
// check channel authorization
|
||||
authenticateChannelCredentials(channelName, channelPassword)
|
||||
authenticateChannelCredentials(skipAuth, channelName, channelPassword)
|
||||
.then(result => {
|
||||
if (!result) {
|
||||
throw new Error('Authentication failed, you do not have access to that channel');
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
<div class="column column--10">
|
||||
<form>
|
||||
<div class="column column--3 column--med-10">
|
||||
<input type="radio" name="anonymous-or-channel" id="anonymous-select" class="input-radio" value="anonymous" {{#unless user}}checked {{/unless}} onchange="toggleChannel(event.target.value)"/>
|
||||
<label class="label label--pointer" for="anonymous-select">Anonymous</label>
|
||||
<input type="radio" name="anonymous-or-channel" id="anonymous-radio" class="input-radio" value="anonymous" {{#unless user}}checked {{/unless}} onchange="toggleChannel(event.target.value)"/>
|
||||
<label class="label label--pointer" for="anonymous-radio">Anonymous</label>
|
||||
</div><div class="column column--7 column--med-10">
|
||||
<input type="radio" name="anonymous-or-channel" id="in-a-channel-select" class="input-radio" value="in a channel" {{#if user}}checked {{/if}} onchange="toggleChannel(event.target.value)"/>
|
||||
<label class="label label--pointer" for="in-a-channel-select">In a channel</label>
|
||||
<input type="radio" name="anonymous-or-channel" id="channel-radio" class="input-radio" value="in a channel" {{#if user}}checked {{/if}} onchange="toggleChannel(event.target.value)"/>
|
||||
<label class="label label--pointer" for="channel-radio">In a channel</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
Presumably
const
Late on catching this one, but this function name doesn't imply it is redirecting me to the homepage.