diff --git a/.gitignore b/.gitignore index 3c3629e6..b512c09d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -node_modules +node_modules \ No newline at end of file diff --git a/README.md b/README.md index d1a8221b..5d124667 100644 --- a/README.md +++ b/README.md @@ -5,26 +5,23 @@ spee.ch is a single-serving site that reads and publishes images to and from the * start lbry * install the [`lbry`](https://github.com/lbryio/lbry) daemon * start the `lbry` daemon -* start RabbitMQ (this will handle the queue for background processing) - * install & run [RabbitMQ](https://www.rabbitmq.com/#getstarted) * clone this repo +* create a folder called `Uploads` in the same root directory where you cloned the repo * run `npm install` * from your terminal, run `npm start` * to run hot, run `nodemon server.js` -* start a worker by running `node worker.js` in a separate session in your terminal * visit [localhost:3000](http://localhost:3000) ## site navigation - -* spee.ch. +* spee.ch * To publish a file, navigate to the homepage. -* spee.ch/ +* spee.ch/< the name of the claim > * To view the file with the largest bid at a claim. * E.g. spee.ch/doitlive. * spee.ch/< the name of the claim >/< the claim_id > * To view a specific file at a claim * E.g. spee.ch/doitlive/c496c8c55ed79816fec39e36a78645aa4458edb5 -* spee.ch//all +* spee.ch/< the name of the claim >/all * To view a batch of files at a claim * E.g. spee.ch/doitlive/all @@ -39,6 +36,6 @@ spee.ch is a single-serving site that reads and publishes images to and from the Note: these are being used for testing durring spee.ch development and may not be maintained -* A GET request to spee.ch/claim_list/ +* A GET request to spee.ch/claim_list/< the name of the claim > * Will return the claim_list for the claim in json format. * E.g. spee.ch/claim_list/doitlive diff --git a/helpers/lbryApi.js b/helpers/lbryApi.js index 8a09f13e..6170d83e 100644 --- a/helpers/lbryApi.js +++ b/helpers/lbryApi.js @@ -38,10 +38,10 @@ function orderTopClaims(claimsListArray){ } function getClaimWithUri(uri, resolve, reject){ - console.log(">> making get request to lbry daemon") + console.log(">> making get request to lbry daemon"); axios.post('http://localhost:5279/lbryapi', { - method: "get", - params: { uri: uri } + "method": "get", + "params": { "uri": uri } } ).then(function (getUriResponse) { console.log(">> 'get claim' success..."); @@ -64,25 +64,37 @@ function getClaimWithUri(uri, resolve, reject){ }); } +function findAllClaims(name, resolve, reject){ + // to do: abstract claim_list function to here +} + module.exports = { - publishClaim: function(publishObject){ - axios.post('http://localhost:5279/lbryapi', publishObject) - .then(function (response) { - // receive resonse from LBRY - // if successfull, (1) delete file (2) send response to the client - console.log(">> 'publish' success..."); - console.log(">> 'publish' response.data:", response.data); - console.log(" [x] Done"); - // return the claim we got - //res.status(200).send(JSON.stringify({msg: "you succsessfully published!", txData: response.data})); - }).catch(function(error){ - // receive response from LBRY - // if not successfull, (1) delete file and (2) send response to the client - console.log(">> 'publish' error.response.data:", error.response.data); - console.log(" [x] Done"); - //res.status(500).send(JSON.stringify({msg: "your file was not published", err: error.response.data.error.message})); + publishClaim: function(publishParams){ + console.log(publishParams); + var deferred = new Promise(function(resolve, reject){ + axios.post('http://localhost:5279/lbryapi', { + "method": "publish", + "params": publishParams + }) + .then(function (response) { + // receive resonse from LBRY + console.log(">> 'publish' success"); + // return the claim we got + resolve(response.data); + return; + }).catch(function(error){ + // receive response from LBRY + console.log(">> 'publish' error"); + if (error.response.data.error){ + reject(error.response.data.error); + } else { + reject(error); + } + return; + }) }) + return deferred; }, getClaimBasedOnNameOnly: function(claimName){ @@ -90,49 +102,47 @@ module.exports = { var deferred = new Promise(function (resolve, reject){ // 2. code to resolve or reject the promise // make a call to the daemon to get the claims list - axios.post('http://localhost:5279/lbryapi', { // receives a promise - method: "claim_list", - params: { name: claimName } + axios.post('http://localhost:5279/lbryapi', { + "method": "claim_list", + "params": { "name": claimName } }) .then(function (response) { - console.log(">> Claim_list success"); - + console.log(">> 'claim_list' success"); var claimsList = response.data.result.claims; console.log(">> Number of claims:", claimsList.length) - // return early if no claims were found if (claimsList.length === 0){ - reject("no claims were found"); + reject("NO_CLAIMS"); console.log("exiting due to lack of claims"); return; } - // filter the claims to return only free, public claims var freePublicClaims = filterForFreePublicClaims(claimsList); - // return early if no free, public claims were found if (!freePublicClaims || (freePublicClaims.length === 0)){ - reject("no free, public claims were found"); + reject("NO_FREE_PUBLIC_CLAIMS"); console.log("exiting due to lack of free or public claims"); return; } - // order the claims - var orderedPublcClaims = orderTopClaims(freePublicClaims); - + var orderedPublicClaims = orderTopClaims(freePublicClaims); // create the uri for the first (selected) claim - console.log(">> ordered free public claims", orderedPublcClaims); - var freePublicClaimUri = "lbry://" + orderedPublcClaims[0].name + "#" + orderedPublcClaims[0].claim_id; - console.log(">> your free public claim uri:", freePublicClaimUri); - + console.log(">> ordered free public claims"); + var freePublicClaimUri = orderedPublicClaims[0].name + "#" + orderedPublicClaims[0].claim_id; + console.log(">> your free public claim URI:", freePublicClaimUri); // fetch the image to display getClaimWithUri(freePublicClaimUri, resolve, reject); - }) .catch(function(error){ - console.log(">> error:", error); + console.log(">> 'claim_list' error:", error); // reject the promise with an approriate message - reject(error.response.data.error); + if (error.code === "ECONNREFUSED"){ + reject("Connection refused. The daemon may not be running.") + } else if (error.response.data.error) { + reject(error.response.data.error); + } else { + reject(error); + }; return; }); }); @@ -146,7 +156,7 @@ module.exports = { to do: need to pass the URI through a test (use 'resolve') to see if it is free and public. Right now it is jumping straight to 'get'ing and serving the asset. */ var deferred = new Promise(function (resolve, reject){ - console.log(">> your uri:", uri); + console.log(">> get claim based on URI:", uri); // fetch the image to display getClaimWithUri(uri, resolve, reject); }); @@ -154,39 +164,52 @@ module.exports = { }, - serveAllClaims: function(claimName, res){ // note: work in progress - // make a call to the daemon to get the claims list - axios.post('http://localhost:5279/lbryapi', { - method: "claim_list", - params: { name: claimName } - } - ).then(function (response) { - console.log(">> Claim_list success"); - console.log(">> Number of claims:", response.data.result.claims.length) - // return early if no claims were found - if (response.data.result.claims.length === 0){ - res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html')); - return; - } - // filter the claims to return free, public claims - var freePublicClaims = filterForFreePublicClaims(response.data.result.claims); - // return early if no free, public claims were found - if (!freePublicClaims || (freePublicClaims.length === 0)){ - res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html')); - return; - } - console.log(">> Number of free public claims:", freePublicClaims.length); - // order the claims - var orderedPublicClaims = orderTopClaims(freePublicClaims); - // serve the response - /* - to do: rather than returning json, serve a page of all these claims - */ - res.status(200).send(orderedPublicClaims); - }).catch(function(error){ - console.log(">> /c/ error:", error.response.data); - // serve the response - res.status(500).send(JSON.stringify({msg: "An error occurred while finding the claim list.", err: error.response.data.error.message})); - }) + getAllClaims: function(claimName, res){ // note: work in progress + var deferred = new Promise(function(resolve, reject){ + console.log(">> get all claims data for", claimName) + // make a call to the daemon to get the claims list + axios.post('http://localhost:5279/lbryapi', { + method: "claim_list", + params: { name: claimName } + } + ).then(function (response) { + console.log(">> 'claim_list' success"); + console.log(">> Number of claims:", response.data.result.claims.length) + console.log(">> 'claim_list' success"); + var claimsList = response.data.result.claims; + console.log(">> Number of claims:", claimsList.length) + // return early if no claims were found + if (claimsList.length === 0){ + reject("NO_CLAIMS"); + console.log("exiting due to lack of claims"); + return; + } + // filter the claims to return only free, public claims + var freePublicClaims = filterForFreePublicClaims(claimsList); + // return early if no free, public claims were found + if (!freePublicClaims || (freePublicClaims.length === 0)){ + reject("NO_FREE_PUBLIC_CLAIMS"); + console.log("exiting due to lack of free or public claims"); + return; + } + // order the claims + var orderedPublicClaims = orderTopClaims(freePublicClaims); + // serve the response + /* + to do: rather than returning json, serve a page of all these claims + */ + resolve(orderedPublicClaims); + }).catch(function(error){ + console.log(">> 'claim_list' error:", error); + if (error.code === "ECONNREFUSED"){ + reject("Connection refused. The daemon may not be running.") + } else if (error.response.data.error) { + reject(error.response.data.error); + } else { + reject(error); + }; + }) + }); + return deferred; } } diff --git a/package.json b/package.json index 38ad5ab0..a3ef74fd 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "connect-multiparty": "^2.0.0", "express": "^4.15.2", "nodemon": "^1.11.0", - "socket.io": "^2.0.1" + "socket.io": "^2.0.1", + "socketio-file-upload": "^0.6.0" } } diff --git a/public/claim.html b/public/claimPlus.html similarity index 100% rename from public/claim.html rename to public/claimPlus.html diff --git a/public/index.html b/public/index.html index d5af3d08..3511eaba 100644 --- a/public/index.html +++ b/public/index.html @@ -12,39 +12,34 @@

Examples:

Publish Your Own

-
- -
- Image preview... -
- Name: -
- Title: -
- Description: -
- Author: -
- Language: -
- License: -
- NSFW: -
- -
+
+
+ +
+ Image preview... +
+ Name: +
+ License: +
+ NSFW: +
+ +
+

+

Help

Site Navigation

@@ -84,28 +79,69 @@ + + - - \ No newline at end of file diff --git a/public/publishingClaim.html b/public/invalidUri.html similarity index 51% rename from public/publishingClaim.html rename to public/invalidUri.html index f599e292..a4b0673e 100644 --- a/public/publishingClaim.html +++ b/public/invalidUri.html @@ -4,11 +4,11 @@ - Publishing Asset + No Claims

spee.ch

-

Publishing Asset

-

Your asset is being published by a handy background worker. You can return to spee.ch and your asset should be published to your claim shortly.

+

Invalid URI

+

There is no claim at that URI.

\ No newline at end of file diff --git a/routes/html-routes.js b/routes/html-routes.js index 59df73cd..6253e54f 100644 --- a/routes/html-routes.js +++ b/routes/html-routes.js @@ -6,27 +6,6 @@ var multipartMiddleware = multipart(); var lbryApi = require('../helpers/lbryApi.js'); var queueApi = require('../helpers/queueApi.js'); -// helper functions -function createPublishObject(req){ - var publishObject = { - "method":"publish", - "params": { - "name": req.body.name, - "file_path": req.files.file.path, - "bid": 0.1, - "metadata": { - "description": req.body.description, - "title": req.body.title, - "author": req.body.author, - "language": req.body.language, - "license": req.body.license, - "nsfw": (req.body.nsfw.toLowerCase() === "true") - } - } - }; - return publishObject; -} - // routes to export module.exports = function(app){ // route to fetch one free public claim @@ -34,45 +13,88 @@ module.exports = function(app){ console.log(" >> GET request on favicon.ico"); res.sendFile(path.join(__dirname, '../public', 'favicon.ico')); }); - // route to publish a new claim - app.post("/publish", multipartMiddleware, function(req, res){ - console.log(" >> POST request on /publish"); - // build the data needed to publish the file - var publishObject = createPublishObject(req); - console.log("publish", publishObject); - // post the task to the que - queueApi.addNewTaskToQueue(JSON.stringify({ - type: 'publish', - data: publishObject - })); - // respond to the client that the task has been queued - res.status(200).sendFile(path.join(__dirname, '../public', 'publishingClaim.html')); - }); // route to fetch one free public claim app.get("/:name/all", function(req, res){ var name = req.params.name; console.log(">> GET request on /" + name + " (all)"); - lbryApi.serveAllClaims(name, res); + // create promise + var promise = lbryApi.getAllClaims(name); + // handle the promise resolve + promise.then(function(orderedFreePublicClaims){ + console.log("/name/all promise success.") + res.status(200).send(orderedFreePublicClaims); + return; + }) + // handle the promise rejection + .catch(function(error){ + console.log("/name/all/ promise error:", error); + // handle the error + if ((error === "NO_CLAIMS") || (error === "NO_FREE_PUBLIC_CLAIMS")){ + res.status(307).sendFile(path.join(__dirname, '../public', 'noClaims.html')); + return; + } else { + res.status(400).send(error); + return; + }; + }) }); // route to fetch one free public claim app.get("/:name/:claim_id", function(req, res){ - console.log(">> GET request on /" + req.params.name + "#" + req.params.claim_id); - res.status(200).sendFile(path.join(__dirname, '../public', 'claim.html')); + var uri = req.params.name + "#" + req.params.claim_id; + console.log(">> GET request on /" + uri); + // create promise + var promise = lbryApi.getClaimBasedOnUri(uri); + // handle the promise resolve + promise.then(function(filePath){ + console.log("/name/claim_id promise success - filepath:", filePath) + res.status(200).sendFile(filePath); + return; + }) + // handle the promise rejection + .catch(function(error){ + console.log("/name/claim_id/ promise error:", error) + // handle the error + if (error === "Invalid URI") { + res.status(400).sendFile(path.join(__dirname, '../public', 'invalidUri.html')); + return; + } else { + res.status(400).send(error); + return; + }; + }); }); + // route to fetch one free public claim app.get("/:name", function(req, res){ - var name = req.params.name; - console.log(">> GET request on /" + name) - // send page (includes a socket to get the file) - res.status(200).sendFile(path.join(__dirname, '../public', 'claim.html')); + var name = req.params.name; + console.log(">> GET request on /" + name); + // create promise + var promise = lbryApi.getClaimBasedOnNameOnly(name); + // handle the promise resolve + promise.then(function(filePath){ + console.log("/name promise success - filepath:", filePath) + res.status(200).sendFile(filePath); + return; + }) + // handle the promise rejection + .catch(function(error){ + console.log("/name/ promise error:", error); + // handle the error + if ((error === "NO_CLAIMS") || (error === "NO_FREE_PUBLIC_CLAIMS")){ + res.status(307).sendFile(path.join(__dirname, '../public', 'noClaims.html')); + return; + }; + res.status(400).send(error); + }); }); + // route for the home page app.get("/", function(req, res){ - res.sendFile(path.join(__dirname, '../public', 'index.html')); + res.status(200).sendFile(path.join(__dirname, '../public', 'index.html')); }); // a catch-all route if someone visits a page that does not exist app.use("*", function(req, res){ - res.sendFile(path.join(__dirname, '../public', 'fourOhfour.html')); + res.status(404).sendFile(path.join(__dirname, '../public', 'fourOhfour.html')); }); } diff --git a/routes/sockets-routes.js b/routes/sockets-routes.js index 51bbbb0a..edd1d4a9 100644 --- a/routes/sockets-routes.js +++ b/routes/sockets-routes.js @@ -4,48 +4,71 @@ module.exports = function(app) { var fs = require('fs'); var path = require('path'); var lbryApi = require('../helpers/lbryApi.js'); + var queueApi = require('../helpers/queueApi.js'); + var siofu = require("socketio-file-upload"); - function sendTheImage(socket, filePath){ - fs.readFile(filePath, function(err, buff){ - if (err) { - console.log("socket: fs err:", err); - return; - }; - //console.log("buff", buff); - socket.emit('claim-send', { image: true, buffer: buff.toString('base64') }); - console.log('socket: the image file has been sent via sockets'); - }); + // functions to create a publishing object + function createPublishParams(name, filepath, license, nsfw){ + var publishParams = { + "name": name, + "file_path": filepath, + "bid": 0.1, + "metadata": { + "description": name + " published via spee.ch", + "title": name, + "author": "spee.ch", + "language": "en", + "license": license, + "nsfw": (nsfw.toLowerCase() === "true") + } + }; + return publishParams; } + // publish an image to lbry + function publish(name, filepath, license, nsfw, socket){ + // update the client + socket.emit("publish-status", "Your image is being published (this might take a second)..."); + // create the publish object + var publishParams = createPublishParams(name, filepath, license, nsfw); + // get a promise to publish + var promise = lbryApi.publishClaim(publishParams); + // handle promise + promise.then(function(data){ + console.log("publish promise success. Tx info:", data) + socket.emit("publish-complete", data); + /* + note: remember to delete the local file + */ + }) + .catch(function(error){ + console.log("error:", error); + socket.emit("publish-status", "publish failed"); + /* + note: remember to delete the local file + */ + }); + }; io.on('connection', function(socket){ console.log('a user connected'); - - // serve an image file from the server - socket.on('claim-request', function(query){ - // 1. retrieve the image from lbry via daemon - console.log("socket: received claim request for:", query) - if (query.indexOf("/") === -1){ - var promise = lbryApi.getClaimBasedOnNameOnly(query) - } else { - var uri = query.replace("/", "#"); - var promise = lbryApi.getClaimBasedOnUri(uri) - } - promise.then(function(data){ - console.log("socket: claim-request - success:", data) - // 3. serve the image back once it is retrieved - sendTheImage(socket, data); - return; - }) - .catch(function(error){ - console.log("socket: claim-request - error:", error) - // handle the error - socket.emit("claim-update", error); - return; - }); - - // 2. emit updates as the image is being retrieved - socket.emit("claim-update", "We are getting your claim for " + query); + // listener for uploader + var uploader = new siofu(); + uploader.dir = path.join(__dirname, '../../Uploads'); + uploader.listen(socket); + // attach upload listeners + uploader.on("error", function(event){ + console.log("an error occured while uploading", event.error); + socket.emit("publish-status", event.error) }) + uploader.on("saved", function(event){ + console.log("saved " + event.file.name); + if (event.file.success){ + socket.emit("publish-status", "file upload successfully completed"); + publish(event.file.meta.name, event.file.pathName, event.file.meta.license,event.file.meta.nsfw, socket) + } else { + socket.emit("publish-status", "file saved, but with errors") + }; + }); // handle disconnect socket.on('disconnect', function(){ diff --git a/server.js b/server.js index 5872b342..abf98fba 100644 --- a/server.js +++ b/server.js @@ -2,6 +2,7 @@ var express = require('express'); var bodyParser = require('body-parser'); var path = require('path'); +var siofu = require("socketio-file-upload"); // set port var PORT = 3000; @@ -15,6 +16,7 @@ app.use(express.static(__dirname + '/public')); // configure express app app.use(bodyParser.json()); // for parsing application/json app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded +app.use(siofu.router); // require express routes require("./routes/api-routes.js")(app);