added testing package and wrote some publish tests
This commit is contained in:
parent
c680a6efa3
commit
75fac0e594
5 changed files with 209 additions and 132 deletions
|
@ -2,6 +2,7 @@ const logger = require('winston');
|
|||
const db = require('../models');
|
||||
const lbryApi = require('../helpers/lbryApi.js');
|
||||
const publishHelpers = require('../helpers/publishHelpers.js');
|
||||
const config = require('../config/speechConfig.js');
|
||||
|
||||
module.exports = {
|
||||
publish (publishParams, fileName, fileType) {
|
||||
|
@ -84,4 +85,45 @@ module.exports = {
|
|||
});
|
||||
});
|
||||
},
|
||||
checkClaimNameAvailability (name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// find any records where the name is used
|
||||
db.File.findAll({ where: { name } })
|
||||
.then(result => {
|
||||
if (result.length >= 1) {
|
||||
const claimAddress = config.wallet.lbryClaimAddress;
|
||||
// filter out any results that were not published from spee.ch's wallet address
|
||||
const filteredResult = result.filter((claim) => {
|
||||
return (claim.address === claimAddress);
|
||||
});
|
||||
// return based on whether any non-spee.ch claims were left
|
||||
if (filteredResult.length >= 1) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
checkChannelAvailability (name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// find any records where the name is used
|
||||
db.Channel.findAll({ where: { channelName: name } })
|
||||
.then(result => {
|
||||
if (result.length >= 1) {
|
||||
return resolve(false);
|
||||
}
|
||||
resolve(true);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,41 +1,96 @@
|
|||
const logger = require('winston');
|
||||
const fs = require('fs');
|
||||
const db = require('../models');
|
||||
const config = require('../config/speechConfig.js');
|
||||
|
||||
module.exports = {
|
||||
validateApiPublishRequest (body, files) {
|
||||
if (!body) {
|
||||
throw new Error('no body found in request');
|
||||
}
|
||||
if (!body.name) {
|
||||
parsePublishApiRequestBody ({name, nsfw, license, title, description, thumbnail}) {
|
||||
// validate name
|
||||
if (!name) {
|
||||
throw new Error('no name field found in request');
|
||||
}
|
||||
if (!files) {
|
||||
throw new Error('no files found in request');
|
||||
const invalidNameCharacters = /[^A-Za-z0-9,-]/.exec(name);
|
||||
if (invalidNameCharacters) {
|
||||
throw new Error('The claim name you provided is not allowed. Only the following characters are allowed: A-Z, a-z, 0-9, and "-"');
|
||||
}
|
||||
if (!files.file) {
|
||||
// optional parameters
|
||||
nsfw = (nsfw === 'true');
|
||||
license = license || null;
|
||||
title = title || null;
|
||||
description = description || null;
|
||||
thumbnail = thumbnail || null;
|
||||
// return results
|
||||
return {
|
||||
name,
|
||||
nsfw,
|
||||
license,
|
||||
title,
|
||||
description,
|
||||
thumbnail,
|
||||
};
|
||||
},
|
||||
parsePublishApiRequestFiles ({file}) {
|
||||
// make sure a file was provided
|
||||
if (!file) {
|
||||
throw new Error('no file with key of [file] found in request');
|
||||
}
|
||||
},
|
||||
validatePublishSubmission (file, claimName) {
|
||||
try {
|
||||
module.exports.validateFile(file);
|
||||
module.exports.validateClaimName(claimName);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
if (!file.path) {
|
||||
throw new Error('no file path found');
|
||||
}
|
||||
},
|
||||
validateFile (file) {
|
||||
if (!file) {
|
||||
logger.debug('publish > file validation > no file found');
|
||||
throw new Error('no file provided');
|
||||
if (!file.type) {
|
||||
throw new Error('no file type found');
|
||||
}
|
||||
// check the file name
|
||||
if (!file.size) {
|
||||
throw new Error('no file type found');
|
||||
}
|
||||
// validate 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');
|
||||
}
|
||||
// validate the file
|
||||
module.exports.validateFileTypeAndSize(file);
|
||||
// return results
|
||||
return {
|
||||
fileName: file.name,
|
||||
filePath: file.path,
|
||||
fileType: file.type,
|
||||
};
|
||||
},
|
||||
parsePublishApiChannel ({channelName, channelPassword}, user) {
|
||||
let anonymous = (channelName === null || channelName === undefined || channelName === '');
|
||||
if (user) {
|
||||
channelName = user.channelName || null;
|
||||
} else {
|
||||
channelName = channelName || null;
|
||||
}
|
||||
channelPassword = channelPassword || null;
|
||||
let 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;
|
||||
}
|
||||
}
|
||||
// cleanse channel name
|
||||
if (channelName) {
|
||||
if (channelName.indexOf('@') !== 0) {
|
||||
channelName = `@${channelName}`;
|
||||
}
|
||||
}
|
||||
return {
|
||||
channelName,
|
||||
channelPassword,
|
||||
skipAuth,
|
||||
};
|
||||
},
|
||||
validateFileTypeAndSize (file) {
|
||||
// check file type and size
|
||||
switch (file.type) {
|
||||
case 'image/jpeg':
|
||||
|
@ -64,25 +119,6 @@ module.exports = {
|
|||
}
|
||||
return file;
|
||||
},
|
||||
validateClaimName (claimName) {
|
||||
const invalidCharacters = /[^A-Za-z0-9,-]/.exec(claimName);
|
||||
if (invalidCharacters) {
|
||||
throw new Error('The claim name you provided is not allowed. Only the following characters are allowed: A-Z, a-z, 0-9, and "-"');
|
||||
}
|
||||
},
|
||||
validateLicense (license) {
|
||||
if ((license.indexOf('Public Domain') === -1) && (license.indexOf('Creative Commons') === -1)) {
|
||||
throw new Error('Only posts with a "Public Domain" or "Creative Commons" license are eligible for publishing through spee.ch');
|
||||
}
|
||||
},
|
||||
cleanseChannelName (channelName) {
|
||||
if (channelName) {
|
||||
if (channelName.indexOf('@') !== 0) {
|
||||
channelName = `@${channelName}`;
|
||||
}
|
||||
}
|
||||
return channelName;
|
||||
},
|
||||
createPublishParams (filePath, name, title, description, license, nsfw, thumbnail, channelName) {
|
||||
logger.debug(`Creating Publish Parameters`);
|
||||
// provide defaults for title
|
||||
|
@ -131,45 +167,5 @@ module.exports = {
|
|||
logger.debug(`successfully deleted ${filePath}`);
|
||||
});
|
||||
},
|
||||
checkClaimNameAvailability (name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// find any records where the name is used
|
||||
db.File.findAll({ where: { name } })
|
||||
.then(result => {
|
||||
if (result.length >= 1) {
|
||||
const claimAddress = config.wallet.lbryClaimAddress;
|
||||
// filter out any results that were not published from spee.ch's wallet address
|
||||
const filteredResult = result.filter((claim) => {
|
||||
return (claim.address === claimAddress);
|
||||
});
|
||||
// return based on whether any non-spee.ch claims were left
|
||||
if (filteredResult.length >= 1) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
checkChannelAvailability (name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// find any records where the name is used
|
||||
db.Channel.findAll({ where: { channelName: name } })
|
||||
.then(result => {
|
||||
if (result.length >= 1) {
|
||||
return resolve(false);
|
||||
}
|
||||
resolve(true);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "a single-serving site that reads and publishes images to and from the LBRY blockchain",
|
||||
"main": "speech.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "mocha",
|
||||
"start": "node speech.js",
|
||||
"lint": "eslint .",
|
||||
"fix": "eslint . --fix",
|
||||
|
@ -55,6 +55,7 @@
|
|||
"eslint-plugin-promise": "3.5.0",
|
||||
"eslint-plugin-react": "6.10.3",
|
||||
"eslint-plugin-standard": "3.0.1",
|
||||
"husky": "^0.13.4"
|
||||
"husky": "^0.13.4",
|
||||
"mocha": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ const multipart = require('connect-multiparty');
|
|||
const config = require('../config/speechConfig.js');
|
||||
const multipartMiddleware = multipart({uploadDir: config.files.uploadDirectory});
|
||||
const db = require('../models');
|
||||
const { publish } = require('../controllers/publishController.js');
|
||||
const { checkClaimNameAvailability, checkChannelAvailability, publish } = require('../controllers/publishController.js');
|
||||
const { getClaimList, resolveUri } = require('../helpers/lbryApi.js');
|
||||
const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js');
|
||||
const { createPublishParams, parsePublishApiRequestBody, parsePublishApiRequestFiles, parsePublishApiChannel } = require('../helpers/publishHelpers.js');
|
||||
const errorHandlers = require('../helpers/errorHandlers.js');
|
||||
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
|
||||
const { authenticateOrSkip } = require('../auth/authentication.js');
|
||||
|
@ -73,56 +73,18 @@ module.exports = (app) => {
|
|||
});
|
||||
// route to run a publish request on the daemon
|
||||
app.post('/api/publish', multipartMiddleware, ({ body, files, ip, originalUrl, user }, res) => {
|
||||
let file, fileName, filePath, fileType, name, nsfw, license, title, description, thumbnail, anonymous, skipAuth, channelName, channelPassword;
|
||||
// validate that mandatory parts of the request are present
|
||||
let name, fileName, filePath, fileType, nsfw, license, title, description, thumbnail, skipAuth, channelName, channelPassword;
|
||||
// validate the body and files of the request
|
||||
try {
|
||||
validateApiPublishRequest(body, files);
|
||||
// validateApiPublishRequest(body, files);
|
||||
({name, nsfw, license, title, description, thumbnail} = parsePublishApiRequestBody(body));
|
||||
({fileName, filePath, fileType} = parsePublishApiRequestFiles(files));
|
||||
({channelName, channelPassword, skipAuth} = parsePublishApiChannel(body, user));
|
||||
} catch (error) {
|
||||
logger.debug('publish request rejected, insufficient request parameters');
|
||||
res.status(400).json({success: false, message: error.message});
|
||||
return;
|
||||
return res.status(400).json({success: false, message: error.message});
|
||||
}
|
||||
// validate file, name, license, and nsfw
|
||||
file = files.file;
|
||||
fileName = file.path.substring(file.path.lastIndexOf('/') + 1);
|
||||
filePath = file.path;
|
||||
fileType = file.type;
|
||||
name = body.name;
|
||||
nsfw = (body.nsfw === 'true');
|
||||
try {
|
||||
validatePublishSubmission(file, name, nsfw);
|
||||
} catch (error) {
|
||||
logger.debug('publish request rejected');
|
||||
res.status(400).json({success: false, message: error.message});
|
||||
return;
|
||||
}
|
||||
// optional inputs
|
||||
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);
|
||||
|
||||
logger.debug(`/api/publish > name: ${name}, license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}" nsfw: "${nsfw}"`);
|
||||
// check channel authorization
|
||||
authenticateOrSkip(skipAuth, channelName, channelPassword)
|
||||
|
|
76
test/publishApiTests.js
Normal file
76
test/publishApiTests.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
const assert = require('assert');
|
||||
|
||||
describe('Array', function () {
|
||||
describe('indexOf()', function () {
|
||||
it('should return -1 when the value is not present', function () {
|
||||
assert.equal(-1, [1, 2, 3].indexOf(4));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('controllers', function () {
|
||||
describe('api/publish', function () {
|
||||
describe('publishHelpers.js', function () {
|
||||
const publishHelpers = require('../helpers/publishHelpers.js');
|
||||
|
||||
describe('#parsePublishApiRequestBody()', function () {
|
||||
it('should throw an error if no body', function () {
|
||||
assert.throws(publishHelpers.parsePublishApiRequestBody.bind(this, null), Error);
|
||||
});
|
||||
it('should throw an error if no body.name', function () {
|
||||
const bodyNoName = {};
|
||||
assert.throws(publishHelpers.parsePublishApiRequestBody.bind(this, bodyNoName), Error);
|
||||
});
|
||||
it('should throw an error if no body.name', function () {
|
||||
const body = {
|
||||
name: 'bob',
|
||||
};
|
||||
assert.doesNotThrow(publishHelpers.parsePublishApiRequestBody.bind(this, body), Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#parsePublishApiRequestFiles()', function () {
|
||||
it('should throw an error if no files', function () {
|
||||
assert.throws(publishHelpers.parsePublishApiRequestFiles.bind(this, null), Error);
|
||||
});
|
||||
it('should throw an error if no files.file', function () {
|
||||
const filesNoFile = {};
|
||||
assert.throws(publishHelpers.parsePublishApiRequestFiles.bind(this, filesNoFile), Error);
|
||||
});
|
||||
it('should throw an error if file.size is too large', function () {
|
||||
const filesTooBig = {
|
||||
file: {
|
||||
name: 'file.jpg',
|
||||
path: '/path/to/file.jpg',
|
||||
type: 'image/jpg',
|
||||
size: 10000001,
|
||||
},
|
||||
};
|
||||
assert.throws(publishHelpers.parsePublishApiRequestFiles.bind(this, filesTooBig), Error);
|
||||
});
|
||||
it('should throw error if not an accepted file type', function () {
|
||||
const filesNoProblems = {
|
||||
file: {
|
||||
name: 'file.jpg',
|
||||
path: '/path/to/file.jpg',
|
||||
type: 'someType/ext',
|
||||
size: 10000000,
|
||||
},
|
||||
};
|
||||
assert.throws(publishHelpers.parsePublishApiRequestFiles.bind(this, filesNoProblems), Error);
|
||||
});
|
||||
it('should throw NO error if no problems', function () {
|
||||
const filesNoProblems = {
|
||||
file: {
|
||||
name: 'file.jpg',
|
||||
path: '/path/to/file.jpg',
|
||||
type: 'image/jpg',
|
||||
size: 10000000,
|
||||
},
|
||||
};
|
||||
assert.doesNotThrow(publishHelpers.parsePublishApiRequestFiles.bind(this, filesNoProblems), Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue