Merge branch 'master' into add-chainquery
This commit is contained in:
commit
5a7e10574f
38 changed files with 353 additions and 96 deletions
40
README.md
40
README.md
|
@ -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
|
||||
|
|
|
@ -157,7 +157,7 @@ inquirer
|
|||
method: 'channel_new',
|
||||
params: {
|
||||
channel_name: thumbnailChannelDefault,
|
||||
amount : 0.1,
|
||||
amount : '0.1',
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
11
client/scss/social-share-link/_social-share-link.scss
Normal file
11
client/scss/social-share-link/_social-share-link.scss
Normal 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;
|
||||
}
|
|
@ -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} />
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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;
|
13
client/src/components/SocialShareLink/index.jsx
Normal file
13
client/src/components/SocialShareLink/index.jsx
Normal 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;
|
|
@ -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) &&
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
10
client/src/containers/ChannelTools/index.js
Normal file
10
client/src/containers/ChannelTools/index.js
Normal 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);
|
23
client/src/containers/ChannelTools/view.jsx
Normal file
23
client/src/containers/ChannelTools/view.jsx
Normal 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;
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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={''}
|
||||
|
|
|
@ -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) {
|
||||
|
|
67
client/src/pages/TosPage/index.jsx
Normal file
67
client/src/pages/TosPage/index.jsx
Normal 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;
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}`,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -116,7 +116,7 @@ module.exports = {
|
|||
method: 'channel_new',
|
||||
params: {
|
||||
channel_name: name,
|
||||
amount : 0.1,
|
||||
amount : '0.1',
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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 => {
|
||||
|
|
9
utils/isApprovedChannel.js
Normal file
9
utils/isApprovedChannel.js
Normal 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;
|
Loading…
Reference in a new issue