commit
f0958cd212
16 changed files with 165 additions and 91 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
ApiPublishTest.html
|
21
README.md
21
README.md
|
@ -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!
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"WalletConfig": {
|
"WalletConfig": {
|
||||||
"lbryAddress": "LBRY_WALLET_ADDRESS"
|
"LbryAddress": "LBRY_WALLET_ADDRESS"
|
||||||
},
|
},
|
||||||
"Database": {
|
"Database": {
|
||||||
"MySqlConnectionUri": "MYSQL_CONNECTION_STRING"
|
"MySqlConnectionUri": "MYSQL_CONNECTION_STRING"
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"WalletConfig": {
|
"WalletConfig": {
|
||||||
"lbryAddress": "none"
|
"LbryAddress": "none"
|
||||||
},
|
},
|
||||||
"AnalyticsConfig":{
|
"AnalyticsConfig":{
|
||||||
"googleId": "none"
|
"GoogleId": "none"
|
||||||
},
|
},
|
||||||
"Database": {
|
"Database": {
|
||||||
"MySqlConnectionUri": "none",
|
"MySqlConnectionUri": "none",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', {
|
||||||
|
|
35
helpers/libraries/publishHelpers.js
Normal file
35
helpers/libraries/publishHelpers.js
Normal 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;
|
||||||
|
},
|
||||||
|
};
|
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
Loading…
Reference in a new issue