Staging #1066

Merged
jessopb merged 55 commits from staging into release 2020-05-16 17:08:51 +02:00
23 changed files with 624 additions and 250 deletions
Showing only changes of commit 5547d02a78 - Show all commits

239
README.md
View file

@ -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 - Firewall open ports
* 80 - 22
* 443 - 80
* 3333 - 443
* 4444 - 3333
* [NodeJS](https://nodejs.org) - 4444
* [MySQL version 5.7 or higher](https://dev.mysql.com/doc/refman/8.0/en/installing.html) - [NodeJS](https://nodejs.org)
* mysqlusername or root - [MySQL version 5.7 or higher](https://dev.mysql.com/doc/refman/8.0/en/installing.html)
* mysqlpassword - mysqlusername or root
* Requires mysql_native_password plugin - mysqlpassword
- Requires mysql_native_password plugin
``` ```
mysql> `ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword';` mysql> `ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword';`
``` ```
* [lbrynet](https://github.com/lbryio/lbry) daemon - [lbrynet](https://github.com/lbryio/lbry) daemon
* run this as a service exposing ports 3333 and 4444 - 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) - _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 commands` gets a list of commands
* `./lbrynet account_balance` gets your balance (initially 0.0) - `./lbrynet account_balance` gets your balance (initially 0.0)
* `./lbrynet address_list` gets addresses you can use to recieve LBC - `./lbrynet address_list` gets addresses you can use to recieve LBC
* [FFmpeg](https://www.ffmpeg.org/download.html) - [FFmpeg](https://www.ffmpeg.org/download.html)
* Spee.ch (below) - Spee.ch (below)
* pm2 (optional) process manager such as pm2 to run speech server.js - 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 - 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_ - _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
- your own fork for customization
#### Change directory into your project #### 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. 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 - server
* [MySQL](https://www.mysql.com/) - [MySQL](https://www.mysql.com/)
* [express](https://www.npmjs.com/package/express) - [express](https://www.npmjs.com/package/express)
* [node](https://nodejs.org/) - [node](https://nodejs.org/)
* [lbry](https://github.com/lbryio/lbry) - [lbry](https://github.com/lbryio/lbry)
* [FFmpeg](https://www.ffmpeg.org/) - [FFmpeg](https://www.ffmpeg.org/)
* client - client
* [react](https://reactjs.org/) - [react](https://reactjs.org/)
* redux - redux
* sagas - sagas
* scss - scss
* handlebars - 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). 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: - retrieve the controlling `LBRY` claim:
* https://spee.ch/`claim` - https://spee.ch/`claim`
* https://spee.ch/`claim`.`ext` (serve) - https://spee.ch/`claim`.`ext` (serve)
* retrieve a specific `LBRY` claim: - retrieve a specific `LBRY` claim:
* https://spee.ch/`claim_id`/`claim` - https://spee.ch/`claim_id`/`claim`
* https://spee.ch/`claim_id`/`claim`.`ext` (serve) - https://spee.ch/`claim_id`/`claim`.`ext` (serve)
* retrieve all contents for the controlling `LBRY` channel - retrieve all contents for the controlling `LBRY` channel
* https://spee.ch/`@channel` - https://spee.ch/`@channel`
* a specific `LBRY` channel - a specific `LBRY` channel
* https://spee.ch/`@channel`:`channel_id` - https://spee.ch/`@channel`:`channel_id`
* retrieve a specific claim within the controlling `LBRY` channel - retrieve a specific claim within the controlling `LBRY` channel
* https://spee.ch/`@channel`/`claim` - https://spee.ch/`@channel`/`claim`
* https://spee.ch/`@channel`/`claim`.`ext` (serve) - https://spee.ch/`@channel`/`claim`.`ext` (serve)
* retrieve a specific claim within a specific `LBRY` channel - retrieve a specific claim within a specific `LBRY` channel
* https://spee.ch/`@channel`:`channel_id`/`claim` - https://spee.ch/`@channel`:`channel_id`/`claim`
* https://spee.ch/`@channel`:`channel_id`/`claim`.`ext` (serve) - 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

View file

@ -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;
@ -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;
@ -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 => {

View file

@ -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": {

View file

@ -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;
} }

View file

@ -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

View file

@ -6,7 +6,8 @@ class PublishPreview extends React.Component {
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') {
this.setPreviewImageSourceFromFile(file);
} else {
if (this.props.thumbnail) { if (this.props.thumbnail) {
this.setPreviewImageSourceFromFile(this.props.thumbnail); this.setPreviewImageSourceFromFile(this.props.thumbnail);
} } else if (file.type.substr(0, file.type.indexOf('/')) === 'image'){
this.setPreviewImageSourceFromFile(file);
} else if (file.type === 'video'){
this.setState({imgSource: this.state.defaultVideoThumbnail});
} else {
this.setState({imgSource: this.state.defaultThumbnail}); this.setState({imgSource: this.state.defaultThumbnail});
} }
} }

View 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;

View file

@ -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 &&

View file

@ -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}
/> />
} }

View file

@ -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 && (

View file

@ -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 = (() => {
@ -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);
} }

View file

@ -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,

View file

@ -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
View 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
View file

@ -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",

View file

@ -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",

View file

@ -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() {
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'; return mime.extension(this.content_type) ? mime.extension(this.content_type) : 'jpg';
}
}, },
}; };

View file

@ -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,

View file

@ -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;

View file

@ -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');
@ -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(
webpackDevMiddleware(clientCompiler, {
publicPath: webpackClientConfig.output.publicPath, 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(
cookieSession({
name: 'session', name: 'session',
keys: [sessionKey], 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(
'handlebars',
expressHandlebars({
async: false, async: false,
dataType: 'text', dataType: 'text',
defaultLayout: 'embed', defaultLayout: 'embed',
partialsDir: Path.join(viewsPath, '/partials'), partialsDir: Path.join(viewsPath, '/partials'),
layoutsDir: Path.join(viewsPath, '/layouts'), 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,8 +168,7 @@ 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();
}); });
}; };
@ -163,11 +177,8 @@ function Server () {
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);
}); });
}; };
@ -178,24 +189,26 @@ 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(
'blockListEndpoint is null due to outdated siteConfig file. \n' +
'Continuing with default LBRY blocklist api endpoint. \n ' + 'Continuing with default LBRY blocklist api endpoint. \n ' +
'(Specify /"blockListEndpoint" : ""/ to disable.'); '(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);
}); });
@ -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();

View file

@ -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 => {
@ -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]);
} }
}) })
@ -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 => {
@ -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 => {

View file

@ -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,

View 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;
}