Merge branch 'master' into add-chainquery

This commit is contained in:
Shawn K 2018-10-08 12:16:12 -05:00 committed by GitHub
commit 5a7e10574f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 353 additions and 96 deletions

View file

@ -3,16 +3,16 @@ Spee.ch is a web app that reads and publishes images and videos to and from the
You can create your own custom version of spee.ch by installing this code base and then creating your own custom components and styles to override the defaults. (More details / guide on how to do that coming soon.)
## Quick start
## Quickstart
_note: this is the quickstart guide, for an in-depth step-by-step overview visit the [fullstart guide](https://github.com/lbryio/spee.ch/blob/readme-update/fullstart.md)._
#### Install System Dependencies:
* [node](https://nodejs.org)
* [mysql](https://dev.mysql.com/doc/refman/8.0/en/installing.html)
* [MySQL](https://dev.mysql.com/doc/refman/8.0/en/installing.html)
* [`lbry`](https://github.com/lbryio/lbry) daemon
* note: retrieve an address from the daemon and send your wallet a couple credits (or join us in the [#speech discord channel](https://discord.gg/YjYbwhS) and we will send you a few)
* [ffmpeg](https://www.ffmpeg.org/download.html)
* [FFmpeg](https://www.ffmpeg.org/download.html)
#### Clone this repo
```
@ -29,7 +29,7 @@ $ cd spee.ch
$ npm install
```
#### Create the config files using the built in CLI
#### Create the config files using the built-in CLI
```
$ npm run configure
```
@ -51,11 +51,11 @@ check out the [customization guide](https://github.com/lbryio/spee.ch/blob/readm
#### (optional) add custom components and update the styles
* create custom components by creating React components in `src/views/` (further instructions coming soon)
* update the css by changing the files in `public/assets/css/` (further instructions and refactor coming soon)
* update the CSS by changing the files in `public/assets/css/` (further instructions and refactor coming soon)
#### (optional) Syncing the full blockchain
* Start the `spee.ch-sync` tool available at [billbitt/spee.ch-sync](https://github.com/billbitt/spee.ch-sync)
* This is not necessary, but highly reccomended. It will decode the blocks of the `LBRY` blockchain and add the claims information to your database's tables
* This is not necessary, but highly recommended. It will decode the blocks of the `LBRY` blockchain and add the claims information to your database's tables
## API
#### /api/claim/publish
@ -111,7 +111,7 @@ curl https://spee.ch/api/claim/availability/doitlive
response:
```
{
"success": <bool>, // `true` if spee.ch succesfully checked the claim availability
"success": <bool>, // `true` if spee.ch successfully checked the claim availability
"data": <bool>, // `true` if claim is available, false if it is not available
"message": <string> // human readable message of whether claim was available or not
}
@ -120,16 +120,16 @@ response:
## Contribute
### Stack
The spee.ch stack is MySQL, Express.js, Node.js, and React.js. Spee.ch also runs `lbrynet` on its server, and it uses the `lbrynet` api to make requests -- such as `publish`, `create_channel`, and `get` -- on the `LBRY` network.
The spee.ch stack is MySQL, Express.js, Node.js, and React.js. Spee.ch also runs `lbrynet` on its server, and it uses the `lbrynet` API to make requests -- such as `publish`, `create_channel`, and `get` -- on the `LBRY` network.
Spee.ch also runs a sync tool, which decodes blocks from the `LBRY` blockchain as they are mined, and stores the information in mysql. It stores all claims in the `Claims` table, and all channel claims in the `Certificates` table.
Spee.ch also runs a sync tool, which decodes blocks from the `LBRY` blockchain as they are mined, and stores the information in MySQL. It stores all claims in the `Claims` table, and all channel claims in the `Certificates` table.
* server
* [mysql](https://www.mysql.com/)
* [MySQL](https://www.mysql.com/)
* [express](https://www.npmjs.com/package/express)
* [node](https://nodejs.org/)
* [lbry](https://github.com/lbryio/lbry)
* [ffmpeg](https://www.ffmpeg.org/)
* [FFmpeg](https://www.ffmpeg.org/)
* client
* [react](https://reactjs.org/)
* redux
@ -139,10 +139,10 @@ Spee.ch also runs a sync tool, which decodes blocks from the `LBRY` blockchain a
### Architecture
* `cli/` contains the code for the cli tool. Running the tool will create `.json` config files and place them in the `config/` folder
* `configure.js` is the entry point for the cli tool
* `cli/` contains the code for the CLI tool. Running the tool will create `.json` config files and place them in the `config/` folder
* `configure.js` is the entry point for the CLI tool
* `cli/defaults/` holds default config files
* `cli/questions/` holds the questions that the cli tool asks to build the config files
* `cli/questions/` holds the questions that the CLI tool asks to build the config files
* `client/` contains all of the client code
* The client side of spee.ch uses `React` and `Redux`
@ -152,12 +152,12 @@ Spee.ch also runs a sync tool, which decodes blocks from the `LBRY` blockchain a
* The Redux code is broken up into `actions/` `reducers/` and `selectors/`
* The React components are broken up into `containers/` (components that pull props directly from the Redux store), `components/` ('dumb' components), and `pages/`
* spee.ch also uses sagas which are in the `sagas/` folders and `channels/`
* `client/scss/` contains the css for the project
* `client/scss/` contains the CSS for the project
*
* `client_custom` is a folder which can be used to override the default components in `client/`
* The folder structure mimics that of the `client/` folder
* to customize spee.ch, place your own components ans scss in the `client_custom/src/` and `client_custom/scss` folders.
* to customize spee.ch, place your own components and scss in the `client_custom/src/` and `client_custom/scss` folders.
* `server/` contains all of the server code
* `index.js` is the entry point for the server. It creates the [express app](https://expressjs.com/), requires the routes, syncs the database, and starts the server listening on the `PORT` designated in the config files.
@ -168,7 +168,7 @@ Spee.ch also runs a sync tool, which decodes blocks from the `LBRY` blockchain a
* `tests/` holds the end-to-end tests for this project
* Spee.ch uses `mocha` with the `chai` assertion library
* unit tests are located inside the project in-line with the files being tested and are designtated with a `xxxx.test.js` file name
* unit tests are located inside the project in-line with the files being tested and are designated with a `xxxx.test.js` file name
### Tests
* This package uses `mocha` with `chai` for testing.
@ -178,7 +178,7 @@ Spee.ch also runs a sync tool, which decodes blocks from the `LBRY` blockchain a
* To run only tests that do not require LBC, run `npm run test:no-lbc`
### URL formats
Spee.ch has a few types of url formats that return different assets from the LBRY network. Below is a list of all possible urls for the content on spee.ch
Spee.ch has a few types of URL formats that return different assets from the LBRY network. Below is a list of all possible URLs for the content on spee.ch
* retrieve the controlling `LBRY` claim:
* https://spee.ch/`claim`
* https://spee.ch/`claim`.`ext` (serve)
@ -197,7 +197,7 @@ Spee.ch has a few types of url formats that return different assets from the LBR
* https://spee.ch/`@channel`:`channel_id`/`claim`.`ext` (serve)
### Bugs
If you find a bug or experience a problem, please report your issue here on github and find us in the lbry discord!
If you find a bug or experience a problem, please report your issue here on GitHub and find us in the lbry discord!
### Issue tags in this repo
#### level 1
@ -210,4 +210,4 @@ Familiarity with web apps is required, but little-to-no familiarity with the lbr
Familiarity with the spee.ch code base and how the lbry daemon functions is required
#### level 4
Issues with lbry (e.g. the spee.ch wallet, lbrynet configuration, etc.) that require strong familiarity with the lbry daemon and/or network to fix. Generally these issues are best suited for the `lbry` `protocol team` but are reported in this repo because of they are part of the spee.ch implementation
Issues with lbry (e.g. the spee.ch wallet, lbrynet configuration, etc.) that require strong familiarity with the lbry daemon and/or network to fix. Generally these issues are best suited for the `lbry` `protocol team` but are reported in this repo because they are part of the spee.ch implementation

View file

@ -157,7 +157,7 @@ inquirer
method: 'channel_new',
params: {
channel_name: thumbnailChannelDefault,
amount : 0.1,
amount : '0.1',
},
})
.then(response => {

View file

@ -26,7 +26,11 @@
"thumbnailChannelId": null,
"additionalClaimAddresses": [],
"disabled": false,
"disabledMessage": "Default publishing disabled message"
"disabledMessage": "Default publishing disabled message",
"closedRegistration": false,
"serveOnlyApproved": false,
"publishOnlyApproved": false,
"approvedChannels": []
},
"startup": {
"performChecks": true,

View file

@ -35,6 +35,7 @@
@import '~row/_row';
@import '~vertical-split/_vertical-split';
@import '~tooltip/_tooltip';
@import '~social-share-link/_social-share-link';
@import '~channel-claims-display/_channel-claims-display';
@import '~dropzone/_dropzone';

View file

@ -0,0 +1,11 @@
.social-share-link {
flex-wrap: wrap;
margin-right: -0.5em;
margin-left: -0.5em;
}
.social-share-link > a{
padding-right:0.5em;
padding-left:0.5em;
padding-bottom:0.3em;
}

View file

@ -3,6 +3,7 @@ import { Route, Switch } from 'react-router-dom';
import HomePage from '@pages/HomePage';
import AboutPage from '@pages/AboutPage';
import TosPage from '@pages/TosPage';
import LoginPage from '@pages/LoginPage';
import ContentPageWrapper from '@pages/ContentPageWrapper';
import FourOhFourPage from '@pages/FourOhFourPage';
@ -13,6 +14,7 @@ const App = () => {
<Switch>
<Route exact path='/' component={HomePage} />
<Route exact path='/about' component={AboutPage} />
<Route exact path='/tos' component={TosPage} />
<Route exact path='/login' component={LoginPage} />
<Route exact path='/multisite' component={MultisitePage} />
<Route exact path='/:identifier/:claim' component={ContentPageWrapper} />

View file

@ -1,9 +1,15 @@
import React from 'react';
import Row from '@components/Row';
import {Link} from "react-router-dom";
const AboutSpeechDetails = () => {
return (
<div>
<Row>
<p className={'text--large'}>
<Link to='/tos'>Terms of Service</Link>
</p>
</Row>
<Row>
<p className={'text--large'}>
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.

View file

@ -1,9 +1,9 @@
import React from 'react';
import SpaceBetween from '@components/SpaceBetween';
import SocialShareLink from '@components/SocialShareLink';
const AssetShareButtons = ({ host, name, shortId }) => {
return (
<SpaceBetween >
<SocialShareLink >
<a
className='link--primary'
target='_blank'
@ -32,7 +32,21 @@ const AssetShareButtons = ({ host, name, shortId }) => {
>
reddit
</a>
</SpaceBetween>
<a
className='link--primary'
target='_blank'
href={`https://sharetomastodon.github.io/?title=${name}&url=${host}/${shortId}/${name}`}
>
mastodon
</a>
<a
className='link--primary'
target='_blank'
href={`https://share.diasporafoundation.org/?title=${name}&url=${host}/${shortId}/${name}`}
>
diaspora
</a>
</SocialShareLink>
);
};

View file

@ -1,8 +1,9 @@
import React from 'react';
const ButtonPrimary = ({ value, onClickHandler }) => {
const ButtonPrimary = ({ value, onClickHandler, type = 'button' }) => {
return (
<button
type={type}
className={'button button-primary'}
onClick={onClickHandler}
>

View file

@ -1,21 +0,0 @@
import React from 'react';
import ChannelLoginForm from '@containers/ChannelLoginForm';
import ChannelCreateForm from '@containers/ChannelCreateForm';
import Row from '@components/Row';
const ChannelTools = () => {
return (
<div>
<Row>
<h3>Log in to an existing channel:</h3>
<ChannelLoginForm />
</Row>
<Row>
<h3>Create a brand new channel:</h3>
<ChannelCreateForm />
</Row>
</div>
);
};
export default ChannelTools;

View file

@ -0,0 +1,13 @@
import React from 'react';
class SocialShareLink extends React.Component {
render () {
return (
<div className={'space-between social-share-link'}>
{this.props.children}
</div>
);
}
}
export default SocialShareLink;

View file

@ -1,4 +1,5 @@
import React from 'react';
import Row from '@components/Row';
import ProgressBar from '@components/ProgressBar';
import { LOCAL_CHECK, UNAVAILABLE, ERROR, AVAILABLE } from '../../constants/asset_display_states';
@ -26,8 +27,12 @@ class AssetDisplay extends React.Component {
}
{(status === ERROR) &&
<div>
<p>Unfortunately, we couldn't download your asset from LBRY. You can help us out by sharing the below error message in the <a className='link--primary' href='https://chat.lbry.io' target='_blank'>LBRY discord</a>.</p>
<i><p id='error-message'>{error}</p></i>
<Row>
<p>Unfortunately, we couldn't download your asset from LBRY. You can help us out by sharing the following error message in the <a className='link--primary' href='https://chat.lbry.io' target='_blank'>LBRY discord</a>.</p>
</Row>
<Row>
<p id='error-message'><i>{error}</i></p>
</Row>
</div>
}
{(status === AVAILABLE) &&

View file

@ -59,7 +59,7 @@ class ChannelCreateForm extends React.Component {
return (
<div>
{ !status ? (
<div>
<form onSubmit={this.handleSubmit}>
<ChannelCreateNameInput
value={name.value}
error={name.error}
@ -74,10 +74,11 @@ class ChannelCreateForm extends React.Component {
defaultMessage={'Choose a name and password for your channel'}
/>
<ButtonPrimary
type={'submit'}
value={'Create Channel'}
onClickHandler={this.handleSubmit}
/>
</div>
</form>
) : (
<div>
<p className={'text--small text--secondary'}>{status}</p>

View file

@ -49,7 +49,7 @@ class ChannelLoginForm extends React.Component {
}
render () {
return (
<div>
<form onSubmit={this.loginToChannel}>
<ChannelLoginNameInput
channelName={this.state.channelName}
handleInput={this.handleInput}
@ -63,10 +63,11 @@ class ChannelLoginForm extends React.Component {
defaultMessage={'Enter the name and password for your channel'}
/>
<ButtonPrimary
type={'submit'}
value={'Authenticate'}
onClickHandler={this.loginToChannel}
/>
</div>
</form>
);
}
}

View file

@ -1,13 +1,18 @@
import {connect} from 'react-redux';
import {setPublishInChannel, updateSelectedChannel, updateError} from '../../actions/publish';
// import isApprovedChannel from '../../../../utils/isApprovedChannel';
import View from './view';
const mapStateToProps = ({ channel, publish }) => {
const mapStateToProps = ({ publish, site, channel: { loggedInChannel: { name, shortId, longId } } }) => {
return {
loggedInChannelName: channel.loggedInChannel.name,
// isApprovedChannel : isApprovedChannel({ longId }, site.approvedChannels),
publishOnlyApproved: site.publishOnlyApproved,
// closedRegistration : site.closedRegistration,
loggedInChannelName: name,
publishInChannel : publish.publishInChannel,
selectedChannel : publish.selectedChannel,
channelError : publish.error.channel,
longId,
};
};

View file

@ -16,9 +16,12 @@ class ChannelSelect extends React.Component {
this.handleSelection = this.handleSelection.bind(this);
}
componentWillMount () {
const { loggedInChannelName } = this.props;
const { loggedInChannelName, onChannelSelect, publishOnlyApproved, onPublishInChannelChange } = this.props;
if (loggedInChannelName) {
this.props.onChannelSelect(loggedInChannelName);
onChannelSelect(loggedInChannelName);
}
if (publishOnlyApproved) {
onPublishInChannelChange(true);
}
}
toggleAnonymousPublish (event) {
@ -34,7 +37,17 @@ class ChannelSelect extends React.Component {
this.props.onChannelSelect(selectedOption);
}
render () {
const { publishInChannel, channelError, selectedChannel, loggedInChannelName } = this.props;
const { publishInChannel, channelError, selectedChannel, loggedInChannelName, publishOnlyApproved } = this.props;
if (publishOnlyApproved) {
return (
<div>
<RowLabeled
label={<Label value={'Channel:'} />}
content={<span>{loggedInChannelName}</span>}
/>
</div>
);
}
return (
<div>
<RowLabeled

View file

@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import View from './view';
const mapStateToProps = ({ site: { closedRegistration } }) => {
return {
closedRegistration,
};
};
export default connect(mapStateToProps, null)(View);

View file

@ -0,0 +1,23 @@
import React from 'react';
import ChannelLoginForm from '@containers/ChannelLoginForm';
import ChannelCreateForm from '@containers/ChannelCreateForm';
import Row from '@components/Row';
class ChannelTools extends React.Component {
render () {
return (
<div>
<Row>
<h3>Log in to an existing channel:</h3>
<ChannelLoginForm />
</Row>
{!this.props.closedRegistration && (<Row>
<h3>Create a brand new channel:</h3>
<ChannelCreateForm />
</Row>)}
</div>
);
}
}
export default ChannelTools;

View file

@ -1,12 +1,15 @@
import { connect } from 'react-redux';
import { logOutChannel, checkForLoggedInChannel } from '../../actions/channel';
import isApprovedChannel from '../../../../utils/isApprovedChannel';
import View from './view';
const mapStateToProps = ({ channel: { loggedInChannel: { name, shortId, longId } } }) => {
const mapStateToProps = ({ site, channel: { loggedInChannel: { name, shortId, longId } } }) => {
return {
channelName : name,
channelShortId: shortId,
channelLongId : longId,
showPublish : (!site.publishOnlyApproved || isApprovedChannel({ longId }, site.approvedChannels)),
closedRegistration: site.closedRegistration,
channelName : name,
channelShortId : shortId,
channelLongId : longId,
};
};

View file

@ -28,16 +28,17 @@ class NavigationLinks extends React.Component {
}
}
render () {
const { channelName, showPublish, closedRegistration } = this.props;
return (
<div className='navigation-links'>
<NavLink
{showPublish && <NavLink
className='nav-bar-link link--nav'
activeClassName='link--nav-active'
to='/'
exact
>
Publish
</NavLink>
</NavLink>}
<NavLink
className='nav-bar-link link--nav'
activeClassName='link--nav-active'
@ -45,7 +46,7 @@ class NavigationLinks extends React.Component {
>
About
</NavLink>
{ this.props.channelName ? (
{ channelName ? (
<NavBarChannelOptionsDropdown
channelName={this.props.channelName}
handleSelection={this.handleSelection}
@ -53,7 +54,7 @@ class NavigationLinks extends React.Component {
VIEW={VIEW}
LOGOUT={LOGOUT}
/>
) : (
) : !closedRegistration && (
<NavLink
id='nav-bar-login-link'
className='nav-bar-link link--nav'

View file

@ -3,12 +3,14 @@ import ErrorPage from '@pages/ErrorPage';
import ShowAssetLite from '@pages/ShowAssetLite';
import ShowAssetDetails from '@pages/ShowAssetDetails';
import ShowChannel from '@pages/ShowChannel';
import { withRouter } from 'react-router-dom';
import { CHANNEL, ASSET_LITE, ASSET_DETAILS } from '../../constants/show_request_types';
class ContentPageWrapper extends React.Component {
componentDidMount () {
this.props.onHandleShowPageUri(this.props.match.params);
const { onHandleShowPageUri, match, homeChannel } = this.props;
onHandleShowPageUri(homeChannel ? { claim: homeChannel } : match.params);
}
componentWillReceiveProps (nextProps) {
if (nextProps.match.params !== this.props.match.params) {
@ -35,4 +37,4 @@ class ContentPageWrapper extends React.Component {
}
};
export default ContentPageWrapper;
export default withRouter(ContentPageWrapper);

View file

@ -2,10 +2,11 @@ import { connect } from 'react-redux';
import { onHandleShowPageUri } from '../../actions/show';
import View from './view';
const mapStateToProps = ({ show }) => {
const mapStateToProps = ({ show, site, channel }) => {
return {
error : show.request.error,
requestType: show.request.type,
homeChannel: site.publishOnlyApproved && !channel.loggedInChannel.name ? `${site.approvedChannels[0].name}:${site.approvedChannels[0].longId}` : null,
};
};

View file

@ -1,11 +1,14 @@
import React from 'react';
import PageLayout from '@components/PageLayout';
import PublishTool from '@containers/PublishTool';
import ContentPageWrapper from '@pages/ContentPageWrapper';
class HomePage extends React.Component {
render () {
return (
const { homeChannel } = this.props;
return homeChannel ? (
<ContentPageWrapper homeChannel={homeChannel} />
) : (
<PageLayout
pageTitle={'Speech'}
pageUri={''}

View file

@ -4,7 +4,7 @@ import PageLayout from '@components/PageLayout';
import HorizontalSplit from '@components/HorizontalSplit';
import ChannelAbout from '@components/ChannelAbout';
import ChannelTools from '@components/ChannelTools';
import ChannelTools from '@containers/ChannelTools';
class LoginPage extends React.Component {
componentWillReceiveProps (newProps) {

View file

@ -0,0 +1,67 @@
import React from 'react';
import PageLayout from '@components/PageLayout';
import Row from '@components/Row';
class TosPage extends React.Component {
render () {
return (
<PageLayout
pageTitle={'Terms of Service'}
pageUri={'tos'}
>
<Row>
<h1>Terms of Service</h1>
<p>Last updated: September 25, 2018</p>
<p>Please read these Terms of Service ("Terms", "Terms of Service") carefully before using the https://spee.ch website (the "Service") operated by LBRY INC ("us", "we", or "our").</p>
<p>Your access to and use of the Service is conditioned upon your acceptance of and compliance with these Terms. These Terms apply to all visitors, users and others who wish to access or use the Service.</p>
<p>By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you do not have permission to access the Service.</p>
</Row>
<Row>
<h3>Links To Other Web Sites</h3>
<p>Our Service may contain links to third party web sites or services that are not owned or controlled by LBRY INC</p>
<p>LBRY INC has no control over, and assumes no responsibility for the content, privacy policies, or practices of any third party web sites or services. We do not warrant the offerings of any of these entities/individuals or their websites.</p>
<p>You acknowledge and agree that LBRY INC shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such third party web sites or services.</p>
<p>We strongly advise you to read the terms and conditions and privacy policies of any third party web sites or services that you visit.</p>
</Row>
<Row>
<h3>Termination</h3>
<p>We may terminate or suspend your access to the Service immediately, without prior notice or liability, under our sole discretion, for any reason whatsoever and without limitation, including but not limited to a breach of the Terms.</p>
<p>All provisions of the Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability.</p>
</Row>
<Row>
<h3>Indemnification</h3>
<p>You agree to defend, indemnify and hold harmless LBRY INC and its licensee and licensors, and their employees, contractors, agents, officers and directors, from and against any and all claims, damages, obligations, losses, liabilities, costs or debt, and expenses (including but not limited to attorney's fees), resulting from or arising out of a) your use and access of the Service, or b) a breach of these Terms.</p>
</Row>
<Row>
<h3>Limitation Of Liability</h3>
<p>In no event shall LBRY INC, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your access to or use of or inability to access or use the Service; (ii) any conduct or content of any third party on the Service; (iii) any content obtained from the Service; and (iv) unauthorized access, use or alteration of your transmissions or content, whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not we have been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose.</p>
</Row>
<Row>
<h3>Disclaimer</h3>
<p>Your use of the Service is at your sole risk. The Service is provided on an "AS IS" and "AS AVAILABLE" basis. The Service is provided without warranties of any kind, whether express or implied, including, but not limited to, implied warranties of merchantability, fitness for a particular purpose, non-infringement or course of performance.</p>
<p>LBRY INC its subsidiaries, affiliates, and its licensors do not warrant that a) the Service will function uninterrupted, secure or available at any particular time or location; b) any errors or defects will be corrected; c) the Service is free of viruses or other harmful components; or d) the results of using the Service will meet your requirements.</p>
</Row>
<Row>
<h3>Exclusions</h3>
<p>Some jurisdictions do not allow the exclusion of certain warranties or the exclusion or limitation of liability for consequential or incidental damages, so the limitations above may not apply to you.</p>
</Row>
<Row>
<h3>Governing Law</h3>
<p>These Terms shall be governed and construed in accordance with the laws of Delaware, United States, without regard to its conflict of law provisions.</p>
<p>Our failure to enforce any right or provision of these Terms will not be considered a waiver of those rights. If any provision of these Terms is held to be invalid or unenforceable by a court, the remaining provisions of these Terms will remain in effect. These Terms constitute the entire agreement between us regarding our Service, and supersede and replace any prior agreements we might have had between us regarding the Service.</p>
</Row>
<Row>
<h3>Changes</h3>
<p>We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material we will provide at least 30 days notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.</p>
<p>By continuing to access or use our Service after any revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new terms, you are no longer authorized to use the Service.</p>
</Row>
<Row>
<h3>Contact Us</h3>
<p>If you have any questions about these Terms, please <a className='link--primary' href='mailto:hello@lbry.io'>contact us</a>.</p>
</Row>
</PageLayout>
);
}
}
export default TosPage;

View file

@ -1,13 +1,18 @@
import siteConfig from '@config/siteConfig.json';
let initialState = {
description : 'default description',
googleAnalyticsId : 'default google id',
host : 'default host',
title : 'default title',
twitter : 'default twitter',
defaultDescription: 'default description',
defaultThumbnail : 'default thumbnail',
description : 'default description',
googleAnalyticsId : 'default google id',
host : 'default host',
title : 'default title',
twitter : 'default twitter',
defaultDescription : 'default description',
defaultThumbnail : 'default thumbnail',
closedRegistration : false,
serveOnlyApproved : false,
publishOnlyApproved: false,
approvedChannels : [],
};
if (siteConfig) {
@ -25,6 +30,12 @@ if (siteConfig) {
title,
twitter,
},
publishing: {
closedRegistration,
serveOnlyApproved,
publishOnlyApproved,
approvedChannels,
},
} = siteConfig;
initialState = {
@ -35,6 +46,10 @@ if (siteConfig) {
twitter,
defaultDescription,
defaultThumbnail,
closedRegistration,
serveOnlyApproved,
publishOnlyApproved,
approvedChannels,
};
}

View file

@ -1,8 +1,12 @@
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
const getChannelData = require('./getChannelData.js');
const isApprovedChannel = require('../../../../../utils/isApprovedChannel');
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const NO_CHANNEL = 'NO_CHANNEL';
const LONG_ID = 'longId';
const SHORT_ID = 'shortId';
const LONG_CLAIM_LENGTH = 40;
/*
@ -14,6 +18,16 @@ const channelData = ({ ip, originalUrl, body, params }, res) => {
const channelName = params.channelName;
let channelClaimId = params.channelClaimId;
if (channelClaimId === 'none') channelClaimId = null;
const chanObj = {};
if (channelName) chanObj.name = channelName;
if (channelClaimId) chanObj[(channelClaimId.length === LONG_CLAIM_LENGTH ? LONG_ID : SHORT_ID)] = channelClaimId;
if (serveOnlyApproved && !isApprovedChannel(chanObj, approvedChannels)) {
return res.status(404).json({
success: false,
message: 'This content is unavailable',
});
}
getChannelData(channelName, channelClaimId)
.then(data => {
res.status(200).json({

View file

@ -18,7 +18,7 @@ const createPublishParams = (filePath, name, title, description, license, nsfw,
const publishParams = {
name,
file_path: filePath,
bid : 0.01,
bid : '0.01',
metadata : {
description,
title,

View file

@ -10,7 +10,7 @@ const createThumbnailPublishParams = (thumbnailFilePath, claimName, license, nsf
return {
name : `${claimName}-thumb`,
file_path: thumbnailFilePath,
bid : 0.01,
bid : '0.01',
metadata : {
title : `${claimName} thumbnail`,
description: `a thumbnail for ${claimName}`,

View file

@ -3,6 +3,8 @@ const logger = require('winston');
const { details: { host }, publishing: { disabled, disabledMessage } } = require('@config/siteConfig');
const { sendGATimingEvent } = require('../../../../utils/googleAnalytics.js');
const isApprovedChannel = require('../../../../../utils/isApprovedChannel');
const { publishing: { publishOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
@ -16,6 +18,7 @@ const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
const authenticateUser = require('./authentication.js');
const CLAIM_TAKEN = 'CLAIM_TAKEN';
const UNAPPROVED_CHANNEL = 'UNAPPROVED_CHANNEL';
/*
@ -54,6 +57,13 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
// check channel authorization
authenticateUser(channelName, channelId, channelPassword, user)
.then(({ channelName, channelClaimId }) => {
if (publishOnlyApproved && !isApprovedChannel({ longId: channelClaimId }, approvedChannels)) {
const error = {
name : UNAPPROVED_CHANNEL,
message: 'This spee.ch instance only allows publishing to approved channels',
};
throw error;
}
return Promise.all([
checkClaimAvailability(name),
createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId),
@ -92,7 +102,7 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
sendGATimingEvent('end-to-end', 'publish', fileType, gaStartTime, Date.now());
})
.catch(error => {
if (error.name === CLAIM_TAKEN) {
if ([CLAIM_TAKEN, UNAPPROVED_CHANNEL].includes(error.name)) {
res.status(400).json({
success: false,
message: error.message,

View file

@ -1,6 +1,7 @@
const logger = require('winston');
const db = require('../../../models');
const isApprovedChannel = require('../../../../utils/isApprovedChannel');
const getClaimId = require('../../utils/getClaimId.js');
const { handleErrorResponse } = require('../../utils/errorHandlers.js');
@ -11,17 +12,28 @@ const NO_CHANNEL = 'NO_CHANNEL';
const NO_CLAIM = 'NO_CLAIM';
const BLOCKED_CLAIM = 'BLOCKED_CLAIM';
const NO_FILE = 'NO_FILE';
const CONTENT_UNAVAILABLE = 'CONTENT_UNAVAILABLE';
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId, originalUrl, ip, res) => {
getClaimId(channelName, channelClaimId, claimName, claimId)
.then(fullClaimId => {
claimId = fullClaimId;
logger.debug('Full claim id:', fullClaimId);
return db.Claim.getOutpoint(claimName, fullClaimId);
return db.Claim.findOne({
where: {
name : claimName,
claimId: fullClaimId,
},
});
})
.then(outpoint => {
logger.debug('Outpoint:', outpoint);
return db.Blocked.isNotBlocked(outpoint);
.then(claim => {
if (serveOnlyApproved && !isApprovedChannel({ longId: claim.dataValues.certificateId }, approvedChannels)) {
throw new Error(CONTENT_UNAVAILABLE);
}
logger.debug('Outpoint:', claim.dataValues.outpoint);
return db.Blocked.isNotBlocked(claim.dataValues.outpoint);
})
.then(() => {
return db.File.findOne({
@ -52,6 +64,13 @@ const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId
message: 'No matching channel id could be found for that url',
});
}
if (error === CONTENT_UNAVAILABLE) {
logger.debug('unapproved channel');
return res.status(400).json({
success: false,
message: 'This content is unavailable',
});
}
if (error === BLOCKED_CLAIM) {
logger.debug('claim was blocked');
return res.status(451).json({

View file

@ -78,7 +78,7 @@ function Server () {
app.use(speechPassport.session());
// configure handlebars & register it with express app
const viewsPath = Path.resolve(process.cwd(), 'node_modules/spee.ch/server/views');
const viewsPath = Path.resolve(process.cwd(), 'server/views');
app.engine('handlebars', expressHandlebars({
async : false,
dataType : 'text',

View file

@ -116,7 +116,7 @@ module.exports = {
method: 'channel_new',
params: {
channel_name: name,
amount : 0.1,
amount : '0.1',
},
})
.then(response => {

View file

@ -2,13 +2,13 @@ const logger = require('winston');
const handleLbrynetResponse = ({ data }, resolve, reject) => {
logger.debug('lbry api data:', data);
if (data.result) {
if (data) {
// check for an error
if (data.result.error) {
logger.debug('Lbrynet api error:', data.result.error);
reject(new Error(data.result.error));
if (data.error) {
logger.debug('Lbrynet api error:', data.error);
reject(new Error(data.error.message));
return;
};
}
resolve(data.result);
return;
}

View file

@ -1,6 +1,8 @@
const logger = require('winston');
const returnShortId = require('./utils/returnShortId.js');
const isApprovedChannel = require('../../utils/isApprovedChannel');
const { assetDefaults: { thumbnail: defaultThumbnail }, details: { host } } = require('@config/siteConfig');
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const NO_CLAIM = 'NO_CLAIM';
@ -354,7 +356,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}
};
Claim.resolveClaim = function (name, claimId) {
Claim.fetchClaim = function (name, claimId) {
logger.debug(`Claim.resolveClaim: ${name} ${claimId}`);
return new Promise((resolve, reject) => {
this
@ -378,6 +380,23 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
});
};
Claim.resolveClaim = function (name, claimId) {
return new Promise((resolve, reject) => {
this
.fetchClaim(name, claimId)
.then(claim => {
logger.info('resolveClaim claims:', claim);
if (serveOnlyApproved && !isApprovedChannel({ longId: claim.certificateId }, approvedChannels)) {
throw new Error('This content is unavailable');
}
return resolve(claim);
})
.catch(error => {
reject(error);
});
});
};
Claim.getOutpoint = function (name, claimId) {
logger.debug(`finding outpoint for ${name}#${claimId}`);
return this

View file

@ -6,6 +6,7 @@ module.exports = {
'/': { controller: handlePageRequest },
'/login': { controller: handlePageRequest },
'/about': { controller: handlePageRequest },
'/tos': { controller: handlePageRequest },
'/trending': { controller: redirect('/popular') },
'/popular': { controller: handlePageRequest },
'/new': { controller: handlePageRequest },

View file

@ -2,6 +2,7 @@ const PassportLocalStrategy = require('passport-local').Strategy;
const { createChannel } = require('../../lbrynet');
const logger = require('winston');
const db = require('../../models');
const { publishing: { closedRegistration } } = require('@config/siteConfig');
module.exports = new PassportLocalStrategy(
{
@ -9,10 +10,13 @@ module.exports = new PassportLocalStrategy(
passwordField: 'password',
},
(username, password, done) => {
if (closedRegistration) {
return done('Registration is disabled');
}
logger.verbose(`new channel signup request. user: ${username} pass: ${password} .`);
let userInfo = {};
// server-side validaton of inputs (username, password)
// create the channel and retrieve the metadata
return createChannel(`@${username}`)
.then(tx => {

View file

@ -0,0 +1,9 @@
function isApprovedChannel (channel, channels) {
const { name, shortId: short, longId: long } = channel;
return Boolean(
(long && channels.find(chan => chan.longId === long)) ||
(name && short && channels.find(chan => chan.name === name && chan.shortId === short))
);
}
module.exports = isApprovedChannel;