Staging #1066
23 changed files with 624 additions and 250 deletions
251
README.md
251
README.md
|
@ -1,6 +1,6 @@
|
||||||
# Spee.ch
|
# Spee.ch
|
||||||
|
|
||||||
spee.ch provides a user-friendly, custom-designed, image and video hosting site backed by a decentralized network and
|
spee.ch provides a user-friendly, custom-designed, image and video hosting site backed by a decentralized network and
|
||||||
blockchain ([LBRY](https://lbry.tech/)). Via just a small set of config files, you can spin your an entire spee.ch site back up including assets.
|
blockchain ([LBRY](https://lbry.tech/)). Via just a small set of config files, you can spin your an entire spee.ch site back up including assets.
|
||||||
|
|
||||||
![App GIF](https://spee.ch/e/speechgif.gif)
|
![App GIF](https://spee.ch/e/speechgif.gif)
|
||||||
|
@ -18,56 +18,64 @@ For a closed, custom-hosted and branded example, check out https://lbry.theantim
|
||||||
### Full Instructions
|
### Full Instructions
|
||||||
|
|
||||||
#### Get some information ready:
|
#### Get some information ready:
|
||||||
* mysqlusername
|
|
||||||
* mysqlpassword
|
- mysqlusername
|
||||||
* domainname or 'http://localhost:3000'
|
- mysqlpassword
|
||||||
* speechport = 3000
|
- domainname or 'http://localhost:3000'
|
||||||
|
- speechport = 3000
|
||||||
|
|
||||||
#### Install and Set Up Dependencies
|
#### Install and Set Up Dependencies
|
||||||
* Firewall open ports
|
|
||||||
* 22
|
|
||||||
* 80
|
|
||||||
* 443
|
|
||||||
* 3333
|
|
||||||
* 4444
|
|
||||||
* [NodeJS](https://nodejs.org)
|
|
||||||
* [MySQL version 5.7 or higher](https://dev.mysql.com/doc/refman/8.0/en/installing.html)
|
|
||||||
* mysqlusername or root
|
|
||||||
* mysqlpassword
|
|
||||||
* Requires mysql_native_password plugin
|
|
||||||
```
|
|
||||||
mysql> `ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword';`
|
|
||||||
```
|
|
||||||
* [lbrynet](https://github.com/lbryio/lbry) daemon
|
|
||||||
* run this as a service exposing ports 3333 and 4444
|
|
||||||
* _note_: once the daemon is running, issue commands in another terminal session (tmux) to retrieve an address for your wallet to recieve 5+ LBC credits (or join us in the [#speech discord channel](https://discord.gg/YjYbwhS) and we will send you a few)
|
|
||||||
* `./lbrynet commands` gets a list of commands
|
|
||||||
* `./lbrynet account_balance` gets your balance (initially 0.0)
|
|
||||||
* `./lbrynet address_list` gets addresses you can use to recieve LBC
|
|
||||||
* [FFmpeg](https://www.ffmpeg.org/download.html)
|
|
||||||
* Spee.ch (below)
|
|
||||||
* pm2 (optional) process manager such as pm2 to run speech server.js
|
|
||||||
* http proxy server e.g. caddy, nginx, or traefik, to forward 80/443 to speech port 3000
|
|
||||||
* _note: even running on http://localhost, you must redirect http or https to port 3000_
|
|
||||||
|
|
||||||
|
- Firewall open ports
|
||||||
|
- 22
|
||||||
|
- 80
|
||||||
|
- 443
|
||||||
|
- 3333
|
||||||
|
- 4444
|
||||||
|
- [NodeJS](https://nodejs.org)
|
||||||
|
- [MySQL version 5.7 or higher](https://dev.mysql.com/doc/refman/8.0/en/installing.html)
|
||||||
|
- mysqlusername or root
|
||||||
|
- mysqlpassword
|
||||||
|
- Requires mysql_native_password plugin
|
||||||
|
```
|
||||||
|
mysql> `ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword';`
|
||||||
|
```
|
||||||
|
- [lbrynet](https://github.com/lbryio/lbry) daemon
|
||||||
|
- run this as a service exposing ports 3333 and 4444
|
||||||
|
- _note_: once the daemon is running, issue commands in another terminal session (tmux) to retrieve an address for your wallet to recieve 5+ LBC credits (or join us in the [#speech discord channel](https://discord.gg/YjYbwhS) and we will send you a few)
|
||||||
|
- `./lbrynet commands` gets a list of commands
|
||||||
|
- `./lbrynet account_balance` gets your balance (initially 0.0)
|
||||||
|
- `./lbrynet address_list` gets addresses you can use to recieve LBC
|
||||||
|
- [FFmpeg](https://www.ffmpeg.org/download.html)
|
||||||
|
- Spee.ch (below)
|
||||||
|
- pm2 (optional) process manager such as pm2 to run speech server.js
|
||||||
|
- http proxy server e.g. caddy, nginx, or traefik, to forward 80/443 to speech port 3000
|
||||||
|
- _note: even running on http://localhost, you must redirect http or https to port 3000_
|
||||||
|
|
||||||
#### Clone spee.ch
|
#### Clone spee.ch
|
||||||
* release version for stable production
|
|
||||||
|
- release version for stable production
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone -b release https://github.com/lbryio/spee.ch.git
|
$ git clone -b release https://github.com/lbryio/spee.ch.git
|
||||||
```
|
```
|
||||||
* master version for development
|
|
||||||
|
- master version for development
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/lbryio/spee.ch.git
|
$ git clone https://github.com/lbryio/spee.ch.git
|
||||||
```
|
```
|
||||||
* your own fork for customization
|
|
||||||
|
|
||||||
#### Change directory into your project
|
- your own fork for customization
|
||||||
|
|
||||||
|
#### Change directory into your project
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd spee.ch
|
$ cd spee.ch
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Install node dependencies
|
#### Install node dependencies
|
||||||
|
|
||||||
```
|
```
|
||||||
$ npm install
|
$ npm install
|
||||||
```
|
```
|
||||||
|
@ -91,7 +99,8 @@ $ npm run start
|
||||||
```
|
```
|
||||||
|
|
||||||
#### View in browser
|
#### View in browser
|
||||||
* Visit [http://localhost:3000](http://localhost:3000) in your browser
|
|
||||||
|
- Visit [http://localhost:3000](http://localhost:3000) in your browser
|
||||||
|
|
||||||
#### Customize your app
|
#### Customize your app
|
||||||
|
|
||||||
|
@ -99,34 +108,44 @@ Check out the [customization guide](https://github.com/lbryio/spee.ch/blob/readm
|
||||||
|
|
||||||
#### (optional) add custom components and update the styles
|
#### (optional) add custom components and update the styles
|
||||||
|
|
||||||
* Create custom components by creating React components in `site/custom/src/`
|
- Create custom components by creating React components in `site/custom/src/`
|
||||||
* Update or override the CSS by changing the files in `site/custom/scss`
|
- Update or override the CSS by changing the files in `site/custom/scss`
|
||||||
|
|
||||||
#### (optional) install your own chainquery
|
#### (optional) install your own chainquery
|
||||||
|
|
||||||
Instructions are coming at [lbry-docker] to install your own chainquery instance using docker-compose. This will require 50GB of preferably SSD space and at least 10 minutes to download, possibly much longer.
|
Instructions are coming at [lbry-docker] to install your own chainquery instance using docker-compose. This will require 50GB of preferably SSD space and at least 10 minutes to download, possibly much longer.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
There are a number of settings available for customizing the behavior of your installation.
|
||||||
|
_INSERT LINK TO SETTINGS.MD_
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
#### /api/claim/publish
|
#### /api/claim/publish
|
||||||
|
|
||||||
method: `POST`
|
method: `POST`
|
||||||
|
|
||||||
example:
|
example:
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim/publish
|
curl -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim/publish
|
||||||
```
|
```
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
* `name` (required, must be unique across the instance)
|
- `name` (required, must be unique across the instance)
|
||||||
* `file` (required) (must be type .mp4, .jpeg, .jpg, .gif, or .png)
|
- `file` (required) (must be type .mp4, .jpeg, .jpg, .gif, or .png)
|
||||||
* `nsfw` (optional)
|
- `nsfw` (optional)
|
||||||
* `license` (optional)
|
- `license` (optional)
|
||||||
* `title` (optional)
|
- `title` (optional)
|
||||||
* `description` (optional)
|
- `description` (optional)
|
||||||
* `thumbnail` URL to thumbnail image, for .mp4 uploads only (optional)
|
- `thumbnail` URL to thumbnail image, for .mp4 uploads only (optional)
|
||||||
* `channelName` channel to publish too (optional)
|
- `channelName` channel to publish too (optional)
|
||||||
* `channelPassword` password for channel to publish too (optional, but required if `channelName` is provided)
|
- `channelPassword` password for channel to publish too (optional, but required if `channelName` is provided)
|
||||||
|
|
||||||
response:
|
response:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"success": <bool>,
|
"success": <bool>,
|
||||||
|
@ -150,13 +169,17 @@ response:
|
||||||
```
|
```
|
||||||
|
|
||||||
#### /api/claim/availability/:name
|
#### /api/claim/availability/:name
|
||||||
|
|
||||||
method: `GET`
|
method: `GET`
|
||||||
|
|
||||||
example:
|
example:
|
||||||
|
|
||||||
```
|
```
|
||||||
curl https://spee.ch/api/claim/availability/doitlive
|
curl https://spee.ch/api/claim/availability/doitlive
|
||||||
```
|
```
|
||||||
|
|
||||||
response:
|
response:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"success": <bool>, // `true` if spee.ch successfully checked the claim availability
|
"success": <bool>, // `true` if spee.ch successfully checked the claim availability
|
||||||
|
@ -168,90 +191,98 @@ response:
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
### Stack
|
### 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.
|
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
* server
|
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.
|
||||||
* [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/)
|
|
||||||
* client
|
|
||||||
* [react](https://reactjs.org/)
|
|
||||||
* redux
|
|
||||||
* sagas
|
|
||||||
* scss
|
|
||||||
* handlebars
|
|
||||||
|
|
||||||
|
- server
|
||||||
|
- [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/)
|
||||||
|
- client
|
||||||
|
- [react](https://reactjs.org/)
|
||||||
|
- redux
|
||||||
|
- sagas
|
||||||
|
- scss
|
||||||
|
- handlebars
|
||||||
|
|
||||||
### Architecture
|
### Architecture
|
||||||
* `cli/` contains the code for the CLI tool. Running the tool will create `.json` config files and place them in the `site/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
|
|
||||||
|
|
||||||
* `client/` contains all of the client code
|
- `cli/` contains the code for the CLI tool. Running the tool will create `.json` config files and place them in the `site/config/` folder
|
||||||
* The client side of spee.ch uses `React` and `Redux`
|
|
||||||
* `client/src/index.js` is the entry point for the client side js. It checks for preloaded state, creates the store, and places the `<App />` component in the document.
|
|
||||||
* `client/src/app.js` holds the `<App />` component, which contains the routes for `react-router-dom`
|
|
||||||
* `client/src/` contains all of the JSX code for the app. When the app is built, the content of this folder is transpiled into the `client/build/` folder.
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
|
|
||||||
* `site/custom` is a folder which can be used to override the default components in `client/`
|
- `configure.js` is the entry point for the CLI tool
|
||||||
* The folder structure mimics that of the `client/` folder
|
- `cli/defaults/` holds default config files
|
||||||
* to customize spee.ch, place your own components and scss in the `site/custom/src/` and `site/custom/scss` folders.
|
- `cli/questions/` holds the questions that the CLI tool asks to build the config files
|
||||||
|
|
||||||
* `server/` contains all of the server code
|
- `client/` contains all of the client 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.
|
|
||||||
* `server/routes/` contains all of the routes for the express app
|
|
||||||
* `server/controllers/` contains all of the controllers for all of the routes
|
|
||||||
* `server/models/` contains all of the models which the app uses to interact with the `MySQL` database.
|
|
||||||
* Spee.ch uses the [sequelize](http://docs.sequelizejs.com/) ORM for communicating with the database.
|
|
||||||
|
|
||||||
* `tests/` holds the end-to-end tests for this project
|
- The client side of spee.ch uses `React` and `Redux`
|
||||||
* Spee.ch uses `mocha` with the `chai` assertion library
|
- `client/src/index.js` is the entry point for the client side js. It checks for preloaded state, creates the store, and places the `<App />` component in the document.
|
||||||
* unit tests are located inside the project in-line with the files being tested and are designated with a `xxxx.test.js` file name
|
- `client/src/app.js` holds the `<App />` component, which contains the routes for `react-router-dom`
|
||||||
|
- `client/src/` contains all of the JSX code for the app. When the app is built, the content of this folder is transpiled into the `client/build/` folder.
|
||||||
|
- 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 \*
|
||||||
|
|
||||||
|
- `site/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 and scss in the `site/custom/src/` and `site/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.
|
||||||
|
- `server/routes/` contains all of the routes for the express app
|
||||||
|
- `server/controllers/` contains all of the controllers for all of the routes
|
||||||
|
- `server/models/` contains all of the models which the app uses to interact with the `MySQL` database.
|
||||||
|
- Spee.ch uses the [sequelize](http://docs.sequelizejs.com/) ORM for communicating with the database.
|
||||||
|
|
||||||
|
- `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 designated with a `xxxx.test.js` file name
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
* This package uses `mocha` with `chai` for testing.
|
|
||||||
* Before running tests, create a `testingConfig.js` file in `devConfig/` by copying `testingConfig.example.js`
|
- This package uses `mocha` with `chai` for testing.
|
||||||
* To run tests:
|
- Before running tests, create a `testingConfig.js` file in `devConfig/` by copying `testingConfig.example.js`
|
||||||
* To run all tests, including those that require LBC (like publishing), simply run `npm test`
|
- To run tests:
|
||||||
* To run only tests that do not require LBC, run `npm run test:no-lbc`
|
- To run all tests, including those that require LBC (like publishing), simply run `npm test`
|
||||||
|
- To run only tests that do not require LBC, run `npm run test:no-lbc`
|
||||||
|
|
||||||
### URL formats
|
### 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. You can learn more about LBRY URLs [here](https://lbry.tech/resources/uri).
|
|
||||||
|
|
||||||
* retrieve the controlling `LBRY` claim:
|
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. You can learn more about LBRY URLs [here](https://lbry.tech/resources/uri).
|
||||||
* https://spee.ch/`claim`
|
|
||||||
* https://spee.ch/`claim`.`ext` (serve)
|
- retrieve the controlling `LBRY` claim:
|
||||||
* retrieve a specific `LBRY` claim:
|
- https://spee.ch/`claim`
|
||||||
* https://spee.ch/`claim_id`/`claim`
|
- https://spee.ch/`claim`.`ext` (serve)
|
||||||
* https://spee.ch/`claim_id`/`claim`.`ext` (serve)
|
- retrieve a specific `LBRY` claim:
|
||||||
* retrieve all contents for the controlling `LBRY` channel
|
- https://spee.ch/`claim_id`/`claim`
|
||||||
* https://spee.ch/`@channel`
|
- https://spee.ch/`claim_id`/`claim`.`ext` (serve)
|
||||||
* a specific `LBRY` channel
|
- retrieve all contents for the controlling `LBRY` channel
|
||||||
* https://spee.ch/`@channel`:`channel_id`
|
- https://spee.ch/`@channel`
|
||||||
* retrieve a specific claim within the controlling `LBRY` channel
|
- a specific `LBRY` channel
|
||||||
* https://spee.ch/`@channel`/`claim`
|
- https://spee.ch/`@channel`:`channel_id`
|
||||||
* https://spee.ch/`@channel`/`claim`.`ext` (serve)
|
- retrieve a specific claim within the controlling `LBRY` channel
|
||||||
* retrieve a specific claim within a specific `LBRY` channel
|
- https://spee.ch/`@channel`/`claim`
|
||||||
* https://spee.ch/`@channel`:`channel_id`/`claim`
|
- https://spee.ch/`@channel`/`claim`.`ext` (serve)
|
||||||
* https://spee.ch/`@channel`:`channel_id`/`claim`.`ext` (serve)
|
- retrieve a specific claim within a specific `LBRY` channel
|
||||||
|
- https://spee.ch/`@channel`:`channel_id`/`claim`
|
||||||
|
- https://spee.ch/`@channel`:`channel_id`/`claim`.`ext` (serve)
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
Spee.ch depends on two other lbry technologies:
|
Spee.ch depends on two other lbry technologies:
|
||||||
* [chainquery](https://github.com/lbryio/chainquery) - a normalized database of the blockchain data. We've provided credentials to use a public chainquery service. You can also install it on your own server to avoid being affected by the commons.
|
|
||||||
* [lbrynet](https://github.com/lbryio/lbry) - a daemon that handles your wallet and transactions.
|
- [chainquery](https://github.com/lbryio/chainquery) - a normalized database of the blockchain data. We've provided credentials to use a public chainquery service. You can also install it on your own server to avoid being affected by the commons.
|
||||||
|
- [lbrynet](https://github.com/lbryio/lbry) - a daemon that handles your wallet and transactions.
|
||||||
|
|
||||||
### Bugs
|
### 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!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -13,7 +13,8 @@ let thumbnailChannelDefault = '@thumbnails';
|
||||||
let thumbnailChannel = '';
|
let thumbnailChannel = '';
|
||||||
let thumbnailChannelId = '';
|
let thumbnailChannelId = '';
|
||||||
|
|
||||||
const createConfigFile = (fileName, configObject, topSecret) => { // siteConfig.json , siteConfig
|
const createConfigFile = (fileName, configObject, topSecret) => {
|
||||||
|
// siteConfig.json , siteConfig
|
||||||
const fileLocation = topSecret
|
const fileLocation = topSecret
|
||||||
? Path.resolve(__dirname, `../site/private/${fileName}`)
|
? Path.resolve(__dirname, `../site/private/${fileName}`)
|
||||||
: Path.resolve(__dirname, `../site/config/${fileName}`);
|
: Path.resolve(__dirname, `../site/config/${fileName}`);
|
||||||
|
@ -39,15 +40,8 @@ try {
|
||||||
siteConfig = require('./defaults/siteConfig.json');
|
siteConfig = require('./defaults/siteConfig.json');
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
details: {
|
details: { port, title, host },
|
||||||
port,
|
publishing: { uploadDirectory, channelClaimBidAmount: channelBid },
|
||||||
title,
|
|
||||||
host,
|
|
||||||
},
|
|
||||||
publishing: {
|
|
||||||
uploadDirectory,
|
|
||||||
channelClaimBidAmount: channelBid,
|
|
||||||
},
|
|
||||||
} = siteConfig;
|
} = siteConfig;
|
||||||
|
|
||||||
let lbryConfig;
|
let lbryConfig;
|
||||||
|
@ -80,12 +74,12 @@ try {
|
||||||
|
|
||||||
// authConfig
|
// authConfig
|
||||||
let randSessionKey = pwGenerator.generate({
|
let randSessionKey = pwGenerator.generate({
|
||||||
length : 20,
|
length: 20,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
let randMasterPass = pwGenerator.generate({
|
let randMasterPass = pwGenerator.generate({
|
||||||
length : 20,
|
length: 20,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -94,7 +88,7 @@ try {
|
||||||
authConfig = require('../site/private/authConfig.json');
|
authConfig = require('../site/private/authConfig.json');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
authConfig = {
|
authConfig = {
|
||||||
sessionKey : randSessionKey,
|
sessionKey: randSessionKey,
|
||||||
masterPassword: randMasterPass,
|
masterPassword: randMasterPass,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -111,7 +105,7 @@ inquirer
|
||||||
console.log('\nRetrieving your primary claim address from LBRY...');
|
console.log('\nRetrieving your primary claim address from LBRY...');
|
||||||
return axios
|
return axios
|
||||||
.post('http://localhost:5279', {
|
.post('http://localhost:5279', {
|
||||||
method: 'wallet_list',
|
method: 'address_list',
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
|
@ -125,7 +119,8 @@ inquirer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error('No data received from lbrynet');
|
throw new Error('No data received from lbrynet');
|
||||||
}).catch(error => {
|
})
|
||||||
|
.catch(error => {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -159,7 +154,10 @@ inquirer
|
||||||
}
|
}
|
||||||
console.log('name:', foundChannel.name);
|
console.log('name:', foundChannel.name);
|
||||||
console.log('claim_id:', foundChannel.claim_id);
|
console.log('claim_id:', foundChannel.claim_id);
|
||||||
if (foundChannel.name === thumbnailChannel && foundChannel.claim_id === thumbnailChannelId) {
|
if (
|
||||||
|
foundChannel.name === thumbnailChannel &&
|
||||||
|
foundChannel.claim_id === thumbnailChannelId
|
||||||
|
) {
|
||||||
console.log('No update to existing thumbnail config required\n');
|
console.log('No update to existing thumbnail config required\n');
|
||||||
} else {
|
} else {
|
||||||
console.log(`Replacing thumbnail channel in config...`);
|
console.log(`Replacing thumbnail channel in config...`);
|
||||||
|
@ -174,11 +172,12 @@ inquirer
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
throw new Error('No data received from lbrynet');
|
throw new Error('No data received from lbrynet');
|
||||||
}).catch(error => {
|
})
|
||||||
|
.catch(error => {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((thumbnailChannelAlreadyExists) => {
|
.then(thumbnailChannelAlreadyExists => {
|
||||||
// exit if a channel already exists, skip this step
|
// exit if a channel already exists, skip this step
|
||||||
if (thumbnailChannelAlreadyExists) {
|
if (thumbnailChannelAlreadyExists) {
|
||||||
return;
|
return;
|
||||||
|
@ -190,7 +189,7 @@ inquirer
|
||||||
method: 'channel_new',
|
method: 'channel_new',
|
||||||
params: {
|
params: {
|
||||||
channel_name: thumbnailChannelDefault,
|
channel_name: thumbnailChannelDefault,
|
||||||
amount : channelBid,
|
amount: channelBid,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
@ -207,7 +206,8 @@ inquirer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error('No data received from lbrynet');
|
throw new Error('No data received from lbrynet');
|
||||||
}).catch(error => {
|
})
|
||||||
|
.catch(error => {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -232,11 +232,15 @@ inquirer
|
||||||
createConfigFile('authConfig.json', authConfig, true);
|
createConfigFile('authConfig.json', authConfig, true);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('\nYou\'re all done!');
|
console.log("\nYou're all done!");
|
||||||
console.log('\nIt\'s a good idea to BACK UP YOUR MASTER PASSWORD \nin "/site/private/authConfig.json" so that you don\'t lose \ncontrol of your channel.');
|
console.log(
|
||||||
|
'\nIt\'s a good idea to BACK UP YOUR MASTER PASSWORD \nin "/site/private/authConfig.json" so that you don\'t lose \ncontrol of your channel.'
|
||||||
|
);
|
||||||
|
|
||||||
console.log('\nNext step: run "npm run start" to build and start your server!');
|
console.log('\nNext step: run "npm run start" to build and start your server!');
|
||||||
console.log('If you want to change any settings, you can edit the files in the "/site" folder.');
|
console.log(
|
||||||
|
'If you want to change any settings, you can edit the files in the "/site" folder.'
|
||||||
|
);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"publishing": {
|
"publishing": {
|
||||||
"primaryClaimAddress": null,
|
"primaryClaimAddress": null,
|
||||||
"uploadDirectory": "/home/lbry/Uploads",
|
"uploadDirectory": "/home/lbry/Uploads",
|
||||||
"lbrynetHome": "/home/lbry",
|
"lbrynetHome": "/CURRENTLYUNUSED",
|
||||||
"thumbnailChannel": null,
|
"thumbnailChannel": null,
|
||||||
"thumbnailChannelId": null,
|
"thumbnailChannelId": null,
|
||||||
"additionalClaimAddresses": [],
|
"additionalClaimAddresses": [],
|
||||||
|
@ -36,20 +36,75 @@
|
||||||
"publishingChannelWhitelist": [],
|
"publishingChannelWhitelist": [],
|
||||||
"channelClaimBidAmount": "0.1",
|
"channelClaimBidAmount": "0.1",
|
||||||
"fileClaimBidAmount": "0.01",
|
"fileClaimBidAmount": "0.01",
|
||||||
|
"fileSizeLimits": {
|
||||||
|
"image": 50000000,
|
||||||
|
"video": 50000000,
|
||||||
|
"audio": 50000000,
|
||||||
|
"text": 50000000,
|
||||||
|
"model": 50000000,
|
||||||
|
"application": 50000000,
|
||||||
|
"customByContentType": {
|
||||||
|
"application/octet-stream": 50000000
|
||||||
|
}
|
||||||
|
},
|
||||||
"maxSizeImage": 10000000,
|
"maxSizeImage": 10000000,
|
||||||
"maxSizeGif": 50000000,
|
"maxSizeGif": 50000000,
|
||||||
"maxSizeVideo": 50000000
|
"maxSizeVideo": 50000000
|
||||||
},
|
},
|
||||||
"serving": {
|
"serving": {
|
||||||
"markdownSettings": {
|
"markdownSettings": {
|
||||||
"skipHtml": true,
|
"skipHtmlMain": true,
|
||||||
"privilegedDisallowedTypesDescriptions": ["Image"],
|
"escapeHtmlMain": true,
|
||||||
"privilegedDisallowedTypesMain": [],
|
"skipHtmlDescriptions": true,
|
||||||
"publicDisallowedTypesDescriptions": ["Image"],
|
"escapeHtmlDescriptions": true,
|
||||||
"publicDisallowedTypesMain": []
|
"allowedTypesMain": [],
|
||||||
|
"allowedTypesDescriptions": [],
|
||||||
|
"allowedTypesExample": [
|
||||||
|
"see react-markdown docs",
|
||||||
|
"root",
|
||||||
|
"text",
|
||||||
|
"break",
|
||||||
|
"paragraph",
|
||||||
|
"emphasis",
|
||||||
|
"strong",
|
||||||
|
"thematicBreak",
|
||||||
|
"blockquote",
|
||||||
|
"delete",
|
||||||
|
"link",
|
||||||
|
"image",
|
||||||
|
"linkReference",
|
||||||
|
"imageReference",
|
||||||
|
"table",
|
||||||
|
"tableHead",
|
||||||
|
"tableBody",
|
||||||
|
"tableRow",
|
||||||
|
"tableCell",
|
||||||
|
"list",
|
||||||
|
"listItem",
|
||||||
|
"heading",
|
||||||
|
"inlineCode",
|
||||||
|
"code",
|
||||||
|
"html",
|
||||||
|
"parsedHtml"
|
||||||
|
],
|
||||||
|
"disallowedTypesMain": [],
|
||||||
|
"disallowedTypesDescriptions": ["image", "html"],
|
||||||
|
"disallowedTypesExample": ["image", "html"]
|
||||||
},
|
},
|
||||||
"customFileExtensions": {
|
"customFileExtensions": {
|
||||||
"application/example-type": "example"
|
"application/x-troff-man": ".man",
|
||||||
|
"application/x-troff-me": ".me",
|
||||||
|
"application/x-mif": ".mif",
|
||||||
|
"application/x-troff-ms": ".ms",
|
||||||
|
"application/x-troff": ".roff",
|
||||||
|
"application/x-python-code": ".pyc",
|
||||||
|
"text/x-python": ".py",
|
||||||
|
"application/x-pn-realaudio": ".ram",
|
||||||
|
"application/x-sgml": ".sgm",
|
||||||
|
"model/stl": ".stl",
|
||||||
|
"image/pict": ".pct",
|
||||||
|
"text/xul": ".xul",
|
||||||
|
"text/x-go": "go"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"startup": {
|
"startup": {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.asset-document {
|
.asset-document {
|
||||||
width: 100%;
|
max-width: 1000px;
|
||||||
padding: $thin-padding;
|
padding: $thin-padding;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -16,15 +16,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.asset-title {
|
.asset-title {
|
||||||
|
max-width: 1000px;
|
||||||
padding-bottom: $thin-padding;
|
padding-bottom: $thin-padding;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.asset-image, .asset-video {
|
.asset-image, .asset-video {
|
||||||
max-height: 95vh;
|
max-height: 75vh;
|
||||||
max-width: 95vw;
|
max-width: 85vw;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
object-position: center;
|
object-position: center;
|
||||||
|
background: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*below must die if this is intended to be shareable component! it also probably doesn't need to be*/
|
/*below must die if this is intended to be shareable component! it also probably doesn't need to be*/
|
||||||
|
@ -111,12 +113,15 @@
|
||||||
margin: $primary-padding;
|
margin: $primary-padding;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@media (min-width: $break-point-tablet) {
|
||||||
|
padding: $primary-padding;
|
||||||
|
}
|
||||||
@media (max-width: $break-point-tablet) {
|
@media (max-width: $break-point-tablet) {
|
||||||
margin: $primary-padding $secondary-padding;
|
padding: $tertiary-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $break-point-mobile) {
|
@media (max-width: $break-point-mobile) {
|
||||||
margin: $primary-padding 0;
|
margin: $tertiary-padding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,4 +130,5 @@
|
||||||
padding-top: $primary-padding;
|
padding-top: $primary-padding;
|
||||||
margin-top: $primary-padding;
|
margin-top: $primary-padding;
|
||||||
color: $grey;
|
color: $grey;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,26 @@
|
||||||
.markdown-preview {
|
.markdown-preview {
|
||||||
// Headers
|
|
||||||
|
margin: $tertiary-padding 0px;
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3,
|
h3 {
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
margin: $tertiary-padding 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h5, h6 {
|
||||||
|
font-size: $text-large;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: $tertiary-padding;
|
margin: $tertiary-padding 0px;
|
||||||
padding-top: $tertiary-padding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paragraphs
|
// Paragraphs
|
||||||
p {
|
p {
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
margin-bottom: $tertiary-padding;
|
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
|
margin: $tertiary-padding 0px;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
|
@ -29,10 +33,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
border-radius: 8px;
|
|
||||||
background: $blockquote-background;
|
background: $blockquote-background;
|
||||||
padding: $tertiary-padding;
|
padding: $tertiary-padding;
|
||||||
min-width: 60%;
|
min-width: 60%;
|
||||||
|
margin: $tertiary-padding;
|
||||||
|
p:first-child{
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strikethrough text
|
// Strikethrough text
|
||||||
|
@ -42,10 +56,10 @@
|
||||||
// Tables
|
// Tables
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 1.2rem;
|
|
||||||
background-color: $base-color;
|
background-color: $base-color;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
border: .5px solid $chrome-color;
|
border: .5px solid $chrome-color;
|
||||||
|
margin: $tertiary-padding 0px;
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
td,
|
td,
|
||||||
|
@ -77,9 +91,20 @@
|
||||||
margin-top: $tertiary-padding;
|
margin-top: $tertiary-padding;
|
||||||
padding: $secondary-padding;
|
padding: $secondary-padding;
|
||||||
object-fit: scale-down;
|
object-fit: scale-down;
|
||||||
max-width: 100%;
|
|
||||||
border: $subtle-border;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 90vw;
|
||||||
|
margin-top: $tertiary-padding 0px;
|
||||||
|
margin-bottom: $tertiary-padding 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Horizontal Rule
|
// Horizontal Rule
|
||||||
|
@ -114,7 +139,7 @@
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
display: inline-block;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lists
|
// Lists
|
||||||
|
|
|
@ -5,8 +5,9 @@ class PublishPreview extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
imgSource : '',
|
imgSource : '',
|
||||||
defaultThumbnail: '/assets/img/video_thumb_default.png',
|
defaultVideoThumbnail: '/assets/img/video_thumb_default.png',
|
||||||
|
defaultThumbnail : '/assets/img/Speech_Logo_Main@OG-02.jpg',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
@ -37,12 +38,13 @@ class PublishPreview extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
setPreviewImageSource (file) {
|
setPreviewImageSource (file) {
|
||||||
if (file.type !== 'video/mp4') {
|
if (this.props.thumbnail) {
|
||||||
|
this.setPreviewImageSourceFromFile(this.props.thumbnail);
|
||||||
|
} else if (file.type.substr(0, file.type.indexOf('/')) === 'image'){
|
||||||
this.setPreviewImageSourceFromFile(file);
|
this.setPreviewImageSourceFromFile(file);
|
||||||
|
} else if (file.type === 'video'){
|
||||||
|
this.setState({imgSource: this.state.defaultVideoThumbnail});
|
||||||
} else {
|
} else {
|
||||||
if (this.props.thumbnail) {
|
|
||||||
this.setPreviewImageSourceFromFile(this.props.thumbnail);
|
|
||||||
}
|
|
||||||
this.setState({imgSource: this.state.defaultThumbnail});
|
this.setState({imgSource: this.state.defaultThumbnail});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
client/src/components/ErrorBoundary/index.jsx
Normal file
24
client/src/components/ErrorBoundary/index.jsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import React from 'react';
|
||||||
|
class ErrorBoundary extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error, info) {
|
||||||
|
// Display fallback UI
|
||||||
|
this.setState({ hasError: true });
|
||||||
|
// You can also log the error to an error reporting service
|
||||||
|
console.log('Error occurred while rendering markdown')
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
// You can render any custom fallback UI
|
||||||
|
return (<p>A component was prevented from crashing the App.</p>);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundary;
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown/with-html';
|
||||||
// TODO: get markdown settings from siteConfig
|
import { serving } from '@config/siteConfig.json';
|
||||||
|
import ErrorBoundary from '@components/ErrorBoundary';
|
||||||
|
const { markdownSettings: { escapeHtmlMain, skipHtmlMain, allowedTypesMain } } = serving;
|
||||||
class FileViewer extends React.Component {
|
class FileViewer extends React.Component {
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
|
@ -39,7 +40,9 @@ class FileViewer extends React.Component {
|
||||||
<div className={'markdown'}>
|
<div className={'markdown'}>
|
||||||
{
|
{
|
||||||
this.state.fileLoaded &&
|
this.state.fileLoaded &&
|
||||||
<ReactMarkdown className={'markdown-preview'} source={this.state.fileText} skipHtml />
|
<ErrorBoundary>
|
||||||
|
<ReactMarkdown className={'markdown-preview'} source={this.state.fileText} skipHtml={skipHtmlMain} allowedTypes={allowedTypesMain} escapeHtml={escapeHtmlMain} />
|
||||||
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!this.state.fileLoaded &&
|
!this.state.fileLoaded &&
|
||||||
|
|
|
@ -14,7 +14,7 @@ const PublishNsfwInput = ({ nsfw, handleInput }) => {
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
id='publish-nsfw'
|
id='publish-nsfw'
|
||||||
name='nsfw'
|
name='nsfw'
|
||||||
value={nsfw}
|
checked={nsfw}
|
||||||
onChange={handleInput}
|
onChange={handleInput}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,8 @@ import { createPermanentURI } from '@clientutils/createPermanentURI';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
|
||||||
const { details: { host } } = siteConfig;
|
const { details: { host } } = siteConfig;
|
||||||
|
const { serving } = siteConfig;
|
||||||
|
const { markdownSettings: { escapeHtmlDescriptions, skipHtmlDescriptions, allowedTypesDescriptions } } = serving;
|
||||||
class AssetInfo extends React.Component {
|
class AssetInfo extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const { editable, asset } = this.props;
|
const { editable, asset } = this.props;
|
||||||
|
@ -38,7 +39,17 @@ class AssetInfo extends React.Component {
|
||||||
{ description && (
|
{ description && (
|
||||||
<RowLabeled
|
<RowLabeled
|
||||||
label={<Label value={'Description'} />}
|
label={<Label value={'Description'} />}
|
||||||
content={<div className='asset-info__description'><ReactMarkdown className={'markdown-preview'} skipHtml disallowedTypes={['image']} source={description} /></div>}
|
content={
|
||||||
|
<div className='asset-info__description'>
|
||||||
|
<ReactMarkdown
|
||||||
|
className={'markdown-preview'}
|
||||||
|
escapeHtml={escapeHtmlDescriptions}
|
||||||
|
skipHtml={skipHtmlDescriptions}
|
||||||
|
allowedTypes={allowedTypesDescriptions}
|
||||||
|
source={description}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{editable && (
|
{editable && (
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { validateFile } from '../../utils/file';
|
|
||||||
import Memeify from '@components/Memeify';
|
import Memeify from '@components/Memeify';
|
||||||
import DropzonePreviewImage from '@components/DropzonePreviewImage';
|
import DropzonePreviewImage from '@components/DropzonePreviewImage';
|
||||||
import DropzoneDropItDisplay from '@components/DropzoneDropItDisplay';
|
import DropzoneDropItDisplay from '@components/DropzoneDropItDisplay';
|
||||||
import DropzoneInstructionsDisplay from '@components/DropzoneInstructionsDisplay';
|
import DropzoneInstructionsDisplay from '@components/DropzoneInstructionsDisplay';
|
||||||
|
import validateFileForPublish from '@globalutils/validateFileForPublish';
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faEdit } from '@fortawesome/free-solid-svg-icons';
|
import { faEdit } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
const isFacebook = (() => {
|
const isFacebook = (() => {
|
||||||
|
@ -29,7 +29,7 @@ class Dropzone extends React.Component {
|
||||||
memeify : false,
|
memeify : false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(props.file) {
|
if (props.file) {
|
||||||
// No side effects allowed with `getDerivedStateFromProps`, so
|
// No side effects allowed with `getDerivedStateFromProps`, so
|
||||||
// we must use `componentDidUpdate` and `constructor` routines.
|
// we must use `componentDidUpdate` and `constructor` routines.
|
||||||
// Note: `FileReader` has an `onloadend` side-effect
|
// Note: `FileReader` has an `onloadend` side-effect
|
||||||
|
@ -133,7 +133,7 @@ class Dropzone extends React.Component {
|
||||||
chooseFile (file) {
|
chooseFile (file) {
|
||||||
if (file) {
|
if (file) {
|
||||||
try {
|
try {
|
||||||
validateFile(file); // validate the file's name, type, and size
|
validateFileForPublish(file); // validate the file's name, type, and size
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return this.props.setFileError(error.message);
|
return this.props.setFileError(error.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,6 @@ const createAssetMetaTags = asset => {
|
||||||
const ogThumbnailContentType = determineContentTypeFromExtension(claimData.thumbnail);
|
const ogThumbnailContentType = determineContentTypeFromExtension(claimData.thumbnail);
|
||||||
const ogThumbnail = claimData.thumbnail || defaultThumbnail;
|
const ogThumbnail = claimData.thumbnail || defaultThumbnail;
|
||||||
|
|
||||||
console.log('asset.claimData', asset.claimData);
|
|
||||||
|
|
||||||
// {property: 'og:title'] = ogTitle},
|
// {property: 'og:title'] = ogTitle},
|
||||||
const metaTags = {
|
const metaTags = {
|
||||||
'og:title': ogTitle,
|
'og:title': ogTitle,
|
||||||
|
|
|
@ -31,7 +31,11 @@ function Logo () {
|
||||||
export default Logo;
|
export default Logo;
|
||||||
```
|
```
|
||||||
|
|
||||||
Restart the server, and you should see your site with a new Logo in the top left corner!
|
Rebuild and restart the server, and you should see your site with a new Logo in the top left corner!
|
||||||
|
```
|
||||||
|
$ npm run build
|
||||||
|
```
|
||||||
|
Then
|
||||||
```
|
```
|
||||||
$ npm run start
|
$ npm run start
|
||||||
```
|
```
|
||||||
|
|
96
docs/settings.md
Normal file
96
docs/settings.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
Settings found in cli/defaults/siteConfig.json will be copied to /site/config/siteConfig.json by running npm run configure
|
||||||
|
|
||||||
|
You are encouraged to dig into those settings to make your installation behave how you wish. Below is a description of settings available.
|
||||||
|
|
||||||
|
ANALYTICS:
|
||||||
|
|
||||||
|
"googleId": null
|
||||||
|
|
||||||
|
ASSET DEFAULTS: _These are some default values for publishes_
|
||||||
|
|
||||||
|
"title": "Default Content Title",
|
||||||
|
"description": "Default Content Description",
|
||||||
|
"thumbnail": "https://spee.ch/0e5d4e8f4086e13f5b9ca3f9648f518e5f524402/speechflag.png"
|
||||||
|
|
||||||
|
DETAILS:
|
||||||
|
|
||||||
|
"port": 3000, - this is the internal server port for the application_
|
||||||
|
"title": "My Site",
|
||||||
|
"ipAddress": "",
|
||||||
|
"host": "https://www.example.com", - must contain "http(s)://" and if localhost, "http://localhost:3000"
|
||||||
|
"description": "A decentralized hosting platform built on LBRY",
|
||||||
|
"twitter": false,
|
||||||
|
"blockListEndpoint": - the LBRY default endpoint is generally for the US. Empty string "" negates.
|
||||||
|
|
||||||
|
PUBLISHING:
|
||||||
|
|
||||||
|
"primaryClaimAddress": null, - generally supplied by your lbrynet sdk
|
||||||
|
"uploadDirectory": "/home/lbry/Uploads", - lbrynet sdk will know your uploads are here
|
||||||
|
"lbrynetHome": "/home/lbry",
|
||||||
|
"thumbnailChannel": null, - when publishing non-image content, thumbnails will go here.
|
||||||
|
"thumbnailChannelId": null,
|
||||||
|
"additionalClaimAddresses": [],
|
||||||
|
"disabled": false,
|
||||||
|
"disabledMessage": "Default publishing disabled message",
|
||||||
|
"closedRegistration": false, - true: prevent new channels from being registered
|
||||||
|
"serveOnlyApproved": false, - true: prevent your site from serving up unapproved channels
|
||||||
|
"publishOnlyApproved": false, - true: restrict
|
||||||
|
"approvedChannels": [], - If either of the above two are true, ['@MyKittens', '@BobsKittens']
|
||||||
|
"publishingChannelWhitelist": [],
|
||||||
|
"channelClaimBidAmount": "0.1", - When creating a channel, how much you deposit to control the name
|
||||||
|
"fileClaimBidAmount": "0.01", - When publishing content, how much you deposit to control the name
|
||||||
|
"maxSizeImage": 10000000, - You may not want people uploading 50GB files. 1000000 = 1MB
|
||||||
|
"maxSizeGif": 50000000,
|
||||||
|
"maxSizeVideo": 50000000
|
||||||
|
|
||||||
|
SERVING:
|
||||||
|
|
||||||
|
"markdownSettings": {
|
||||||
|
"skipHtmlMain": true, - false: render html, in a somewhat unpredictable way~
|
||||||
|
"escapeHtmlMain": true, - true: rather than render html, escape it and print it visibly
|
||||||
|
"skipHtmlDescriptions": true, - as above, for descriptions
|
||||||
|
"escapeHtmlDescriptions": true, - as above, for descriptions
|
||||||
|
"allowedTypesMain": [], - markdown rendered as main content
|
||||||
|
"allowedTypesDescriptions": [], - markdown rendered in description in content details
|
||||||
|
"allowedTypesExample": [ - here are examples of allowed types
|
||||||
|
"see react-markdown docs", `https://github.com/rexxars/react-markdown`
|
||||||
|
"root",
|
||||||
|
"text",
|
||||||
|
"break",
|
||||||
|
"paragraph",
|
||||||
|
"emphasis",
|
||||||
|
"strong",
|
||||||
|
"thematicBreak",
|
||||||
|
"blockquote",
|
||||||
|
"delete",
|
||||||
|
"link",
|
||||||
|
"image", - you may not have a lot of control over how these are rendered
|
||||||
|
"linkReference",
|
||||||
|
"imageReference",
|
||||||
|
"table",
|
||||||
|
"tableHead",
|
||||||
|
"tableBody",
|
||||||
|
"tableRow",
|
||||||
|
"tableCell",
|
||||||
|
"list",
|
||||||
|
"listItem",
|
||||||
|
"heading",
|
||||||
|
"inlineCode",
|
||||||
|
"code",
|
||||||
|
"html", - potentially DANGEROUS, intended for `serveOnlyApproved = true` environments, includes iframes, divs.
|
||||||
|
"parsedHtml"
|
||||||
|
],
|
||||||
|
"disallowedTypesMain": [], - not implemented
|
||||||
|
"disallowedTypesDescriptions": ["image", "html"], - not implemented
|
||||||
|
"disallowedTypesExample": ["image", "html"] - not implemented
|
||||||
|
},
|
||||||
|
"customFileExtensions": { - suggest a file extension for experimental content types you may be publishing
|
||||||
|
"application/example-type": "example"
|
||||||
|
}
|
||||||
|
|
||||||
|
STARTUP:
|
||||||
|
|
||||||
|
"performChecks": true,
|
||||||
|
"performUpdates": true
|
||||||
|
|
||||||
|
}
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -8091,9 +8091,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.10",
|
"version": "4.17.11",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||||
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
|
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||||
},
|
},
|
||||||
"lodash.assign": {
|
"lodash.assign": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
"image-size": "^0.6.3",
|
"image-size": "^0.6.3",
|
||||||
"inquirer": "^5.2.0",
|
"inquirer": "^5.2.0",
|
||||||
"ip": "^1.1.5",
|
"ip": "^1.1.5",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
"make-dir": "^1.3.0",
|
"make-dir": "^1.3.0",
|
||||||
"mime-types": "^2.1.21",
|
"mime-types": "^2.1.21",
|
||||||
"module-alias": "^2.1.0",
|
"module-alias": "^2.1.0",
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const mime = require('mime-types');
|
const mime = require('mime-types');
|
||||||
|
const {
|
||||||
|
serving: { customFileExtensions },
|
||||||
|
} = require('@config/siteConfig');
|
||||||
|
|
||||||
const getterMethods = {
|
const getterMethods = {
|
||||||
generated_extension() {
|
generated_extension() {
|
||||||
return mime.extension(this.content_type) ? mime.extension(this.content_type) : 'jpg';
|
logger.info('trying to generate extension', this.content_type);
|
||||||
|
if (customFileExtensions.hasOwnProperty(this.content_type)) {
|
||||||
|
return customFileExtensions[this.content_type];
|
||||||
|
} else {
|
||||||
|
return mime.extension(this.content_type) ? mime.extension(this.content_type) : 'jpg';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const validateFileTypeAndSize = require('./validateFileTypeAndSize.js');
|
const validateFileTypeAndSize = require('./validateFileTypeAndSize.js');
|
||||||
|
const validateFileForPublish = require('./validateFileForPublish.js');
|
||||||
|
|
||||||
const parsePublishApiRequestFiles = ({ file, thumbnail }, isUpdate) => {
|
const parsePublishApiRequestFiles = ({ file, thumbnail }, isUpdate) => {
|
||||||
// make sure a file was provided
|
// make sure a file was provided
|
||||||
|
@ -40,7 +41,7 @@ const parsePublishApiRequestFiles = ({ file, thumbnail }, isUpdate) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the file
|
// validate the file
|
||||||
if (file) validateFileTypeAndSize(file);
|
if (file) validateFileForPublish(file);
|
||||||
// return results
|
// return results
|
||||||
const obj = {
|
const obj = {
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
const logger = require('winston');
|
||||||
|
|
||||||
|
const { publishing } = require('@config/siteConfig.json');
|
||||||
|
|
||||||
|
const { fileSizeLimits } = publishing;
|
||||||
|
|
||||||
|
const SIZE_MB = 1000000;
|
||||||
|
|
||||||
|
const validateFileForPublish = file => {
|
||||||
|
let contentType = file.type;
|
||||||
|
let mediaType = contentType ? contentType.substr(0, contentType.indexOf('/')) : '';
|
||||||
|
let mediaTypeLimit = fileSizeLimits[mediaType] || false;
|
||||||
|
let customLimits = fileSizeLimits['customByContentType'];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
throw new Error('no file provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/'/.test(file.name)) {
|
||||||
|
throw new Error('apostrophes are not allowed in the file name');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(customLimits).includes(contentType)) {
|
||||||
|
if (file.size > customLimits[contentType]) {
|
||||||
|
throw new Error(
|
||||||
|
`Sorry, type ${contentType} is limited to ${customLimits[contentType] / SIZE_MB} MB.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mediaTypeLimit) {
|
||||||
|
if (file.size > mediaTypeLimit) {
|
||||||
|
throw new Error(`Sorry, type ${mediaType} is limited to ${mediaTypeLimit / SIZE_MB} MB.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = validateFileForPublish;
|
110
server/index.js
110
server/index.js
|
@ -13,7 +13,7 @@ const httpContext = require('express-http-context');
|
||||||
const db = require('./models');
|
const db = require('./models');
|
||||||
const requestLogger = require('./middleware/requestLogger');
|
const requestLogger = require('./middleware/requestLogger');
|
||||||
const createDatabaseIfNotExists = require('./models/utils/createDatabaseIfNotExists');
|
const createDatabaseIfNotExists = require('./models/utils/createDatabaseIfNotExists');
|
||||||
const { getWalletBalance } = require('./lbrynet/index');
|
const { getAccountBalance } = require('./lbrynet/index');
|
||||||
const configureLogging = require('./utils/configureLogging');
|
const configureLogging = require('./utils/configureLogging');
|
||||||
const configureSlack = require('./utils/configureSlack');
|
const configureSlack = require('./utils/configureSlack');
|
||||||
const { setupBlockList } = require('./utils/blockList');
|
const { setupBlockList } = require('./utils/blockList');
|
||||||
|
@ -27,10 +27,7 @@ const {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
details: { port: PORT, blockListEndpoint },
|
details: { port: PORT, blockListEndpoint },
|
||||||
startup: {
|
startup: { performChecks, performUpdates },
|
||||||
performChecks,
|
|
||||||
performUpdates,
|
|
||||||
},
|
|
||||||
} = require('@config/siteConfig');
|
} = require('@config/siteConfig');
|
||||||
|
|
||||||
const { sessionKey } = require('@private/authConfig.json');
|
const { sessionKey } = require('@private/authConfig.json');
|
||||||
|
@ -38,7 +35,7 @@ const { sessionKey } = require('@private/authConfig.json');
|
||||||
// configure.js doesn't handle new keys in config.json files yet. Make sure it doens't break.
|
// configure.js doesn't handle new keys in config.json files yet. Make sure it doens't break.
|
||||||
let finalBlockListEndpoint;
|
let finalBlockListEndpoint;
|
||||||
|
|
||||||
function Server () {
|
function Server() {
|
||||||
this.initialize = () => {
|
this.initialize = () => {
|
||||||
// configure logging
|
// configure logging
|
||||||
configureLogging();
|
configureLogging();
|
||||||
|
@ -53,12 +50,16 @@ function Server () {
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const webpackDevMiddleware = require('webpack-dev-middleware');
|
const webpackDevMiddleware = require('webpack-dev-middleware');
|
||||||
|
|
||||||
const webpackClientConfig = require('../webpack/webpack.client.config')(null, { mode: 'development' });
|
const webpackClientConfig = require('../webpack/webpack.client.config')(null, {
|
||||||
|
mode: 'development',
|
||||||
|
});
|
||||||
const clientCompiler = webpack(webpackClientConfig);
|
const clientCompiler = webpack(webpackClientConfig);
|
||||||
|
|
||||||
app.use(webpackDevMiddleware(clientCompiler, {
|
app.use(
|
||||||
publicPath: webpackClientConfig.output.publicPath,
|
webpackDevMiddleware(clientCompiler, {
|
||||||
}));
|
publicPath: webpackClientConfig.output.publicPath,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
app.use(require('webpack-hot-middleware')(clientCompiler));
|
app.use(require('webpack-hot-middleware')(clientCompiler));
|
||||||
}
|
}
|
||||||
|
@ -67,8 +68,15 @@ function Server () {
|
||||||
app.enable('trust proxy');
|
app.enable('trust proxy');
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
if (req.get('User-Agent') === 'Mozilla/5.0 (Windows NT 5.1; rv:14.0) Gecko/20120405 Firefox/14.0a1') {
|
if (
|
||||||
res.status(403).send('<h1>Forbidden</h1>If you are seeing this by mistake, please contact us using <a href="https://chat.lbry.io/">https://chat.lbry.io/</a>');
|
req.get('User-Agent') ===
|
||||||
|
'Mozilla/5.0 (Windows NT 5.1; rv:14.0) Gecko/20120405 Firefox/14.0a1'
|
||||||
|
) {
|
||||||
|
res
|
||||||
|
.status(403)
|
||||||
|
.send(
|
||||||
|
'<h1>Forbidden</h1>If you are seeing this by mistake, please contact us using <a href="https://chat.lbry.io/">https://chat.lbry.io/</a>'
|
||||||
|
);
|
||||||
res.end();
|
res.end();
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
|
@ -101,38 +109,45 @@ function Server () {
|
||||||
app.use(requestLogger);
|
app.use(requestLogger);
|
||||||
|
|
||||||
// initialize passport
|
// initialize passport
|
||||||
app.use(cookieSession({
|
app.use(
|
||||||
name: 'session',
|
cookieSession({
|
||||||
keys: [sessionKey],
|
name: 'session',
|
||||||
}));
|
keys: [sessionKey],
|
||||||
|
})
|
||||||
|
);
|
||||||
app.use(speechPassport.initialize());
|
app.use(speechPassport.initialize());
|
||||||
app.use(speechPassport.session());
|
app.use(speechPassport.session());
|
||||||
|
|
||||||
// configure handlebars & register it with express app
|
// configure handlebars & register it with express app
|
||||||
const viewsPath = Path.resolve(process.cwd(), 'server/views');
|
const viewsPath = Path.resolve(process.cwd(), 'server/views');
|
||||||
app.engine('handlebars', expressHandlebars({
|
app.engine(
|
||||||
async : false,
|
'handlebars',
|
||||||
dataType : 'text',
|
expressHandlebars({
|
||||||
defaultLayout: 'embed',
|
async: false,
|
||||||
partialsDir : Path.join(viewsPath, '/partials'),
|
dataType: 'text',
|
||||||
layoutsDir : Path.join(viewsPath, '/layouts'),
|
defaultLayout: 'embed',
|
||||||
}));
|
partialsDir: Path.join(viewsPath, '/partials'),
|
||||||
|
layoutsDir: Path.join(viewsPath, '/layouts'),
|
||||||
|
})
|
||||||
|
);
|
||||||
app.set('views', viewsPath);
|
app.set('views', viewsPath);
|
||||||
app.set('view engine', 'handlebars');
|
app.set('view engine', 'handlebars');
|
||||||
|
|
||||||
// set the routes on the app
|
// set the routes on the app
|
||||||
const routes = require('./routes');
|
const routes = require('./routes');
|
||||||
|
|
||||||
Object.keys(routes).map((routePath) => {
|
Object.keys(routes).map(routePath => {
|
||||||
let routeData = routes[routePath];
|
let routeData = routes[routePath];
|
||||||
let routeMethod = routeData.hasOwnProperty('method') ? routeData.method : 'get';
|
let routeMethod = routeData.hasOwnProperty('method') ? routeData.method : 'get';
|
||||||
let controllers = Array.isArray(routeData.controller) ? routeData.controller : [routeData.controller];
|
let controllers = Array.isArray(routeData.controller)
|
||||||
|
? routeData.controller
|
||||||
|
: [routeData.controller];
|
||||||
|
|
||||||
app[routeMethod](
|
app[routeMethod](
|
||||||
routePath,
|
routePath,
|
||||||
logMetricsMiddleware,
|
logMetricsMiddleware,
|
||||||
setRouteDataInContextMiddleware(routePath, routeData),
|
setRouteDataInContextMiddleware(routePath, routeData),
|
||||||
...controllers,
|
...controllers
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -153,22 +168,18 @@ function Server () {
|
||||||
};
|
};
|
||||||
this.syncDatabase = () => {
|
this.syncDatabase = () => {
|
||||||
logger.info(`Syncing database...`);
|
logger.info(`Syncing database...`);
|
||||||
return createDatabaseIfNotExists()
|
return createDatabaseIfNotExists().then(() => {
|
||||||
.then(() => {
|
db.sequelize.sync();
|
||||||
db.sequelize.sync();
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
this.performChecks = () => {
|
this.performChecks = () => {
|
||||||
if (!performChecks) {
|
if (!performChecks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.info(`Performing checks...`);
|
logger.info(`Performing checks...`);
|
||||||
return Promise.all([
|
return Promise.all([getAccountBalance()]).then(([accountBalance]) => {
|
||||||
getWalletBalance(),
|
logger.info('Starting LBC balance:', accountBalance);
|
||||||
])
|
});
|
||||||
.then(([walletBalance]) => {
|
|
||||||
logger.info('Starting LBC balance:', walletBalance);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.performUpdates = () => {
|
this.performUpdates = () => {
|
||||||
|
@ -178,27 +189,29 @@ function Server () {
|
||||||
if (blockListEndpoint) {
|
if (blockListEndpoint) {
|
||||||
finalBlockListEndpoint = blockListEndpoint;
|
finalBlockListEndpoint = blockListEndpoint;
|
||||||
} else if (!blockListEndpoint) {
|
} else if (!blockListEndpoint) {
|
||||||
if (typeof (blockListEndpoint) !== 'string') {
|
if (typeof blockListEndpoint !== 'string') {
|
||||||
logger.warn('blockListEndpoint is null due to outdated siteConfig file. \n' +
|
logger.warn(
|
||||||
'Continuing with default LBRY blocklist api endpoint. \n ' +
|
'blockListEndpoint is null due to outdated siteConfig file. \n' +
|
||||||
'(Specify /"blockListEndpoint" : ""/ to disable.');
|
'Continuing with default LBRY blocklist api endpoint. \n ' +
|
||||||
|
'(Specify /"blockListEndpoint" : ""/ to disable.'
|
||||||
|
);
|
||||||
finalBlockListEndpoint = 'https://api.lbry.io/file/list_blocked';
|
finalBlockListEndpoint = 'https://api.lbry.io/file/list_blocked';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.info(`Peforming updates...`);
|
logger.info(`Peforming updates...`);
|
||||||
if (!finalBlockListEndpoint) {
|
if (!finalBlockListEndpoint) {
|
||||||
logger.info('Configured for no Block List');
|
logger.info('Configured for no Block List');
|
||||||
db.Tor.refreshTable().then((updatedTorList) => {
|
db.Tor.refreshTable().then(updatedTorList => {
|
||||||
logger.info('Tor list updated, length:', updatedTorList.length);
|
logger.info('Tor list updated, length:', updatedTorList.length);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
db.Blocked.refreshTable(finalBlockListEndpoint),
|
db.Blocked.refreshTable(finalBlockListEndpoint),
|
||||||
db.Tor.refreshTable()])
|
db.Tor.refreshTable(),
|
||||||
.then(([updatedBlockedList, updatedTorList]) => {
|
]).then(([updatedBlockedList, updatedTorList]) => {
|
||||||
logger.info('Blocked list updated, length:', updatedBlockedList.length);
|
logger.info('Blocked list updated, length:', updatedBlockedList.length);
|
||||||
logger.info('Tor list updated, length:', updatedTorList.length);
|
logger.info('Tor list updated, length:', updatedTorList.length);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.start = () => {
|
this.start = () => {
|
||||||
|
@ -210,10 +223,7 @@ function Server () {
|
||||||
return this.startServerListening();
|
return this.startServerListening();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Promise.all([
|
return Promise.all([this.performChecks(), this.performUpdates()]);
|
||||||
this.performChecks(),
|
|
||||||
this.performUpdates(),
|
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return setupBlockList();
|
return setupBlockList();
|
||||||
|
|
|
@ -8,7 +8,7 @@ const handleLbrynetResponse = require('./utils/handleLbrynetResponse.js');
|
||||||
const { publishing } = require('@config/siteConfig');
|
const { publishing } = require('@config/siteConfig');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publishClaim (publishParams) {
|
publishClaim(publishParams) {
|
||||||
logger.debug(`lbryApi >> Publishing claim to "${publishParams.name}"`);
|
logger.debug(`lbryApi >> Publishing claim to "${publishParams.name}"`);
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -18,7 +18,13 @@ module.exports = {
|
||||||
params: publishParams,
|
params: publishParams,
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
sendGATimingEvent('lbrynet', 'publish', chooseGaLbrynetPublishLabel(publishParams), gaStartTime, Date.now());
|
sendGATimingEvent(
|
||||||
|
'lbrynet',
|
||||||
|
'publish',
|
||||||
|
chooseGaLbrynetPublishLabel(publishParams),
|
||||||
|
gaStartTime,
|
||||||
|
Date.now()
|
||||||
|
);
|
||||||
handleLbrynetResponse(response, resolve, reject);
|
handleLbrynetResponse(response, resolve, reject);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
@ -26,7 +32,7 @@ module.exports = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getClaim (uri) {
|
getClaim(uri) {
|
||||||
logger.debug(`lbryApi >> Getting Claim for "${uri}"`);
|
logger.debug(`lbryApi >> Getting Claim for "${uri}"`);
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -47,7 +53,7 @@ module.exports = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async abandonClaim ({claimId}) {
|
async abandonClaim({ claimId }) {
|
||||||
logger.debug(`lbryApi >> Abandon claim "${claimId}"`);
|
logger.debug(`lbryApi >> Abandon claim "${claimId}"`);
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
try {
|
try {
|
||||||
|
@ -62,7 +68,7 @@ module.exports = {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getClaimList (claimName) {
|
getClaimList(claimName) {
|
||||||
logger.debug(`lbryApi >> Getting claim_list for "${claimName}"`);
|
logger.debug(`lbryApi >> Getting claim_list for "${claimName}"`);
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -80,7 +86,7 @@ module.exports = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
resolveUri (uri) {
|
resolveUri(uri) {
|
||||||
logger.debug(`lbryApi >> Resolving URI for "${uri}"`);
|
logger.debug(`lbryApi >> Resolving URI for "${uri}"`);
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -97,9 +103,11 @@ module.exports = {
|
||||||
db.Claim.findOne({ where: { claimId: uri.split('#')[1] } })
|
db.Claim.findOne({ where: { claimId: uri.split('#')[1] } })
|
||||||
.then(() => reject('This claim has not yet been confirmed on the LBRY blockchain'))
|
.then(() => reject('This claim has not yet been confirmed on the LBRY blockchain'))
|
||||||
.catch(() => reject(`Claim ${uri} does not exist`));
|
.catch(() => reject(`Claim ${uri} does not exist`));
|
||||||
} else if (data.result[uri].error) { // check for errors
|
} else if (data.result[uri].error) {
|
||||||
|
// check for errors
|
||||||
reject(data.result[uri].error);
|
reject(data.result[uri].error);
|
||||||
} else { // if no errors, resolve
|
} else {
|
||||||
|
// if no errors, resolve
|
||||||
resolve(data.result[uri]);
|
resolve(data.result[uri]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -108,7 +116,7 @@ module.exports = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getDownloadDirectory () {
|
getDownloadDirectory() {
|
||||||
logger.debug('lbryApi >> Retrieving the download directory path from lbry daemon...');
|
logger.debug('lbryApi >> Retrieving the download directory path from lbry daemon...');
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -117,11 +125,19 @@ module.exports = {
|
||||||
method: 'settings_get',
|
method: 'settings_get',
|
||||||
})
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
sendGATimingEvent('lbrynet', 'getDownloadDirectory', 'SETTINGS_GET', gaStartTime, Date.now());
|
sendGATimingEvent(
|
||||||
|
'lbrynet',
|
||||||
|
'getDownloadDirectory',
|
||||||
|
'SETTINGS_GET',
|
||||||
|
gaStartTime,
|
||||||
|
Date.now()
|
||||||
|
);
|
||||||
if (data.result) {
|
if (data.result) {
|
||||||
resolve(data.result.download_directory);
|
resolve(data.result.download_directory);
|
||||||
} else {
|
} else {
|
||||||
return new Error('Successfully connected to lbry daemon, but unable to retrieve the download directory.');
|
return new Error(
|
||||||
|
'Successfully connected to lbry daemon, but unable to retrieve the download directory.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
@ -130,7 +146,7 @@ module.exports = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
createChannel (name) {
|
createChannel(name) {
|
||||||
logger.debug(`lbryApi >> Creating channel for ${name}...`);
|
logger.debug(`lbryApi >> Creating channel for ${name}...`);
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -139,7 +155,7 @@ module.exports = {
|
||||||
method: 'channel_new',
|
method: 'channel_new',
|
||||||
params: {
|
params: {
|
||||||
channel_name: name,
|
channel_name: name,
|
||||||
amount : publishing.channelClaimBidAmount,
|
amount: publishing.channelClaimBidAmount,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
@ -151,15 +167,21 @@ module.exports = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getWalletBalance () {
|
getAccountBalance() {
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios
|
axios
|
||||||
.post(lbrynetUri, {
|
.post(lbrynetUri, {
|
||||||
method: 'wallet_balance',
|
method: 'account_balance',
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
sendGATimingEvent('lbrynet', 'getWalletBalance', 'SETTINGS_GET', gaStartTime, Date.now());
|
sendGATimingEvent(
|
||||||
|
'lbrynet',
|
||||||
|
'getAccountBalance',
|
||||||
|
'SETTINGS_GET',
|
||||||
|
gaStartTime,
|
||||||
|
Date.now()
|
||||||
|
);
|
||||||
handleLbrynetResponse(response, resolve, reject);
|
handleLbrynetResponse(response, resolve, reject);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|
|
@ -53,6 +53,7 @@ module.exports = async (data, chName = null, chShortId = null) => {
|
||||||
claimId: dataVals.claim_id || data.claimId,
|
claimId: dataVals.claim_id || data.claimId,
|
||||||
fileExt: fileExt,
|
fileExt: fileExt,
|
||||||
description: dataVals.description,
|
description: dataVals.description,
|
||||||
|
nsfw: dataVals.is_nsfw,
|
||||||
thumbnail: dataVals.thumbnail_url || data.thumbnail || thumbnail,
|
thumbnail: dataVals.thumbnail_url || data.thumbnail || thumbnail,
|
||||||
outpoint,
|
outpoint,
|
||||||
host,
|
host,
|
||||||
|
|
34
utils/validateFileForPublish.js
Normal file
34
utils/validateFileForPublish.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { publishing } from '@config/siteConfig.json';
|
||||||
|
|
||||||
|
const { fileSizeLimits } = publishing;
|
||||||
|
|
||||||
|
const SIZE_MB = 1000000;
|
||||||
|
|
||||||
|
export default function validateFileForPublish(file) {
|
||||||
|
let contentType = file.type;
|
||||||
|
let mediaType = contentType ? contentType.substr(0, contentType.indexOf('/')) : '';
|
||||||
|
let mediaTypeLimit = fileSizeLimits[mediaType] || false;
|
||||||
|
let customLimits = fileSizeLimits['customByContentType'];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
throw new Error('no file provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/'/.test(file.name)) {
|
||||||
|
throw new Error('apostrophes are not allowed in the file name');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(customLimits).includes(contentType)) {
|
||||||
|
if (file.size > customLimits[contentType]) {
|
||||||
|
throw new Error(
|
||||||
|
`Sorry, type ${contentType} is limited to ${customLimits[contentType] / SIZE_MB} MB.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mediaTypeLimit) {
|
||||||
|
if (file.size > mediaTypeLimit) {
|
||||||
|
throw new Error(`Sorry, type ${mediaType} is limited to ${mediaTypeLimit / SIZE_MB} MB.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue