merged master spee.ch branch

This commit is contained in:
bill bittner 2018-03-13 09:07:25 -07:00
commit bf836c83df
11 changed files with 80 additions and 69 deletions

View file

@ -2,7 +2,9 @@ const logger = require('winston');
const db = require('../models'); const db = require('../models');
const lbryApi = require('../helpers/lbryApi.js'); const lbryApi = require('../helpers/lbryApi.js');
const publishHelpers = require('../helpers/publishHelpers.js'); const publishHelpers = require('../helpers/publishHelpers.js');
const { publishing: { primaryClaimAddress } } = require('../config/siteConfig.js'); const { publishing: { primaryClaimAddress, additionalClaimAddresses } } = require('../config/siteConfig.js');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
module.exports = { module.exports = {
publish (publishParams, fileName, fileType) { publish (publishParams, fileName, fileType) {
@ -87,36 +89,42 @@ module.exports = {
}); });
}, },
claimNameIsAvailable (name) { claimNameIsAvailable (name) {
const claimAddresses = additionalClaimAddresses || [];
claimAddresses.push(primaryClaimAddress);
// find any records where the name is used // find any records where the name is used
return db.File.findAll({ where: { name } }) return db.Claim
.findAll({
attributes: ['address'],
where : {
name,
address: {
[Op.or]: claimAddresses,
},
},
})
.then(result => { .then(result => {
if (result.length >= 1) { if (result.length >= 1) {
// filter out any results that were not published from spee.ch's wallet address
const filteredResult = result.filter((claim) => {
return (claim.address === primaryClaimAddress);
});
// return based on whether any non-spee.ch claims were left
if (filteredResult.length >= 1) {
throw new Error('That claim is already in use'); throw new Error('That claim is already in use');
}; };
return name; return name;
}; })
return name; .catch(error => {
throw error;
}); });
}, },
checkChannelAvailability (name) { checkChannelAvailability (name) {
return new Promise((resolve, reject) => { return db.Channel
// find any records where the name is used .findAll({
db.Channel.findAll({ where: { channelName: name } }) where: { channelName: name },
})
.then(result => { .then(result => {
if (result.length >= 1) { if (result.length >= 1) {
return resolve(false); throw new Error('That channel has already been claimed');
} }
resolve(true); return name;
}) })
.catch(error => { .catch(error => {
reject(error); throw error;
});
}); });
}, },
}; };

View file

@ -1,5 +1,6 @@
const logger = require('winston'); const logger = require('winston');
const fs = require('fs'); const fs = require('fs');
const { details, publishing } = require('../config/siteConfig.js'); const { details, publishing } = require('../config/siteConfig.js');
module.exports = { module.exports = {

View file

@ -348,14 +348,13 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
where: { name, claimId }, where: { name, claimId },
}) })
.then(claimArray => { .then(claimArray => {
logger.debug('claims found on resolve:', claimArray.length);
switch (claimArray.length) { switch (claimArray.length) {
case 0: case 0:
return resolve(null); return resolve(null);
case 1: case 1:
return resolve(prepareClaimData(claimArray[0].dataValues)); return resolve(prepareClaimData(claimArray[0].dataValues));
default: default:
logger.error(`more than one entry matches that name (${name}) and claimID (${claimId})`); logger.error(`more than one record matches ${name}#${claimId} in db.Claim`);
return resolve(prepareClaimData(claimArray[0].dataValues)); return resolve(prepareClaimData(claimArray[0].dataValues));
} }
}) })

View file

@ -85,11 +85,14 @@ h3, p {
font-size: x-large; font-size: x-large;
} }
.text--large { .text--large {
font-size: 2rem; font-size: 2rem;
} }
.text--disabled {
color: #9b9b9b;
}
.pull-quote { .pull-quote {
font-size: 3rem; font-size: 3rem;
margin-top: 1rem; margin-top: 1rem;
@ -165,6 +168,11 @@ a, a:visited {
color: #9b9b9b; color: #9b9b9b;
} }
.link--disabled-text {
color: #9b9b9b;
text-decoration: underline;
}
.link--nav { .link--nav {
color: black; color: black;
border-bottom: 2px solid white; border-bottom: 2px solid white;
@ -504,7 +512,7 @@ table {
/* PUBLISH FORM */ /* PUBLISH FORM */
.dropzone { .dropzone, .dropzone--disabled {
border: 2px dashed #9b9b9b; border: 2px dashed #9b9b9b;
text-align: center; text-align: center;
position: relative; position: relative;

View file

@ -20,7 +20,7 @@ class AboutPage extends React.Component {
</div><div className='column column--5 column--med-10 align-content-top'> </div><div className='column column--5 column--med-10 align-content-top'>
<div className='column column--8 column--med-10'> <div className='column column--8 column--med-10'>
<p>Spee.ch is a media-hosting site that reads from and publishes content to the <a className='link--primary' href='https://lbry.io'>LBRY</a> blockchain.</p> <p>Spee.ch is a media-hosting site that reads from and publishes content to the <a className='link--primary' href='https://lbry.io'>LBRY</a> blockchain.</p>
<p>Spee.ch is a hosting service, but with the added benefit that it stores your content on a decentralized network of computers -- the LBRY network. This means that your images are stored in multiple locations without a single point of failure.</p> <p>Spee.ch is a hosting service, but with the added benefit that it stores your content on a decentralized network of computers -- the <a className='link--primary' href='https://lbry.io/get'>LBRY</a> network. This means that your images are stored in multiple locations without a single point of failure.</p>
<h3>Contribute</h3> <h3>Contribute</h3>
<p>If you have an idea for your own spee.ch-like site on top of LBRY, fork our <a className='link--primary' href='https://github.com/lbryio/spee.ch'>github repo</a> and go to town!</p> <p>If you have an idea for your own spee.ch-like site on top of LBRY, fork our <a className='link--primary' href='https://github.com/lbryio/spee.ch'>github repo</a> and go to town!</p>
<p>If you want to improve spee.ch, join our <a className='link--primary' href='https://discord.gg/YjYbwhS'>discord channel</a> or solve one of our <a className='link--primary' href='https://github.com/lbryio/spee.ch/issues'>github issues</a>.</p> <p>If you want to improve spee.ch, join our <a className='link--primary' href='https://discord.gg/YjYbwhS'>discord channel</a> or solve one of our <a className='link--primary' href='https://github.com/lbryio/spee.ch/issues'>github issues</a>.</p>

View file

@ -38,12 +38,8 @@ class ChannelCreateForm extends React.Component {
updateIsChannelAvailable (channel) { updateIsChannelAvailable (channel) {
const channelWithAtSymbol = `@${channel}`; const channelWithAtSymbol = `@${channel}`;
request(`/api/channel/availability/${channelWithAtSymbol}`) request(`/api/channel/availability/${channelWithAtSymbol}`)
.then(isAvailable => { .then(() => {
if (isAvailable) {
this.setState({'error': null}); this.setState({'error': null});
} else {
this.setState({'error': 'That channel has already been claimed'});
}
}) })
.catch((error) => { .catch((error) => {
this.setState({'error': error.message}); this.setState({'error': error.message});
@ -51,21 +47,9 @@ class ChannelCreateForm extends React.Component {
} }
checkIsChannelAvailable (channel) { checkIsChannelAvailable (channel) {
const channelWithAtSymbol = `@${channel}`; const channelWithAtSymbol = `@${channel}`;
return new Promise((resolve, reject) => { return request(`/api/channel/availability/${channelWithAtSymbol}`);
request(`/api/channel/availability/${channelWithAtSymbol}`)
.then(isAvailable => {
if (!isAvailable) {
return reject(new Error('That channel has already been claimed'));
} }
resolve(); checkIsPasswordProvided (password) {
})
.catch((error) => {
reject(error);
});
});
}
checkIsPasswordProvided () {
const password = this.state.password;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!password || password.length < 1) { if (!password || password.length < 1) {
return reject(new Error('Please provide a password')); return reject(new Error('Please provide a password'));
@ -94,9 +78,9 @@ class ChannelCreateForm extends React.Component {
} }
createChannel (event) { createChannel (event) {
event.preventDefault(); event.preventDefault();
this.checkIsPasswordProvided() this.checkIsPasswordProvided(this.state.password)
.then(() => { .then(() => {
return this.checkIsChannelAvailable(this.state.channel, this.state.password); return this.checkIsChannelAvailable(this.state.channel);
}) })
.then(() => { .then(() => {
this.setState({status: 'We are publishing your new channel. Sit tight...'}); this.setState({status: 'We are publishing your new channel. Sit tight...'});
@ -107,7 +91,11 @@ class ChannelCreateForm extends React.Component {
this.props.onChannelLogin(result.channelName, result.shortChannelId, result.channelClaimId); this.props.onChannelLogin(result.channelName, result.shortChannelId, result.channelClaimId);
}) })
.catch((error) => { .catch((error) => {
if (error.message) {
this.setState({'error': error.message, status: null}); this.setState({'error': error.message, status: null});
} else {
this.setState({'error': error, status: null});
};
}); });
} }
render () { render () {

View file

@ -3,6 +3,7 @@ import View from './view';
const mapStateToProps = ({ publish }) => { const mapStateToProps = ({ publish }) => {
return { return {
disabled: publish.disabled,
file : publish.file, file : publish.file,
status : publish.status.status, status : publish.status.status,
}; };

View file

@ -5,6 +5,14 @@ import PublishStatus from 'containers/PublishStatus';
class PublishTool extends React.Component { class PublishTool extends React.Component {
render () { render () {
if (this.props.disabled) {
return (
<div className='row dropzone--disabled row--tall flex-container--column flex-container--center-center'>
<p className='text--disabled'>Publishing is temporarily disabled.</p>
<p className='text--disabled'>Please check back soon or join our <a className='link--disabled-text' href='https://discord.gg/YjYbwhS'>discord channel</a> for updates.</p>
</div>
);
}
if (this.props.file) { if (this.props.file) {
if (this.props.status) { if (this.props.status) {
return ( return (
@ -13,9 +21,8 @@ class PublishTool extends React.Component {
} else { } else {
return <PublishDetails />; return <PublishDetails />;
} }
} else {
return <Dropzone />;
} }
return <Dropzone />;
} }
}; };

View file

@ -29,7 +29,6 @@ class ShowAssetDetails extends React.Component {
</div> </div>
</div> </div>
</div> </div>
}
</div> </div>
); );
}; };

View file

@ -1,7 +1,9 @@
import * as actions from 'constants/publish_action_types'; import * as actions from 'constants/publish_action_types';
import { LOGIN } from 'constants/publish_channel_select_states'; import { LOGIN } from 'constants/publish_channel_select_states';
const { publishing } = require('../../config/siteConfig.js');
const initialState = { const initialState = {
disabled : publishing.disabled,
publishInChannel : false, publishInChannel : false,
selectedChannel : LOGIN, selectedChannel : LOGIN,
showMetadataInputs: false, showMetadataInputs: false,

View file

@ -16,14 +16,12 @@ const NO_CLAIM = 'NO_CLAIM';
module.exports = (app) => { module.exports = (app) => {
// route to check whether site has published to a channel // route to check whether site has published to a channel
app.get('/api/channel/availability/:name', ({ ip, originalUrl, params }, res) => { app.get('/api/channel/availability/:name', ({ ip, originalUrl, params: { name } }, res) => {
checkChannelAvailability(params.name) const gaStartTime = Date.now();
.then(result => { checkChannelAvailability(name)
if (result === true) { .then(availableName => {
res.status(200).json(true); res.status(200).json(availableName);
} else { sendGATimingEvent('end-to-end', 'claim name availability', name, gaStartTime, Date.now());
res.status(200).json(false);
}
}) })
.catch(error => { .catch(error => {
errorHandlers.handleErrorResponse(originalUrl, ip, error, res); errorHandlers.handleErrorResponse(originalUrl, ip, error, res);
@ -107,10 +105,12 @@ module.exports = (app) => {
}); });
}); });
// route to check whether this site published to a claim // route to check whether this site published to a claim
app.get('/api/claim/availability/:name', ({ ip, originalUrl, params }, res) => { app.get('/api/claim/availability/:name', ({ ip, originalUrl, params: { name } }, res) => {
claimNameIsAvailable(params.name) const gaStartTime = Date.now();
claimNameIsAvailable(name)
.then(result => { .then(result => {
res.status(200).json(result); res.status(200).json(result);
sendGATimingEvent('end-to-end', 'claim name availability', name, gaStartTime, Date.now());
}) })
.catch(error => { .catch(error => {
errorHandlers.handleErrorResponse(originalUrl, ip, error, res); errorHandlers.handleErrorResponse(originalUrl, ip, error, res);
@ -128,8 +128,6 @@ module.exports = (app) => {
}); });
// route to run a publish request on the daemon // route to run a publish request on the daemon
app.post('/api/claim/publish', multipartMiddleware, ({ body, files, headers, ip, originalUrl, user }, res) => { app.post('/api/claim/publish', multipartMiddleware, ({ body, files, headers, ip, originalUrl, user }, res) => {
logger.debug('api/claim/publish req.body:', body);
logger.debug('api/claim/publish req.files:', files);
// define variables // define variables
let channelName, channelId, channelPassword, description, fileName, filePath, fileType, gaStartTime, license, name, nsfw, thumbnail, thumbnailFileName, thumbnailFilePath, thumbnailFileType, title; let channelName, channelId, channelPassword, description, fileName, filePath, fileType, gaStartTime, license, name, nsfw, thumbnail, thumbnailFileName, thumbnailFilePath, thumbnailFileType, title;
// record the start time of the request // record the start time of the request