Merge pull request #14 from billbitt/master
replacing live-coded spee.ch with @billbitt js version
This commit is contained in:
commit
c2ff396058
19 changed files with 579 additions and 209 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1 @@
|
||||||
.idea/
|
node_modules
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
if (!defined('ROOT_PAGE')) { die('not allowed'); }
|
|
||||||
|
|
||||||
class LBRY
|
|
||||||
{
|
|
||||||
public static function api($function, array $params = [])
|
|
||||||
{
|
|
||||||
$ch = curl_init();
|
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_URL, 'http://localhost:5279/lbryapi');
|
|
||||||
curl_setopt($ch, CURLOPT_POST, 1);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['method' => $function, 'params' => $params]));
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
|
|
||||||
$serverOutput = curl_exec($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
if ($serverOutput)
|
|
||||||
{
|
|
||||||
$responseData = json_decode($serverOutput, true);
|
|
||||||
if (isset($responseData['error']))
|
|
||||||
{
|
|
||||||
throw new Exception($responseData['error']['message'] ?? 'Something unknown went wrong');
|
|
||||||
}
|
|
||||||
if (isset($responseData['result']))
|
|
||||||
{
|
|
||||||
return $responseData['result'];
|
|
||||||
}
|
|
||||||
throw new Exception('Received unknown response format.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function publishPublicClaim($name, $tmpFileName)
|
|
||||||
{
|
|
||||||
$filePath = '/home/lbry/publishes/newupload-' . random_int(1, PHP_INT_MAX);
|
|
||||||
|
|
||||||
move_uploaded_file($tmpFileName, $filePath);
|
|
||||||
|
|
||||||
$apiResult = LBRY::api('publish', [
|
|
||||||
'name' => $name,
|
|
||||||
'bid' => 1,
|
|
||||||
'file_path' => $filePath,
|
|
||||||
'description' => 'An image published from spee.ch',
|
|
||||||
'author' => 'https://spee.ch',
|
|
||||||
'language' => 'en',
|
|
||||||
'license' => 'Public Domain',
|
|
||||||
'nsfw' => 0,
|
|
||||||
'title' => 'Image published from spee.ch'
|
|
||||||
]);
|
|
||||||
|
|
||||||
return isset($apiResult['claim_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function findTopPublicFreeClaim($name)
|
|
||||||
{
|
|
||||||
$claims = LBRY::api('claim_list', ['name' => $name]);
|
|
||||||
|
|
||||||
if (!$claims || !isset($claims['claims']))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$freePublicClaims = array_filter($claims['claims'], function($claim) {
|
|
||||||
$metadata = json_decode($claim['value'], true);
|
|
||||||
return
|
|
||||||
//TODO: Expand these checks AND verify it is an image claim!
|
|
||||||
($metadata['license'] == "Public Domain" || stripos($metadata['license'], 'Creative Commons') !== false) &&
|
|
||||||
!isset($metadata['fee']);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (count($freePublicClaims) > 1)
|
|
||||||
{
|
|
||||||
usort($freePublicClaims, function($claimA, $claimB) {
|
|
||||||
if ($claimA['amount'] == $claimB['amount'])
|
|
||||||
{
|
|
||||||
return $claimA['height'] < $claimB['height'] ? -1 : 1;
|
|
||||||
}
|
|
||||||
return $claimA['amount'] > $claimB['amount'] ? -1 : 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return reset($freePublicClaims);
|
|
||||||
}
|
|
||||||
}
|
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 LBRY
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
46
README.md
46
README.md
|
@ -1,6 +1,44 @@
|
||||||
# spee.ch
|
# spee.ch (js)
|
||||||
spee.ch is a simple but powerful image hosting service on top of the LBRY protocol.
|
this is a clone of spee.ch with a javascript backend
|
||||||
|
|
||||||
It was built in real-time on March 29th, 2017.
|
## how to use this repository
|
||||||
|
* 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
|
||||||
|
* run `npm install`
|
||||||
|
* from your terminal, run `npm start`
|
||||||
|
* to run hot, run `nodemon server.js`
|
||||||
|
* start at least one worker by running `node worker.js`
|
||||||
|
* visit [localhost:3000](http://localhost:3000) and enjoy!
|
||||||
|
|
||||||
You can watch the video and learn more from [https://spee.ch](https://spee.ch)
|
## site navigation
|
||||||
|
|
||||||
|
* spee.ch.
|
||||||
|
* To publish a file, navigate to the homepage.
|
||||||
|
* 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/<the name of the claim>/all
|
||||||
|
* To view a batch of files at a claim
|
||||||
|
* E.g. spee.ch/doitlive/all
|
||||||
|
|
||||||
|
## development to-do's
|
||||||
|
* discover/explore functionality for home page
|
||||||
|
* display a list of claims at /:name/all
|
||||||
|
* fetching: a temporary page while the request is being made (with a loading bar?)
|
||||||
|
* publishing: a temporary page while the request is being handled by the server (with a loading bar?)
|
||||||
|
* publishing: after publishing, take the user to a temp page with the tx info and status of the tx (then redirect when the tx is complete)
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
Note: these are being used for testing durring spee.ch development and may not be maintained
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
173
helpers/lbryApi.js
Normal file
173
helpers/lbryApi.js
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
// load dependencies
|
||||||
|
var path = require('path');
|
||||||
|
var axios = require('axios');
|
||||||
|
|
||||||
|
// helper function to filter an array of claims for only free, public claims
|
||||||
|
function filterForFreePublicClaims(claimsListArray){
|
||||||
|
//console.log(">> filterForFreePublicClaims, claimsListArray:", claimsListArray);
|
||||||
|
if (!claimsListArray) {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
var freePublicClaims = claimsListArray.filter(function(claim){
|
||||||
|
return (((claim.value.stream.metadata.license.indexOf('Public Domain') != -1) || (claim.value.stream.metadata.license.indexOf('Creative Commons') != -1)) &&
|
||||||
|
(!claim.value.stream.metadata.fee || claim.value.stream.metadata.fee === 0));
|
||||||
|
});
|
||||||
|
return freePublicClaims;
|
||||||
|
}
|
||||||
|
// helper function to decide if a claim is free and public
|
||||||
|
function isFreePublicClaim(claim){
|
||||||
|
console.log(">> isFreePublicClaim, claim:", claim);
|
||||||
|
if ((claim.value.stream.metadata.license === 'Public Domain' || claim.value.stream.metadata.license === 'Creative Commons') &&
|
||||||
|
(!claim.value.stream.metadata.fee || claim.value.stream.metadata.fee.amount === 0)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// helper function to order a set of claims
|
||||||
|
function orderTopClaims(claimsListArray){
|
||||||
|
console.log(">> orderTopClaims, claimsListArray:");
|
||||||
|
claimsListArray.sort(function(claimA, claimB){
|
||||||
|
if (claimA.amount === claimB.amount){
|
||||||
|
return (claimA.height > claimB.height);
|
||||||
|
} else {
|
||||||
|
return (claimA.amount < claimB.amount);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return claimsListArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
serveClaimBasedOnNameOnly: function(claimName, res){
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
// order the claims
|
||||||
|
var orderedPublcClaims = 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);
|
||||||
|
// fetch the image to display
|
||||||
|
axios.post('http://localhost:5279/lbryapi', {
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
uri: freePublicClaimUri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(function (getResponse) {
|
||||||
|
console.log(">> 'get claim' success...");
|
||||||
|
console.log(">> response data:", getResponse.data);
|
||||||
|
console.log(">> dl path =", getResponse.data.result.download_path)
|
||||||
|
// return the claim we got
|
||||||
|
res.status(200).sendFile(getResponse.data.result.download_path);
|
||||||
|
}).catch(function(getError){
|
||||||
|
console.log(">> /c/ 'get' error:", getError.response.data);
|
||||||
|
res.status(500).send(JSON.stringify({msg: "An error occurred while fetching the free, public claim by URI.", err: getError.response.data.error.message}));
|
||||||
|
})
|
||||||
|
}).catch(function(error){
|
||||||
|
console.log(">> /c/ error:", error.response.data);
|
||||||
|
res.status(500).send(JSON.stringify({msg: "An error occurred while getting the claim list.", err: error.response.data.error.message}));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
serveClaimBasedOnUri: function(uri, res){
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
console.log(">> your uri:", uri);
|
||||||
|
// fetch the image to display
|
||||||
|
axios.post('http://localhost:5279/lbryapi', { // to do: abstract this code to a function that can be shared
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
uri: uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(function (getResponse) {
|
||||||
|
console.log(">> 'get claim' success...");
|
||||||
|
console.log(">> response data:", getResponse.data);
|
||||||
|
console.log(">> dl path =", getResponse.data.result.download_path)
|
||||||
|
/*
|
||||||
|
to do: make sure the file has completed downloading before serving back the file
|
||||||
|
*/
|
||||||
|
// return the claim we got
|
||||||
|
res.status(200).sendFile(getResponse.data.result.download_path);
|
||||||
|
|
||||||
|
/* delete the file after a certain amount of time? */
|
||||||
|
|
||||||
|
}).catch(function(error){
|
||||||
|
console.log(">> /c/ 'get' error:", error.response.data);
|
||||||
|
res.status(500).send(JSON.stringify({msg: "an error occurred", err: error.response.data.error.message}));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
serveAllClaims: function(claimName, res){
|
||||||
|
// 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
|
||||||
|
res.status(200).send(orderedPublicClaims); //to do: rather than returning json, serve a page of all these claims
|
||||||
|
}).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}));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
22
helpers/queueApi.js
Normal file
22
helpers/queueApi.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// require amqp library
|
||||||
|
var amqp = require('amqplib/callback_api');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addNewTaskToQueue: function(task){
|
||||||
|
// connect to RabbitMQ server
|
||||||
|
amqp.connect('amqp://localhost', function(err, conn) {
|
||||||
|
// create a channel
|
||||||
|
conn.createChannel(function(err, ch) {
|
||||||
|
var q = 'task_queue2'; // declaring a que is idempotent (it will only be created if it doesnt already exist)
|
||||||
|
var msg = task || "request received with no task!";
|
||||||
|
// declare a queue
|
||||||
|
ch.assertQueue(q, {durable: true});
|
||||||
|
// publish a message to the queue
|
||||||
|
ch.sendToQueue(q, new Buffer.from(msg), {persistent: true});
|
||||||
|
console.log(` [x] Sent '${msg}' to ${q}`);
|
||||||
|
});
|
||||||
|
// close the connection and exit
|
||||||
|
setTimeout(function() {conn.close() }, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
39
image.php
39
image.php
|
@ -1,39 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
if (!defined('ROOT_PAGE')) { die('not allowed'); }
|
|
||||||
|
|
||||||
$claim = LBRY::findTopPublicFreeClaim($name);
|
|
||||||
|
|
||||||
if ($claim)
|
|
||||||
{
|
|
||||||
$getResult = LBRY::api('get', ['name' => $name, 'claim_id' => $claim['claim_id']]);
|
|
||||||
|
|
||||||
if (isset($getResult['completed']) && $getResult['completed'] && isset($getResult['download_path']))
|
|
||||||
{
|
|
||||||
$path = $getResult['download_path'];
|
|
||||||
// $validType = isset($getResult['content_type']) && in_array($getResult['content_type'], ['image/jpeg', 'image/png']);
|
|
||||||
header('Content-type: image/jpeg');
|
|
||||||
header('Content-length: ' . filesize($path));
|
|
||||||
readfile($getResult['download_path']);
|
|
||||||
}
|
|
||||||
elseif (isset($getResult['written_bytes']))
|
|
||||||
{
|
|
||||||
echo 'This image is on it\'s way...<br/>';
|
|
||||||
echo 'Received: ' . $getResult['written_bytes'] . " / " . $getResult['total_bytes'] . ' bytes';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo 'There seems to be a valid claim, but are having trouble retrieving the content.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseif (isset($_GET['new']) && $_GET['new'])
|
|
||||||
{
|
|
||||||
echo 'Your image is on the way. It can take a few minutes to reach the blockchain and be public. You can refresh this page to check the progress.';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo 'No valid claim for this name. Make one!';
|
|
||||||
include './publish.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(0);
|
|
45
index.php
45
index.php
|
@ -1,45 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
|
|
||||||
define('ROOT_PAGE', 1);
|
|
||||||
|
|
||||||
require_once './LBRY.class.php';
|
|
||||||
|
|
||||||
$urlParts = parse_url($_SERVER['REQUEST_URI']);
|
|
||||||
$name = ltrim($urlParts['path'], '/');
|
|
||||||
|
|
||||||
if ($name)
|
|
||||||
{
|
|
||||||
include './image.php';
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
if (isset($_POST['publish']) && isset($_POST['name']) && isset($_FILES['file']))
|
|
||||||
{
|
|
||||||
$success = LBRY::publishPublicClaim($_POST['name'], $_FILES['file']['tmp_name']);
|
|
||||||
if ($success)
|
|
||||||
{
|
|
||||||
header('Location: /' . $_POST['name'] . '?new=1');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo '<p>Something went wrong publishing your content. We are only somewhat sorry.</p>';
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<h1><img src="https://spee.ch/speechlogo" alt="spee.ch logo" style="max-height: 36px; vertical-align: middle; max-width: 36px;" />spee.ch</h1>
|
|
||||||
<p>spee.ch is a single-serving site that reads and publishes images to and from the <a href="https://lbry.io">LBRY</a> blockchain.</p>
|
|
||||||
<p>Examples:</p>
|
|
||||||
<ul>
|
|
||||||
<?php foreach(['thailand', 'doitlive', 'coconuts', 'cow-air-balloon'] as $sampleName): ?>
|
|
||||||
<li><a href="/<?php echo $sampleName ?>">spee.ch/<?php echo $sampleName ?></a></li>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</ul>
|
|
||||||
<h3>Publish Your Own</h3>
|
|
||||||
<?php include './publish.php' ?>
|
|
||||||
<h3>About This Site</h3>
|
|
||||||
<p>It was built live in a little over 2 hours on March 29th, 2017. You can watch the video here:</p>
|
|
||||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/C9LCapt_OYw" frameborder="0" allowfullscreen></iframe>
|
|
33
package.json
Normal file
33
package.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "spee.ch-backend",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "a back end for spee.ch",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/billbitt/spee.ch-backend.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"spee.ch",
|
||||||
|
"lbry",
|
||||||
|
"blockchain"
|
||||||
|
],
|
||||||
|
"author": "@billbitt @vxn",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/billbitt/spee.ch-backend/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/billbitt/spee.ch-backend#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"amqplib": "^0.5.1",
|
||||||
|
"axios": "^0.16.1",
|
||||||
|
"body-parser": "^1.17.1",
|
||||||
|
"connect-multiparty": "^2.0.0",
|
||||||
|
"express": "^4.15.2",
|
||||||
|
"nodemon": "^1.11.0"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 180 B |
14
public/fourOhfour.html
Normal file
14
public/fourOhfour.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Four Oh Four</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>spee.ch</h1>
|
||||||
|
<h3>404: Not Found</h3>
|
||||||
|
<p>That page does not exist. Return <a href="/">home</a>.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
106
public/index.html
Normal file
106
public/index.html
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Spee.ch</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>spee.ch</h1>
|
||||||
|
<p>spee.ch is a single-serving site that reads and publishes images to and from the <a href="https://lbry.io">LBRY</a> blockchain.</p>
|
||||||
|
<h3>Examples:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/coconuts">spee.ch/coconuts</a></li>
|
||||||
|
<li><a href="/test">spee.ch/test</a></li>
|
||||||
|
<li><a href="/doitlive">spee.ch/doitlive</a></li>
|
||||||
|
<li><a href="/doitlive/all">spee.ch/doitlive/all</a></li>
|
||||||
|
<li><a href="/doitlive/ca3023187e901df9e9aabd95d6ae09b6cc69b3f0">spee.ch/doitlive/ca3023187e901df9e9aabd95d6ae09b6cc69b3f0</a></li>
|
||||||
|
</ul>
|
||||||
|
<h3>Publish Your Own</h3>
|
||||||
|
<form id="publish-form" action="/publish" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file" accept="video/*,image/*" onchange="previewFile()" enctype="multipart/form-data"/>
|
||||||
|
<br/>
|
||||||
|
<img src="" height="200" alt="Image preview..."/>
|
||||||
|
<br/>
|
||||||
|
Title: <input type="text" name="title" value="title"/>
|
||||||
|
<br/>
|
||||||
|
Description: <input type="text" name="description" value="description"/>
|
||||||
|
<br/>
|
||||||
|
Author: <input type="text" name="author" value="author"/>
|
||||||
|
<br/>
|
||||||
|
Language: <input type="text" name="language" value="en"/>
|
||||||
|
<br/>
|
||||||
|
License: <select type="text" name="license" value="license">
|
||||||
|
<option value="Creative Commons">Creative Commons</option>
|
||||||
|
<option value="Public Domain">Public Domain</option>
|
||||||
|
</select>
|
||||||
|
<br/>
|
||||||
|
NSFW: <select type="text" name="nsfw" value="nsfw">
|
||||||
|
<option value="true">True</option>
|
||||||
|
<option value="false">False</option>
|
||||||
|
</select>
|
||||||
|
<br/>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h3>Help</h3>
|
||||||
|
<h4>Site Navigation</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong><a href="/">spee.ch</a></strong>.
|
||||||
|
<ul>
|
||||||
|
<li>To publish a file, navigate to the homepage.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>spee.ch/<the name of the claim></strong>
|
||||||
|
<ul>
|
||||||
|
<li>To view the file with the largest bid at a claim.</li>
|
||||||
|
<li>E.g. <a href="/doitlive">spee.ch/doitlive</a>.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>spee.ch/< the name of the claim >/< the claim_id ></strong>
|
||||||
|
<ul>
|
||||||
|
<li>To view a specific file at a claim</li>
|
||||||
|
<li>E.g. <a href="/doitlive/c496c8c55ed79816fec39e36a78645aa4458edb5">spee.ch/doitlive/c496c8c55ed79816fec39e36a78645aa4458edb5</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>spee.ch/<the name of the claim>/all</strong>
|
||||||
|
<ul>
|
||||||
|
<li>To view a batch of files at a claim</li>
|
||||||
|
<li>E.g. <a href="/doitlive/all">spee.ch/doitlive/all</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h4>API</h4>
|
||||||
|
<p>Note: these are being used for testing durring spee.ch development and may not be maintained</p>
|
||||||
|
<ul>
|
||||||
|
<li>A GET request to <strong>spee.ch/claim_list/<the name of the claim></strong>
|
||||||
|
<ul>
|
||||||
|
<li>Will return the claim_list for the claim in json format. </li>
|
||||||
|
<li>E.g. <a href="/claim_list/doitlive">spee.ch/claim_list/doitlive</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function previewFile(){
|
||||||
|
var preview = document.querySelector('img'); //selects the query named img
|
||||||
|
var file = document.querySelector('input[type=file]').files[0]; //sames as here
|
||||||
|
var reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onloadend = function () {
|
||||||
|
preview.src = reader.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
reader.readAsDataURL(file); //reads the data as a URL
|
||||||
|
} else {
|
||||||
|
preview.src = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previewFile(); //calls the function named previewFile()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
14
public/noClaims.html
Normal file
14
public/noClaims.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>No Claims</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>spee.ch</h1>
|
||||||
|
<h3>No Claims</h3>
|
||||||
|
<p>There are no free, public images at that claim. You should publish one at <a href="/">spee.ch</a>.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
public/publishingClaim.html
Normal file
14
public/publishingClaim.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Publishing Asset</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>spee.ch</h1>
|
||||||
|
<h3>Publishing Asset</h3>
|
||||||
|
<p>Your asset is being published by a handy background worker. You can return to <a href="/">spee.ch</a> and your asset should be published to your claim shortly.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
publish.php
14
publish.php
|
@ -1,14 +0,0 @@
|
||||||
<form method="POST" action="/" enctype="multipart/form-data">
|
|
||||||
<div>
|
|
||||||
<input type="file" name="file" />
|
|
||||||
</div>
|
|
||||||
<?php if (isset($name) && $name != ''): ?>
|
|
||||||
<input type="hidden" name="name" value="<?php echo $name ?>" />
|
|
||||||
<?php else: ?>
|
|
||||||
<div>
|
|
||||||
lbry://<input type="text" name="name" />
|
|
||||||
</div>
|
|
||||||
<?php endif ?>
|
|
||||||
<input type="submit" name="publish" value="Go" />
|
|
||||||
<p>Publishing can take a few moments. Please be patient.</p>
|
|
||||||
</form>
|
|
29
routes/api-routes.js
Normal file
29
routes/api-routes.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// require dependencies
|
||||||
|
var path = require('path');
|
||||||
|
var axios = require('axios');
|
||||||
|
var multipart = require('connect-multiparty');
|
||||||
|
var multipartMiddleware = multipart();
|
||||||
|
// import helpers
|
||||||
|
var lbryApi = require('../helpers/lbryApi.js');
|
||||||
|
var queueApi = require('../helpers/queueApi.js');
|
||||||
|
|
||||||
|
module.exports = function(app){
|
||||||
|
// route to return claim list in json
|
||||||
|
app.get("/claim_list/:claim", function(req, res){
|
||||||
|
var claim = req.params.claim;
|
||||||
|
// make a call to the daemon
|
||||||
|
axios.post('http://localhost:5279/lbryapi', {
|
||||||
|
method: "claim_list",
|
||||||
|
params: {
|
||||||
|
name: claim
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(function (response) {
|
||||||
|
console.log("success");
|
||||||
|
res.send(response.data);
|
||||||
|
}).catch(function(error){
|
||||||
|
console.log(error.data);
|
||||||
|
res.send(error.data);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
80
routes/html-routes.js
Normal file
80
routes/html-routes.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// load dependencies
|
||||||
|
var path = require('path');
|
||||||
|
var multipart = require('connect-multiparty');
|
||||||
|
var multipartMiddleware = multipart();
|
||||||
|
// load helpers
|
||||||
|
var lbryApi = require('../helpers/lbryApi.js');
|
||||||
|
var queueApi = require('../helpers/queueApi.js');
|
||||||
|
|
||||||
|
// routes to export
|
||||||
|
module.exports = function(app){
|
||||||
|
// route to fetch one free public claim
|
||||||
|
app.get("/favicon.ico", function(req, res){
|
||||||
|
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){
|
||||||
|
// receive the request
|
||||||
|
console.log(" >> POST request on /publish");
|
||||||
|
//console.log(">> req.files:", req.files)
|
||||||
|
console.log(" >> req.body:", req.body)
|
||||||
|
|
||||||
|
// build the data needed to publish the file
|
||||||
|
var publishObject = {
|
||||||
|
"method":"publish",
|
||||||
|
"params": {
|
||||||
|
"name": req.body.title,
|
||||||
|
"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.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//console.log(">> publishObject:", 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);
|
||||||
|
});
|
||||||
|
// route to fetch one free public claim
|
||||||
|
app.get("/:name/:claim_id", function(req, res){
|
||||||
|
var uri = "lbry://" + req.params.name + "#" + req.params.claim_id;
|
||||||
|
console.log(">> GET request on /" + uri);
|
||||||
|
lbryApi.serveClaimBasedOnUri(uri, res);
|
||||||
|
});
|
||||||
|
// route to fetch one free public claim
|
||||||
|
app.get("/:name", function(req, res){
|
||||||
|
var name = req.params.name;
|
||||||
|
console.log(">> GET request on /" + name)
|
||||||
|
// publish a message to the cue
|
||||||
|
// queueApi.addNewTaskToQueue("return claim for " + req.params.name + " ...")
|
||||||
|
// retrieve the claim
|
||||||
|
lbryApi.serveClaimBasedOnNameOnly(name, res);
|
||||||
|
});
|
||||||
|
// route for the home page
|
||||||
|
app.get("/", function(req, res){
|
||||||
|
res.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'));
|
||||||
|
});
|
||||||
|
}
|
20
server.js
Normal file
20
server.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// load dependencies
|
||||||
|
var express = require('express');
|
||||||
|
var bodyParser = require('body-parser');
|
||||||
|
var path = require('path');
|
||||||
|
// set port
|
||||||
|
var PORT = 80;
|
||||||
|
// initialize express
|
||||||
|
var app = express();
|
||||||
|
// make express look in the public directory for assets (css/js/img)
|
||||||
|
app.use(express.static(__dirname + '/public'));
|
||||||
|
// configure epress
|
||||||
|
app.use(bodyParser.json()); // for parsing application/json
|
||||||
|
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
|
||||||
|
// require in routes
|
||||||
|
require("./routes/api-routes.js")(app);
|
||||||
|
require("./routes/html-routes.js")(app);
|
||||||
|
// start server
|
||||||
|
app.listen(PORT, function() {
|
||||||
|
console.log("Listening on PORT " + PORT);
|
||||||
|
});
|
31
worker.js
Normal file
31
worker.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// load dependencies
|
||||||
|
var amqp = require('amqplib/callback_api');
|
||||||
|
// load helpers
|
||||||
|
var lbryApi = require('./helpers/lbryApi');
|
||||||
|
// open a connection and a channel
|
||||||
|
amqp.connect('amqp://localhost', function(err, conn) {
|
||||||
|
// open a channel
|
||||||
|
conn.createChannel(function(err, ch) {
|
||||||
|
var q = 'task_queue2';
|
||||||
|
// declare the cue (in case the publisher hasn't made it yet)
|
||||||
|
ch.assertQueue(q, {durable: true});
|
||||||
|
// tell the queue to only assign one task at a time to this worker
|
||||||
|
ch.prefetch(1);
|
||||||
|
// listen for messages & pass callback for what to do with the msgs
|
||||||
|
console.log(" [x] Waiting for messages in %s. To exit press ctrl+c", q);
|
||||||
|
ch.consume(q, function(msg) {
|
||||||
|
var task = JSON.parse(msg.content.toString());
|
||||||
|
console.log(` [o] Received a ${task.type} task`);
|
||||||
|
// initiate the task
|
||||||
|
switch(task.type) {
|
||||||
|
case 'publish':
|
||||||
|
console.log(" [-] publishing:", task.data);
|
||||||
|
lbryApi.publishClaim(task.data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(" [-] that task type is not recognized");
|
||||||
|
console.log(" [x] Done");
|
||||||
|
}
|
||||||
|
}, {noAck: true});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue