Merge pull request #41 from lbryio/api-post-route

Api post route
This commit is contained in:
Bill Bittner 2017-06-26 17:24:41 -07:00 committed by GitHub
commit f0958cd212
16 changed files with 165 additions and 91 deletions

3
.gitignore vendored
View file

@ -1 +1,2 @@
node_modules node_modules
ApiPublishTest.html

View file

@ -32,5 +32,26 @@ spee.ch is a single-serving site that reads and publishes images to and from the
* To view a batch of files at a claim * To view a batch of files at a claim
* E.g. spee.ch/doitlive/all * E.g. spee.ch/doitlive/all
## API
#### GET
* /api/resolve/:name
* a successfull request returns the resolve results for the claim at that name in JSON format
* /api/claim_list/:name
* a successfull request returns a list of claims at that claim name in JSON format
#### POST
* /api/publish
* request parameters:
* body (form-data):
* claim: string (optional, defults to the file's name sans extension)
* license: string (optional, defaults to "No License Provided")
* nsfw: string ("on"/"off") or boolean (true/false). (optional, defaults `true`)
* files:
* (the `files` object submitted must use "file1" or "null" as the key for the file's value object)
* a successfull request will return the transaction details resulting from your published claim in JSON format
## bugs ## bugs
If you find a bug or experience a problem, please report your issue here on github and find us in the lbry slack! If you find a bug or experience a problem, please report your issue here on github and find us in the lbry slack!

View file

@ -1,6 +1,6 @@
{ {
"WalletConfig": { "WalletConfig": {
"lbryAddress": "LBRY_WALLET_ADDRESS" "LbryAddress": "LBRY_WALLET_ADDRESS"
}, },
"Database": { "Database": {
"MySqlConnectionUri": "MYSQL_CONNECTION_STRING" "MySqlConnectionUri": "MYSQL_CONNECTION_STRING"

View file

@ -1,9 +1,9 @@
{ {
"WalletConfig": { "WalletConfig": {
"lbryAddress": "none" "LbryAddress": "none"
}, },
"AnalyticsConfig":{ "AnalyticsConfig":{
"googleId": "none" "GoogleId": "none"
}, },
"Database": { "Database": {
"MySqlConnectionUri": "none", "MySqlConnectionUri": "none",

View file

@ -1,9 +1,9 @@
{ {
"WalletConfig": { "WalletConfig": {
"lbryAddress": "none" "LbryAddress": "none"
}, },
"AnalyticsConfig":{ "AnalyticsConfig":{
"googleId": "UA-100747990-1" "GoogleId": "UA-100747990-1"
}, },
"Database": { "Database": {
"MySqlConnectionUri": "none", "MySqlConnectionUri": "none",

View file

@ -1,9 +1,9 @@
{ {
"WalletConfig": { "WalletConfig": {
"lbryAddress": "none" "LbryAddress": "none"
}, },
"AnalyticsConfig":{ "AnalyticsConfig":{
"googleId": "UA-60403362-2" "GoogleId": "UA-60403362-2"
}, },
"Database": { "Database": {
"MySqlConnectionUri": "none", "MySqlConnectionUri": "none",

View file

@ -1,9 +1,9 @@
{ {
"WalletConfig": { "WalletConfig": {
"lbryAddress": "none" "LbryAddress": "none"
}, },
"AnalyticsConfig":{ "AnalyticsConfig":{
"googleId": "UA-100747990-1" "GoogleId": "UA-100747990-1"
}, },
"Database": { "Database": {
"MySqlConnectionUri": "none", "MySqlConnectionUri": "none",

View file

@ -1,32 +1,8 @@
const fs = require('fs'); const fs = require('fs');
const logger = require('winston'); const logger = require('winston');
const lbryApi = require('../helpers/libraries/lbryApi.js'); const lbryApi = require('../helpers/libraries/lbryApi.js');
const config = require('config');
const walledAddress = config.get('WalletConfig.lbryAddress');
const errorHandlers = require('../helpers/libraries/errorHandlers.js');
const db = require('../models'); const db = require('../models');
function createPublishParams (claim, filePath, license, nsfw) {
logger.debug(`Creating Publish Parameters for "${claim}"`);
const publishParams = {
name : claim,
file_path: filePath,
bid : 0.01,
metadata : {
description: `${claim} published via spee.ch`,
title : claim,
author : 'spee.ch',
language : 'en',
license,
nsfw,
},
claim_address : walledAddress,
change_address: walledAddress,
};
logger.debug('publishParams:', publishParams);
return publishParams;
}
function deleteTemporaryFile (filePath) { function deleteTemporaryFile (filePath) {
fs.unlink(filePath, err => { fs.unlink(filePath, err => {
if (err) throw err; if (err) throw err;
@ -49,51 +25,45 @@ function upsert (Model, values, condition) {
} }
module.exports = { module.exports = {
publish (name, fileName, filePath, fileType, license, nsfw, socket, visitor) { publish: (publishParams, fileName, fileType) => {
console.log('nsfw:', nsfw); const deferred = new Promise((resolve, reject) => {
// validate nsfw // 1. publish the file
if (typeof nsfw === 'string') { lbryApi
nsfw = (nsfw.toLowerCase() === 'true'); .publishClaim(publishParams)
} .then(result => {
// update the client logger.info(`Successfully published ${fileName}`, result);
socket.emit('publish-status', 'Your image is being published (this might take a second)...'); // 2. update old record of create new one (update is in case the claim has been published before by this daemon)
// send to analytics upsert(
visitor.event('Publish Route', 'Publish Request', filePath).send(); db.File,
// create the publish object {
const publishParams = createPublishParams(name, filePath, license, nsfw); name : publishParams.name,
// 1. publish the file claimId : result.claim_id,
lbryApi outpoint: `${result.txid}:${result.nout}`,
.publishClaim(publishParams, fileName, fileType) height : 0,
.then(result => { fileName,
logger.info(`Successfully published ${fileName}`, result); filePath: publishParams.file_path,
// google analytics fileType,
visitor.event('Publish Route', 'Publish Success', filePath).send(); nsfw : publishParams.metadata.nsfw,
// 2. update old record of create new one (update is in case the claim has been published before by this daemon) },
upsert( { name: publishParams.name, claimId: result.claim_id }
db.File, ).then(() => {
{ // resolve the promise with the result from lbryApi.publishClaim;
name, resolve(result);
claimId : result.claim_id, })
outpoint: `${result.txid}:${result.nout}`, .catch(error => {
height : 0, logger.error('Sequelize findOne error', error);
fileName, // reject the promise
filePath, reject(error);
fileType, });
nsfw, })
}, .catch(error => {
{ name, claimId: result.claim_id } logger.error(`Error publishing ${fileName}`, error);
).catch(error => { // delete the local file
logger.error('Sequelize findOne error', error); deleteTemporaryFile(publishParams.file_path);
// reject the promise
reject(error);
}); });
// update client });
socket.emit('publish-complete', { name, result }); return deferred;
})
.catch(error => {
logger.error(`Error publishing ${fileName}`, error);
// google analytics
visitor.event('Publish Route', 'Publish Failure', filePath).send();
socket.emit('publish-failure', errorHandlers.handlePublishError(error));
deleteTemporaryFile(filePath);
});
}, },
}; };

View file

@ -8,7 +8,7 @@ module.exports = {
} else if (error.response) { } else if (error.response) {
res.status(error.response.status).send(error.response.data.error.message); res.status(error.response.status).send(error.response.data.error.message);
} else if (error.code === 'ECONNREFUSED') { } else if (error.code === 'ECONNREFUSED') {
res.status(400).send('Connection refused. The daemon may not be running.'); res.status(503).send('Connection refused. The daemon may not be running.');
} else { } else {
res.status(400).send(JSON.stringify(error)); res.status(400).send(JSON.stringify(error));
} }

View file

@ -2,8 +2,8 @@ const axios = require('axios');
const logger = require('winston'); const logger = require('winston');
module.exports = { module.exports = {
publishClaim (publishParams, fileName, fileType) { publishClaim (publishParams) {
logger.debug(`Publishing claim for "${fileName}"`); logger.debug(`Publishing claim to "${publishParams.name}"`);
const deferred = new Promise((resolve, reject) => { const deferred = new Promise((resolve, reject) => {
axios axios
.post('http://localhost:5279/lbryapi', { .post('http://localhost:5279/lbryapi', {

View file

@ -0,0 +1,35 @@
const logger = require('winston');
const config = require('config');
const walletAddress = config.get('WalletConfig.LbryAddress');
module.exports = {
createPublishParams (name, filePath, license, nsfw) {
logger.debug(`Creating Publish Parameters for "${name}"`);
// ensure nsfw is a boolean
if (nsfw.toLowerCase === 'true') {
nsfw = true;
} else if (nsfw.toLowerCase === 'on') {
nsfw = true;
} else {
nsfw = false;
}
const publishParams = {
name,
file_path: filePath,
bid : 0.01,
metadata : {
description: `${name} published via spee.ch`,
title : name,
author : 'spee.ch',
language : 'en',
license,
nsfw,
},
claim_address : walletAddress,
change_address: walletAddress,
};
logger.debug('publishParams:', publishParams);
return publishParams;
},
};

View file

@ -29,6 +29,7 @@
"axios": "^0.16.1", "axios": "^0.16.1",
"body-parser": "^1.17.1", "body-parser": "^1.17.1",
"config": "^1.26.1", "config": "^1.26.1",
"connect-multiparty": "^2.0.0",
"express": "^4.15.2", "express": "^4.15.2",
"express-handlebars": "^3.0.0", "express-handlebars": "^3.0.0",
"mysql2": "^1.3.5", "mysql2": "^1.3.5",

View file

@ -1,6 +1,10 @@
const errorHandlers = require('../helpers/libraries/errorHandlers.js');
const lbryApi = require('../helpers/libraries/lbryApi.js');
const logger = require('winston'); const logger = require('winston');
const multipart = require('connect-multiparty');
const multipartMiddleware = multipart();
const publishController = require('../controllers/publishController.js');
const lbryApi = require('../helpers/libraries/lbryApi.js');
const publishHelpers = require('../helpers/libraries/publishHelpers.js');
const errorHandlers = require('../helpers/libraries/errorHandlers.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
@ -27,4 +31,33 @@ module.exports = app => {
errorHandlers.handleRequestError(error, res); errorHandlers.handleRequestError(error, res);
}); });
}); });
// route to run a publish request on the daemon
app.post('/api/publish', multipartMiddleware, ({ originalUrl, body, files }, res) => {
logger.debug(`POST request on ${originalUrl}`);
const file = files.thumbnail || files.null;
if (!file) {
res.status(400).send('error: No file was submitted or the key used was incorrect. Files posted through this route must use a key of "thumbnail" or null');
return;
}
const name = body.claim || file.name.substring(0, file.name.indexOf('.'));
const license = body.license || 'No License Provided';
const nsfw = body.nsfw || true;
const fileName = file.name;
const filePath = file.path;
const fileType = file.type;
/*
make sure it's not a harmful file type
*/
// prepare the publish parameters
const publishParams = publishHelpers.createPublishParams(name, filePath, license, nsfw);
// publish the file
publishController
.publish(publishParams, fileName, fileType)
.then(result => {
res.status(200).json(result);
})
.catch(error => {
errorHandlers.handleRequestError(error, res);
});
});
}; };

View file

@ -1,5 +1,7 @@
const publishController = require('../controllers/publishController.js');
const logger = require('winston'); const logger = require('winston');
const publishController = require('../controllers/publishController.js');
const publishHelpers = require('../helpers/libraries/publishHelpers.js');
const errorHandlers = require('../helpers/libraries/errorHandlers.js');
module.exports = (app, siofu, hostedContentPath, ua, googleAnalyticsId) => { module.exports = (app, siofu, hostedContentPath, ua, googleAnalyticsId) => {
const http = require('http').Server(app); const http = require('http').Server(app);
@ -7,8 +9,9 @@ module.exports = (app, siofu, hostedContentPath, ua, googleAnalyticsId) => {
io.on('connection', socket => { io.on('connection', socket => {
logger.silly('a user connected via sockets'); logger.silly('a user connected via sockets');
// create visitor record // google analytics
const visitor = ua(googleAnalyticsId, { https: true }); const visitor = ua(googleAnalyticsId, { https: true });
visitor.event('Publish Route', 'Publish Request').send();
// attach upload listeners // attach upload listeners
const uploader = new siofu(); const uploader = new siofu();
uploader.dir = hostedContentPath; uploader.dir = hostedContentPath;
@ -27,8 +30,18 @@ module.exports = (app, siofu, hostedContentPath, ua, googleAnalyticsId) => {
uploader.on('saved', ({ file }) => { uploader.on('saved', ({ file }) => {
if (file.success) { if (file.success) {
logger.debug(`Client successfully uploaded ${file.name}`); logger.debug(`Client successfully uploaded ${file.name}`);
socket.emit('publish-status', 'file upload successfully completed'); socket.emit('publish-status', 'file upload successfully completed. Your image is being published (this might take a second)...');
publishController.publish(file.meta.name, file.name, file.pathName, file.meta.type, file.meta.license, file.meta.nsfw, socket, visitor); // prepare the publish parameters
const publishParams = publishHelpers.createPublishParams(file.meta.name, file.pathName, file.meta.license, file.meta.nsfw);
// publish the file
publishController
.publish(publishParams, file.name, file.meta.type)
.then(result => {
socket.emit('publish-complete', { name: publishParams.name, result });
})
.catch(error => {
socket.emit('publish-failure', errorHandlers.handlePublishError(error));
});
} else { } else {
logger.error(`An error occurred in uploading the client's file`); logger.error(`An error occurred in uploading the client's file`);
socket.emit('publish-failure', 'file uploaded, but with errors'); socket.emit('publish-failure', 'file uploaded, but with errors');

View file

@ -8,13 +8,13 @@ const config = require('config');
const ua = require('universal-analytics'); const ua = require('universal-analytics');
const winston = require('winston'); const winston = require('winston');
const googleAnalyticsId = config.get('AnalyticsConfig.googleId'); const googleAnalyticsId = config.get('AnalyticsConfig.GoogleId');
const hostedContentPath = config.get('Database.PublishUploadPath'); const hostedContentPath = config.get('Database.PublishUploadPath');
// configure logging // configure logging
const logLevel = config.get('Logging.LogLevel'); const logLevel = config.get('Logging.LogLevel');
const logDir = config.get('Logging.LogDirectory'); const logDir = config.get('Logging.LogDirectory');
require('./helpers/logging/loggerSetup.js')(winston, logLevel, logDir); require('./config/loggerSetup.js')(winston, logLevel, logDir);
// set port // set port
const PORT = 3000; const PORT = 3000;
@ -38,7 +38,7 @@ const hbs = expressHandlebars.create({
helpers : { helpers : {
// define any extra helpers you may need // define any extra helpers you may need
googleAnalytics () { googleAnalytics () {
const googleApiKey = config.get('AnalyticsConfig.googleId'); const googleApiKey = config.get('AnalyticsConfig.GoogleId');
return new Handlebars.SafeString( return new Handlebars.SafeString(
`<script> `<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){