Publish api fix #191
8 changed files with 300 additions and 187 deletions
|
@ -2,21 +2,23 @@ const db = require('../models');
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
authenticateApiPublish (username, password) {
|
authenticateChannelCredentials (channelName, userPassword) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (username === 'none') {
|
if (!channelName) {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const userName = channelName.substring(1);
|
||||||
|
logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`);
|
||||||
db.User
|
db.User
|
||||||
.findOne({where: {userName: username}})
|
.findOne({where: { userName }})
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.debug('no user found');
|
logger.debug('no user found');
|
||||||
resolve(false);
|
resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!user.validPassword(password, user.password)) {
|
if (!user.validPassword(userPassword, user.password)) {
|
||||||
logger.debug('incorrect password');
|
logger.debug('incorrect password');
|
||||||
resolve(false);
|
resolve(false);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -7,24 +7,23 @@ module.exports = {
|
||||||
publish (publishParams, fileName, fileType) {
|
publish (publishParams, fileName, fileType) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let publishResults = {};
|
let publishResults = {};
|
||||||
// 1. make sure the name is available
|
// 1. publish the file
|
||||||
publishHelpers.checkClaimNameAvailability(publishParams.name)
|
lbryApi.publishClaim(publishParams)
|
||||||
// 2. publish the file
|
// 2. upsert File record (update is in case the claim has been published before by this daemon)
|
||||||
.then(result => {
|
|
||||||
if (result === true) {
|
|
||||||
return lbryApi.publishClaim(publishParams);
|
|
||||||
} else {
|
|
||||||
return new Error('That name is already in use by spee.ch.');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// 3. upsert File record (update is in case the claim has been published before by this daemon)
|
|
||||||
.then(tx => {
|
.then(tx => {
|
||||||
logger.info(`Successfully published ${fileName}`, tx);
|
logger.info(`Successfully published ${fileName}`, tx);
|
||||||
publishResults = tx;
|
publishResults = tx;
|
||||||
return db.Channel.findOne({where: {channelName: publishParams.channel_name}});
|
return db.Channel.findOne({where: {channelName: publishParams.channel_name}});
|
||||||
})
|
})
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (user) { logger.debug('successfully found user in User table') } else { logger.error('user for publish not found in User table') };
|
let certificateId;
|
||||||
|
if (user) {
|
||||||
|
certificateId = user.channelClaimId;
|
||||||
|
logger.debug('successfully found user in User table');
|
||||||
|
} else {
|
||||||
|
certificateId = null;
|
||||||
|
logger.debug('user for publish not found in User table');
|
||||||
|
};
|
||||||
const fileRecord = {
|
const fileRecord = {
|
||||||
name : publishParams.name,
|
name : publishParams.name,
|
||||||
claimId : publishResults.claim_id,
|
claimId : publishResults.claim_id,
|
||||||
|
@ -39,17 +38,17 @@ module.exports = {
|
||||||
nsfw : publishParams.metadata.nsfw,
|
nsfw : publishParams.metadata.nsfw,
|
||||||
};
|
};
|
||||||
const claimRecord = {
|
const claimRecord = {
|
||||||
name : publishParams.name,
|
name : publishParams.name,
|
||||||
claimId : publishResults.claim_id,
|
claimId : publishResults.claim_id,
|
||||||
title : publishParams.metadata.title,
|
title : publishParams.metadata.title,
|
||||||
description : publishParams.metadata.description,
|
description: publishParams.metadata.description,
|
||||||
address : publishParams.claim_address,
|
address : publishParams.claim_address,
|
||||||
outpoint : `${publishResults.txid}:${publishResults.nout}`,
|
outpoint : `${publishResults.txid}:${publishResults.nout}`,
|
||||||
height : 0,
|
height : 0,
|
||||||
contentType : fileType,
|
contentType: fileType,
|
||||||
nsfw : publishParams.metadata.nsfw,
|
nsfw : publishParams.metadata.nsfw,
|
||||||
certificateId: user.channelClaimId,
|
certificateId,
|
||||||
amount : publishParams.bid,
|
amount : publishParams.bid,
|
||||||
};
|
};
|
||||||
const upsertCriteria = {
|
const upsertCriteria = {
|
||||||
name : publishParams.name,
|
name : publishParams.name,
|
||||||
|
|
|
@ -30,8 +30,16 @@ module.exports = {
|
||||||
logger.error('Publish Error:', useObjectPropertiesIfNoKeys(error));
|
logger.error('Publish Error:', useObjectPropertiesIfNoKeys(error));
|
||||||
if (error.code === 'ECONNREFUSED') {
|
if (error.code === 'ECONNREFUSED') {
|
||||||
return 'Connection refused. The daemon may not be running.';
|
return 'Connection refused. The daemon may not be running.';
|
||||||
} else if (error.response.data.error) {
|
} else if (error.response) {
|
||||||
return error.response.data.error.message;
|
if (error.response.data) {
|
||||||
|
if (error.response.data.message) {
|
||||||
|
return error.response.data.message;
|
||||||
|
} else if (error.response.data.error) {
|
||||||
|
return error.response.data.error.message;
|
||||||
|
}
|
||||||
|
return error.response.data;
|
||||||
|
}
|
||||||
|
return error.response;
|
||||||
} else {
|
} else {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +1,130 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const config = require('config');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const db = require('../models');
|
const db = require('../models');
|
||||||
|
const config = require('config');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
validateFile (file, name, license, nsfw) {
|
validateApiPublishRequest (body, files) {
|
||||||
|
if (!body) {
|
||||||
|
throw new Error('no body found in request');
|
||||||
|
}
|
||||||
|
if (!body.name) {
|
||||||
|
throw new Error('no name field found in request');
|
||||||
|
}
|
||||||
|
if (!body.nsfw) {
|
||||||
|
throw new Error('no nsfw field found in request');
|
||||||
|
}
|
||||||
|
if (!files) {
|
||||||
|
throw new Error('no files found in request');
|
||||||
|
}
|
||||||
|
if (!files.file) {
|
||||||
|
throw new Error('no file with key of [file] found in request');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validatePublishSubmission (file, claimName, nsfw) {
|
||||||
|
try {
|
||||||
|
module.exports.validateFile(file);
|
||||||
|
module.exports.validateClaimName(claimName);
|
||||||
|
module.exports.validateNSFW(nsfw);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateFile (file) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
throw new Error('No file was submitted or the key used was incorrect. Files posted through this route must use a key of "speech" or null');
|
logger.debug('publish > file validation > no file found');
|
||||||
|
throw new Error('no file provided');
|
||||||
|
}
|
||||||
|
// check the file name
|
||||||
|
if (/'/.test(file.name)) {
|
||||||
|
logger.debug('publish > file validation > file name had apostrophe in it');
|
||||||
|
throw new Error('apostrophes are not allowed in the file name');
|
||||||
}
|
}
|
||||||
// check file type and size
|
// check file type and size
|
||||||
switch (file.type) {
|
switch (file.type) {
|
||||||
case 'image/jpeg':
|
case 'image/jpeg':
|
||||||
|
case 'image/jpg':
|
||||||
case 'image/png':
|
case 'image/png':
|
||||||
|
if (file.size > 10000000) {
|
||||||
|
logger.debug('publish > file validation > .jpeg/.jpg/.png was too big');
|
||||||
|
throw new Error('Sorry, images are limited to 10 megabytes.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'image/gif':
|
case 'image/gif':
|
||||||
if (file.size > 50000000) {
|
if (file.size > 50000000) {
|
||||||
throw new Error('Your image exceeds the 50 megabyte limit.');
|
logger.debug('publish > file validation > .gif was too big');
|
||||||
|
throw new Error('Sorry, .gifs are limited to 50 megabytes.');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'video/mp4':
|
case 'video/mp4':
|
||||||
if (file.size > 50000000) {
|
if (file.size > 50000000) {
|
||||||
throw new Error('Your video exceeds the 50 megabyte limit.');
|
logger.debug('publish > file validation > .mp4 was too big');
|
||||||
|
throw new Error('Sorry, videos are limited to 50 megabytes.');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('The ' + file.Type + ' content type is not supported. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.');
|
logger.debug('publish > file validation > unrecognized file type');
|
||||||
|
throw new Error('The ' + file.type + ' content type is not supported. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.');
|
||||||
}
|
}
|
||||||
// validate claim name
|
return file;
|
||||||
const invalidCharacters = /[^A-Za-z0-9,-]/.exec(name);
|
},
|
||||||
|
validateClaimName (claimName) {
|
||||||
|
const invalidCharacters = /[^A-Za-z0-9,-]/.exec(claimName);
|
||||||
if (invalidCharacters) {
|
if (invalidCharacters) {
|
||||||
throw new Error('The url name you provided is not allowed. Only the following characters are allowed: A-Z, a-z, 0-9, and "-"');
|
throw new Error('The claim name you provided is not allowed. Only the following characters are allowed: A-Z, a-z, 0-9, and "-"');
|
||||||
}
|
}
|
||||||
// validate license
|
},
|
||||||
|
validateLicense (license) {
|
||||||
if ((license.indexOf('Public Domain') === -1) && (license.indexOf('Creative Commons') === -1)) {
|
if ((license.indexOf('Public Domain') === -1) && (license.indexOf('Creative Commons') === -1)) {
|
||||||
throw new Error('Only posts with a "Public Domain" license, or one of the Creative Commons licenses are eligible for publishing through spee.ch');
|
throw new Error('Only posts with a "Public Domain" or "Creative Commons" license are eligible for publishing through spee.ch');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
cleanseNSFW (nsfw) {
|
||||||
switch (nsfw) {
|
switch (nsfw) {
|
||||||
case true:
|
case true:
|
||||||
case false:
|
|
||||||
case 'true':
|
|
||||||
case 'false':
|
|
||||||
case 'on':
|
case 'on':
|
||||||
|
case 'true':
|
||||||
|
case 1:
|
||||||
|
case '1':
|
||||||
|
return true;
|
||||||
|
case false:
|
||||||
|
case 'false':
|
||||||
case 'off':
|
case 'off':
|
||||||
case 0:
|
case 0:
|
||||||
case '0':
|
case '0':
|
||||||
case 1:
|
return false;
|
||||||
case '1':
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new Error('NSFW value was not accepted. NSFW must be set to either true, false, "on", or "off"');
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createPublishParams (name, filePath, title, description, license, nsfw, channel) {
|
cleanseChannelName (channelName) {
|
||||||
logger.debug(`Creating Publish Parameters for "${name}"`);
|
if (channelName) {
|
||||||
const claimAddress = config.get('WalletConfig.LbryClaimAddress');
|
if (channelName.indexOf('@') !== 0) {
|
||||||
const defaultChannel = config.get('WalletConfig.DefaultChannel');
|
channelName = `@${channelName}`;
|
||||||
// filter nsfw and ensure it is a boolean
|
|
||||||
if (nsfw === false) {
|
|
||||||
nsfw = false;
|
|
||||||
} else if (typeof nsfw === 'string') {
|
|
||||||
if (nsfw.toLowerCase === 'false' || nsfw.toLowerCase === 'off' || nsfw === '0') {
|
|
||||||
nsfw = false;
|
|
||||||
}
|
}
|
||||||
} else if (nsfw === 0) {
|
|
||||||
nsfw = false;
|
|
||||||
} else {
|
|
||||||
nsfw = true;
|
|
||||||
}
|
}
|
||||||
// provide defaults for title & description
|
return channelName;
|
||||||
if (title === null || title === '') {
|
},
|
||||||
|
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, channelName) {
|
||||||
|
logger.debug(`Creating Publish Parameters`);
|
||||||
|
// provide defaults for title
|
||||||
|
if (title === null || title.trim() === '') {
|
||||||
title = name;
|
title = name;
|
||||||
}
|
}
|
||||||
|
// provide default for description
|
||||||
if (description === null || description.trim() === '') {
|
if (description === null || description.trim() === '') {
|
||||||
description = `${name} published via spee.ch`;
|
description = `${name} published via spee.ch`;
|
||||||
}
|
}
|
||||||
|
// provide default for license
|
||||||
|
if (license === null || license.trim() === '') {
|
||||||
|
license = 'All Rights Reserved';
|
||||||
|
}
|
||||||
// create the publish params
|
// create the publish params
|
||||||
const publishParams = {
|
const publishParams = {
|
||||||
name,
|
name,
|
||||||
|
@ -86,16 +138,12 @@ module.exports = {
|
||||||
license,
|
license,
|
||||||
nsfw,
|
nsfw,
|
||||||
},
|
},
|
||||||
claim_address: claimAddress,
|
claim_address: config.get('WalletConfig.LbryClaimAddress'),
|
||||||
};
|
};
|
||||||
// add channel if applicable
|
// add channel to params, if applicable
|
||||||
if (channel !== 'none') {
|
if (channelName) {
|
||||||
publishParams['channel_name'] = channel;
|
publishParams['channel_name'] = channelName;
|
||||||
} else {
|
|
||||||
publishParams['channel_name'] = defaultChannel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('publishParams:', publishParams);
|
|
||||||
return publishParams;
|
return publishParams;
|
||||||
},
|
},
|
||||||
deleteTemporaryFile (filePath) {
|
deleteTemporaryFile (filePath) {
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"express": "^4.15.2",
|
"express": "^4.15.2",
|
||||||
"express-handlebars": "^3.0.0",
|
"express-handlebars": "^3.0.0",
|
||||||
"express-session": "^1.15.5",
|
"express-session": "^1.15.5",
|
||||||
|
"form-data": "^2.3.1",
|
||||||
"helmet": "^3.8.1",
|
"helmet": "^3.8.1",
|
||||||
"mysql2": "^1.3.5",
|
"mysql2": "^1.3.5",
|
||||||
"nodemon": "^1.11.0",
|
"nodemon": "^1.11.0",
|
||||||
|
|
|
@ -2,46 +2,56 @@
|
||||||
|
|
||||||
// validation function which checks the proposed file's type, size, and name
|
// validation function which checks the proposed file's type, size, and name
|
||||||
function validateFile(file) {
|
function validateFile(file) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
throw new Error('no file provided');
|
console.log('no file found');
|
||||||
}
|
throw new Error('no file provided');
|
||||||
if (/'/.test(file.name)) {
|
}
|
||||||
throw new Error('apostrophes are not allowed in the file name');
|
if (/'/.test(file.name)) {
|
||||||
}
|
console.log('file name had apostrophe in it');
|
||||||
// validate size and type
|
throw new Error('apostrophes are not allowed in the file name');
|
||||||
switch (file.type) {
|
}
|
||||||
case 'image/jpeg':
|
// validate size and type
|
||||||
case 'image/jpg':
|
switch (file.type) {
|
||||||
case 'image/png':
|
case 'image/jpeg':
|
||||||
case 'image/gif':
|
case 'image/jpg':
|
||||||
if (file.size > 50000000){
|
case 'image/png':
|
||||||
throw new Error('Sorry, images are limited to 50 megabytes.');
|
if (file.size > 10000000){
|
||||||
}
|
console.log('file was too big');
|
||||||
break;
|
throw new Error('Sorry, images are limited to 10 megabytes.');
|
||||||
case 'video/mp4':
|
}
|
||||||
if (file.size > 50000000){
|
break;
|
||||||
throw new Error('Sorry, videos are limited to 50 megabytes.');
|
case 'image/gif':
|
||||||
}
|
if (file.size > 50000000){
|
||||||
break;
|
console.log('file was too big');
|
||||||
default:
|
throw new Error('Sorry, .gifs are limited to 50 megabytes.');
|
||||||
throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.')
|
}
|
||||||
}
|
break;
|
||||||
|
case 'video/mp4':
|
||||||
|
if (file.size > 50000000){
|
||||||
|
console.log('file was too big');
|
||||||
|
throw new Error('Sorry, videos are limited to 50 megabytes.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('file type is not supported');
|
||||||
|
throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// validation function that checks to make sure the claim name is valid
|
// validation function that checks to make sure the claim name is valid
|
||||||
function validateClaimName (name) {
|
function validateClaimName (name) {
|
||||||
// ensure a name was entered
|
// ensure a name was entered
|
||||||
if (name.length < 1) {
|
if (name.length < 1) {
|
||||||
throw new NameError("You must enter a name for your url");
|
throw new NameError("You must enter a name for your url");
|
||||||
}
|
}
|
||||||
// validate the characters in the 'name' field
|
// validate the characters in the 'name' field
|
||||||
const invalidCharacters = /[^A-Za-z0-9,-]/g.exec(name);
|
const invalidCharacters = /[^A-Za-z0-9,-]/g.exec(name);
|
||||||
if (invalidCharacters) {
|
if (invalidCharacters) {
|
||||||
throw new NameError('"' + invalidCharacters + '" characters are not allowed in the url.');
|
throw new NameError('"' + invalidCharacters + '" characters are not allowed in the url.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateChannelName (name) {
|
function validateChannelName (name) {
|
||||||
name = name.substring(name.indexOf('@') + 1);
|
name = name.substring(name.indexOf('@') + 1);
|
||||||
// ensure a name was entered
|
// ensure a name was entered
|
||||||
if (name.length < 1) {
|
if (name.length < 1) {
|
||||||
throw new ChannelNameError("You must enter a name for your channel");
|
throw new ChannelNameError("You must enter a name for your channel");
|
||||||
|
@ -60,9 +70,9 @@ function validatePassword (password) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanseClaimName(name) {
|
function cleanseClaimName(name) {
|
||||||
name = name.replace(/\s+/g, '-'); // replace spaces with dashes
|
name = name.replace(/\s+/g, '-'); // replace spaces with dashes
|
||||||
name = name.replace(/[^A-Za-z0-9-]/g, ''); // remove all characters that are not A-Z, a-z, 0-9, or '-'
|
name = name.replace(/[^A-Za-z0-9-]/g, ''); // remove all characters that are not A-Z, a-z, 0-9, or '-'
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validation functions to check claim & channel name eligibility as the inputs change
|
// validation functions to check claim & channel name eligibility as the inputs change
|
||||||
|
@ -99,14 +109,14 @@ function checkAvailability(name, successDisplayElement, errorDisplayElement, val
|
||||||
// check to make sure it is available
|
// check to make sure it is available
|
||||||
isNameAvailable(name, apiUrl)
|
isNameAvailable(name, apiUrl)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
console.log('result:', result)
|
console.log('result:', result)
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
hideError(errorDisplayElement);
|
hideError(errorDisplayElement);
|
||||||
showSuccess(successDisplayElement)
|
showSuccess(successDisplayElement)
|
||||||
} else {
|
} else {
|
||||||
hideSuccess(successDisplayElement);
|
hideSuccess(successDisplayElement);
|
||||||
showError(errorDisplayElement, errorMessage);
|
showError(errorDisplayElement, errorMessage);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
hideSuccess(successDisplayElement);
|
hideSuccess(successDisplayElement);
|
||||||
|
@ -119,58 +129,69 @@ function checkAvailability(name, successDisplayElement, errorDisplayElement, val
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkClaimName(name){
|
function checkClaimName(name){
|
||||||
const successDisplayElement = document.getElementById('input-success-claim-name');
|
const successDisplayElement = document.getElementById('input-success-claim-name');
|
||||||
const errorDisplayElement = document.getElementById('input-error-claim-name');
|
const errorDisplayElement = document.getElementById('input-error-claim-name');
|
||||||
checkAvailability(name, successDisplayElement, errorDisplayElement, validateClaimName, isNameAvailable, 'Sorry, that url ending has been taken by another user', '/api/isClaimAvailable/');
|
checkAvailability(name, successDisplayElement, errorDisplayElement, validateClaimName, isNameAvailable, 'Sorry, that url ending has been taken', '/api/isClaimAvailable/');
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkChannelName(name){
|
function checkChannelName(name){
|
||||||
const successDisplayElement = document.getElementById('input-success-channel-name');
|
const successDisplayElement = document.getElementById('input-success-channel-name');
|
||||||
const errorDisplayElement = document.getElementById('input-error-channel-name');
|
const errorDisplayElement = document.getElementById('input-error-channel-name');
|
||||||
name = `@${name}`;
|
name = `@${name}`;
|
||||||
checkAvailability(name, successDisplayElement, errorDisplayElement, validateChannelName, isNameAvailable, 'Sorry, that Channel has been taken by another user', '/api/isChannelAvailable/');
|
checkAvailability(name, successDisplayElement, errorDisplayElement, validateChannelName, isNameAvailable, 'Sorry, that Channel name has been taken by another user', '/api/isChannelAvailable/');
|
||||||
}
|
}
|
||||||
|
|
||||||
// validation function which checks all aspects of the publish submission
|
// validation function which checks all aspects of the publish submission
|
||||||
function validateFilePublishSubmission(stagedFiles, claimName, channelName){
|
function validateFilePublishSubmission(stagedFiles, claimName, channelName){
|
||||||
return new Promise(function (resolve, reject) {
|
console.log(`validating publish submission > name: ${claimName} channel: ${channelName} file:`, stagedFiles);
|
||||||
// 1. make sure only 1 file was selected
|
return new Promise(function (resolve, reject) {
|
||||||
if (!stagedFiles) {
|
// 1. make sure 1 file was staged
|
||||||
return reject(new FileError("Please select a file"));
|
if (!stagedFiles) {
|
||||||
} else if (stagedFiles.length > 1) {
|
reject(new FileError("Please select a file"));
|
||||||
return reject(new FileError("Only one file is allowed at a time"));
|
return;
|
||||||
}
|
} else if (stagedFiles.length > 1) {
|
||||||
// 2. validate the file's name, type, and size
|
reject(new FileError("Only one file is allowed at a time"));
|
||||||
try {
|
return;
|
||||||
validateFile(stagedFiles[0]);
|
}
|
||||||
} catch (error) {
|
// 2. validate the file's name, type, and size
|
||||||
return reject(error);
|
try {
|
||||||
}
|
validateFile(stagedFiles[0]);
|
||||||
// 3. validate that a channel was chosen
|
} catch (error) {
|
||||||
if (channelName === 'new' || channelName === 'login') {
|
reject(error);
|
||||||
return reject(new ChannelNameError("Please select a valid channel"));
|
return;
|
||||||
|
}
|
||||||
|
// 3. validate that a channel was chosen
|
||||||
|
if (channelName === 'new' || channelName === 'login') {
|
||||||
|
reject(new ChannelNameError("Please log in to a channel"));
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
// 4. validate the claim name
|
// 4. validate the claim name
|
||||||
try {
|
try {
|
||||||
validateClaimName(claimName);
|
validateClaimName(claimName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return reject(error);
|
reject(error);
|
||||||
}
|
return;
|
||||||
// if all validation passes, check availability of the name
|
}
|
||||||
isNameAvailable(claimName, '/api/isClaimAvailable/')
|
// if all validation passes, check availability of the name (note: do we need to re-validate channel name vs. credentials as well?)
|
||||||
.then(() => {
|
return isNameAvailable(claimName, '/api/isClaimAvailable/')
|
||||||
resolve();
|
.then(result => {
|
||||||
})
|
if (result) {
|
||||||
.catch(error => {
|
resolve();
|
||||||
reject(error);
|
} else {
|
||||||
});
|
reject(new NameError('that url ending is already taken'));
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// validation function which checks all aspects of the publish submission
|
// validation function which checks all aspects of a new channel submission
|
||||||
function validateNewChannelSubmission(channelName, password){
|
function validateNewChannelSubmission(userName, password){
|
||||||
|
const channelName = `@${userName}`;
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
// 1. validate name
|
// 1. validate name
|
||||||
try {
|
try {
|
||||||
validateChannelName(channelName);
|
validateChannelName(channelName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -184,12 +205,17 @@ function validateNewChannelSubmission(channelName, password){
|
||||||
}
|
}
|
||||||
// 3. if all validation passes, check availability of the name
|
// 3. if all validation passes, check availability of the name
|
||||||
isNameAvailable(channelName, '/api/isChannelAvailable/') // validate the availability
|
isNameAvailable(channelName, '/api/isChannelAvailable/') // validate the availability
|
||||||
.then(() => {
|
.then(result => {
|
||||||
console.log('channel is avaliable');
|
if (result) {
|
||||||
resolve();
|
console.log('channel is available');
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
console.log('channel is not available');
|
||||||
|
reject(new ChannelNameError('that channel name has already been taken'));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch( error => {
|
.catch( error => {
|
||||||
console.log('error: channel is not avaliable');
|
console.log('error evaluating channel name availability', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,10 +4,10 @@ const multipartMiddleware = multipart();
|
||||||
const db = require('../models');
|
const db = require('../models');
|
||||||
const { publish } = require('../controllers/publishController.js');
|
const { publish } = require('../controllers/publishController.js');
|
||||||
const { getClaimList, resolveUri } = require('../helpers/lbryApi.js');
|
const { getClaimList, resolveUri } = require('../helpers/lbryApi.js');
|
||||||
const { createPublishParams, validateFile, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js');
|
const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseNSFW, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js');
|
||||||
const errorHandlers = require('../helpers/errorHandlers.js');
|
const errorHandlers = require('../helpers/errorHandlers.js');
|
||||||
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
|
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
|
||||||
const { authenticateApiPublish } = require('../auth/authentication.js');
|
const { authenticateChannelCredentials } = require('../auth/authentication.js');
|
||||||
|
|
||||||
module.exports = (app) => {
|
module.exports = (app) => {
|
||||||
// route to run a claim_list request on the daemon
|
// route to run a claim_list request on the daemon
|
||||||
|
@ -71,52 +71,80 @@ module.exports = (app) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// route to run a publish request on the daemon
|
// route to run a publish request on the daemon
|
||||||
app.post('/api/publish', multipartMiddleware, ({ body, files, headers, ip, originalUrl }, res) => {
|
app.post('/api/publish', multipartMiddleware, (req, res) => {
|
||||||
// google analytics
|
logger.debug(req);
|
||||||
sendGoogleAnalytics('PUBLISH', headers, ip, originalUrl);
|
const body = req.body;
|
||||||
// validate that a file was provided
|
const files = req.files;
|
||||||
const file = files.speech || files.null;
|
|
||||||
const name = body.name || file.name.substring(0, file.name.indexOf('.'));
|
|
||||||
const title = body.title || null;
|
|
||||||
const description = body.description || null;
|
|
||||||
const license = body.license || 'No License Provided';
|
|
||||||
const nsfw = body.nsfw || null;
|
|
||||||
const channelName = body.channelName || 'none';
|
|
||||||
const channelPassword = body.channelPassword || null;
|
|
||||||
logger.debug(`name: ${name}, license: ${license}, nsfw: ${nsfw}`);
|
|
||||||
try {
|
try {
|
||||||
validateFile(file, name, license, nsfw);
|
validateApiPublishRequest(body, files);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
postToStats('publish', originalUrl, ip, null, null, error.message);
|
logger.debug('publish request rejected, insufficient request parameters');
|
||||||
logger.debug('rejected >>', error.message);
|
res.status(400).json({success: false, message: error.message});
|
||||||
res.status(400).send(error.message);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// required inputs
|
||||||
|
const file = files.file;
|
||||||
const fileName = file.name;
|
const fileName = file.name;
|
||||||
const filePath = file.path;
|
const filePath = file.path;
|
||||||
const fileType = file.type;
|
const fileType = file.type;
|
||||||
// channel authorization
|
const name = body.name;
|
||||||
authenticateApiPublish(channelName, channelPassword)
|
let nsfw = body.nsfw;
|
||||||
|
// cleanse nsfw
|
||||||
|
nsfw = cleanseNSFW(nsfw);
|
||||||
|
// validate file, name, license, and nsfw
|
||||||
|
try {
|
||||||
|
validatePublishSubmission(file, name, nsfw);
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug('publish request rejected');
|
||||||
|
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;
|
||||||
|
let channelName = body.channelName || null;
|
||||||
|
channelName = cleanseChannelName(channelName);
|
||||||
|
const channelPassword = body.channelPassword || null;
|
||||||
|
logger.debug(`license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}"`);
|
||||||
|
// check channel authorization
|
||||||
|
authenticateChannelCredentials(channelName, channelPassword)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
res.status(401).send('Authentication failed, you do not have access to that channel');
|
throw new Error('Authentication failed, you do not have access to that channel');
|
||||||
throw new Error('authentication failed');
|
|
||||||
}
|
}
|
||||||
return createPublishParams(name, filePath, title, description, license, nsfw, channelName);
|
// make sure the claim name is available
|
||||||
|
return checkClaimNameAvailability(name);
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('That name is already in use by spee.ch.');
|
||||||
|
}
|
||||||
|
// create publish parameters object
|
||||||
|
return createPublishParams(filePath, name, title, description, license, nsfw, channelName);
|
||||||
})
|
})
|
||||||
// create publish parameters object
|
|
||||||
.then(publishParams => {
|
.then(publishParams => {
|
||||||
|
logger.debug('publishParams:', publishParams);
|
||||||
|
// publish the asset
|
||||||
return publish(publishParams, fileName, fileType);
|
return publish(publishParams, fileName, fileType);
|
||||||
})
|
})
|
||||||
// publish the asset
|
|
||||||
.then(result => {
|
.then(result => {
|
||||||
postToStats('publish', originalUrl, ip, null, null, 'success');
|
// postToStats('publish', originalUrl, ip, null, null, 'success');
|
||||||
res.status(200).json(result);
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: {
|
||||||
|
url : `spee.ch/${result.claim_id}/${name}`,
|
||||||
|
lbryTx: result,
|
||||||
|
},
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('publish api error', error);
|
logger.error('publish api error', error);
|
||||||
|
res.status(400).json({success: false, message: error.message});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// route to get a short claim id from long claim Id
|
// route to get a short claim id from long claim Id
|
||||||
app.get('/api/shortClaimId/:longId/:name', ({ originalUrl, ip, params }, res) => {
|
app.get('/api/shortClaimId/:longId/:name', ({ originalUrl, ip, params }, res) => {
|
||||||
// serve content
|
// serve content
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const publishController = require('../controllers/publishController.js');
|
const { publish } = require('../controllers/publishController.js');
|
||||||
const publishHelpers = require('../helpers/publishHelpers.js');
|
const { createPublishParams } = require('../helpers/publishHelpers.js');
|
||||||
const errorHandlers = require('../helpers/errorHandlers.js');
|
const errorHandlers = require('../helpers/errorHandlers.js');
|
||||||
const { postToStats } = require('../controllers/statsController.js');
|
const { postToStats } = require('../controllers/statsController.js');
|
||||||
|
|
||||||
|
@ -40,12 +40,13 @@ module.exports = (app, siofu, hostedContentPath) => {
|
||||||
NOTE: need to validate that client has the credentials to the channel they chose
|
NOTE: need to validate that client has the credentials to the channel they chose
|
||||||
otherwise they could circumvent security client side.
|
otherwise they could circumvent security client side.
|
||||||
*/
|
*/
|
||||||
|
let channelName = file.meta.cannel;
|
||||||
|
if (channelName === 'none') channelName = null;
|
||||||
// prepare the publish parameters
|
// prepare the publish parameters
|
||||||
const publishParams = publishHelpers.createPublishParams(file.meta.name, file.pathName, file.meta.title, file.meta.description, file.meta.license, file.meta.nsfw, file.meta.channel);
|
const publishParams = createPublishParams(file.pathName, file.meta.name, file.meta.title, file.meta.description, file.meta.license, file.meta.nsfw, channelName);
|
||||||
logger.debug(publishParams);
|
logger.debug(publishParams);
|
||||||
// publish the file
|
// publish the file
|
||||||
publishController.publish(publishParams, file.name, file.meta.type)
|
publish(publishParams, file.name, file.meta.type)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
postToStats('PUBLISH', '/', null, null, null, 'success');
|
postToStats('PUBLISH', '/', null, null, null, 'success');
|
||||||
socket.emit('publish-complete', { name: publishParams.name, result });
|
socket.emit('publish-complete', { name: publishParams.name, result });
|
||||||
|
|
Loading…
Reference in a new issue