Staging #1066

Merged
jessopb merged 55 commits from staging into release 2020-05-16 17:08:51 +02:00
13 changed files with 249 additions and 81 deletions
Showing only changes of commit 827db9b458 - Show all commits

View file

@ -2,5 +2,6 @@ client/build
node_modules/ node_modules/
public/bundle public/bundle
server/render/build server/render/build
server/bundle
test/ test/
server/chainquery server/chainquery

View file

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017-2018 LBRY Inc. Copyright (c) 2017-2019 LBRY Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,

View file

@ -17,7 +17,8 @@
"ipAddress": "", "ipAddress": "",
"host": "https://www.example.com", "host": "https://www.example.com",
"description": "A decentralized hosting platform built on LBRY", "description": "A decentralized hosting platform built on LBRY",
"twitter": false "twitter": false,
"blockListEndpoint": "https://api.lbry.io/file/list_blocked"
}, },
"publishing": { "publishing": {
"primaryClaimAddress": null, "primaryClaimAddress": null,

View file

@ -2,6 +2,16 @@
position: relative; position: relative;
} }
.asset-preview__blocked {
box-sizing: border-box;
background: black;
color: white;
height: 80%;
padding: 5px;
//remove margin-bottom after mystery 5px on wrapper is gone.
margin-bottom: 5px;
}
.asset-preview__image { .asset-preview__image {
width : 100%; width : 100%;
padding: 0; padding: 0;

View file

@ -2,51 +2,59 @@ import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import createCanonicalLink from '../../../../utils/createCanonicalLink'; import createCanonicalLink from '../../../../utils/createCanonicalLink';
const ClaimPending = () => {
return (
<div className='claim-pending'>PENDING</div>
);
};
const AssetPreview = ({ defaultThumbnail, claimData }) => { const AssetPreview = ({ defaultThumbnail, claimData }) => {
const {name, fileExt, contentType, thumbnail, title, pending} = claimData; const {name, fileExt, contentType, thumbnail, title, blocked} = claimData;
const showUrl = createCanonicalLink({asset: {...claimData}}); const showUrl = createCanonicalLink({asset: {...claimData}});
const embedUrl = `${showUrl}.${fileExt}`; const embedUrl = `${showUrl}.${fileExt}`;
switch (contentType) {
case 'image/jpeg': /*
case 'image/jpg': This blocked section shouldn't be necessary after pagination is reworked,
case 'image/png': though it might be useful for channel_mine situations.
case 'image/gif': */
return (
<Link to={showUrl} className='asset-preview'> if (blocked) {
<div> return (
<div className='asset-preview'>
<div className='asset-preview__blocked'>
<h3>Error 451</h3>
<p>This content is blocked for legal reasons.</p>
</div>
<h3 className='asset-preview__title'>Blocked Content</h3>
</div>
);
} else {
switch (contentType) {
case 'image/jpeg':
case 'image/jpg':
case 'image/png':
case 'image/gif':
return (
<Link to={showUrl} className='asset-preview'>
<img <img
className={'asset-preview__image'} className={'asset-preview__image'}
src={embedUrl} src={embedUrl}
alt={name} alt={name}
/> />
<h3 className='asset-preview__title'>{title}</h3> <h3 className='asset-preview__title'>{title}</h3>
</div> </Link>
</Link> );
); case 'video/mp4':
case 'video/mp4': return (
return ( <Link to={showUrl} className='asset-preview'>
<Link to={showUrl} className='asset-preview'>
<div>
<div className='asset-preview__play-wrapper'> <div className='asset-preview__play-wrapper'>
<img <img
className={'asset-preview__video'} className={'asset-preview__video'}
src={thumbnail || defaultThumbnail} src={thumbnail || defaultThumbnail}
alt={name} alt={name}
/> />
<div className='asset-preview__play-overlay'></div> <div className='asset-preview__play-overlay' />
</div> </div>
<h3 className='asset-preview__title'>{title}</h3> <h3 className='asset-preview__title'>{title}</h3>
</div> </Link>
</Link> );
); default:
default: return null;
return null; }
} }
}; };

View file

@ -0,0 +1,13 @@
import { connect } from 'react-redux';
import View from './view';
import { selectAsset } from '../../selectors/show';
const mapStateToProps = (props) => {
const {show} = props;
const asset = selectAsset(show);
return {
asset,
};
};
export default connect(mapStateToProps, null)(View);

View file

@ -0,0 +1,58 @@
import React from 'react';
import createCanonicalLink from '../../../../utils/createCanonicalLink';
import HorizontalSplit from '@components/HorizontalSplit';
/*
This component shouldn't be necessary after pagination is reworked,
though it might be useful for channel_mine situations.
*/
class BlockedLeft extends React.PureComponent {
render () {
return (
<React.Fragment>
<img className='asset-image' src={'https://upload.wikimedia.org/wikipedia/commons/archive/a/af/20120315000030%21OR_451.svg'} alt={'451 image'} />
</React.Fragment>
);
}
}
class BlockedRight extends React.PureComponent {
render () {
return (
<React.Fragment>
<p>In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.</p>
<p><a href={'https://lbry.io/faq/dmca'} >Click here</a> for more information.</p>
</React.Fragment>
);
}
}
class AssetBlocked extends React.Component {
componentDidMount () {
/*
This function and fetch exists to send the browser the appropriate 451 error.
*/
const { asset } = this.props;
const { claimData: { contentType, outpoint } } = asset;
let fileExt;
if (typeof contentType === 'string') {
fileExt = contentType.split('/')[1] || 'jpg';
}
const sourceUrl = `${createCanonicalLink({ asset: asset.claimData })}.${fileExt}?${outpoint}`;
fetch(sourceUrl)
.catch();
}
render () {
return (
<div>
<HorizontalSplit
collapseOnMobile
leftSide={<BlockedLeft />}
rightSide={<BlockedRight />}
/>
</div>
);
}
};
export default AssetBlocked;

View file

@ -2,6 +2,7 @@ import React from 'react';
import PageLayout from '@components/PageLayout'; import PageLayout from '@components/PageLayout';
import * as Icon from 'react-feather'; import * as Icon from 'react-feather';
import AssetDisplay from '@containers/AssetDisplay'; import AssetDisplay from '@containers/AssetDisplay';
import AssetBlocked from '@containers/AssetBlocked';
import AssetInfo from '@containers/AssetInfo'; import AssetInfo from '@containers/AssetInfo';
import ErrorPage from '@pages/ErrorPage'; import ErrorPage from '@pages/ErrorPage';
import AssetTitle from '@containers/AssetTitle'; import AssetTitle from '@containers/AssetTitle';
@ -29,23 +30,32 @@ class ShowAssetDetails extends React.Component {
render () { render () {
const { asset } = this.props; const { asset } = this.props;
if (asset) { if (asset) {
const { claimData: { name } } = asset; const { claimData: { name, blocked } } = asset;
return ( if (!blocked) {
<PageLayout return (
pageTitle={`${name} - details`} <PageLayout
asset={asset} pageTitle={`${name} - details`}
> asset={asset}
<div className="asset-main"> >
<AssetDisplay /> <div className="asset-main">
<AssetTitle /> <AssetDisplay />
<AssetTitle />
<button className='collapse-button' onClick={this.collapse}> <button className='collapse-button' onClick={this.collapse}>
{this.state.closed ? <Icon.PlusCircle className='plus-icon' /> : <Icon.MinusCircle />} {this.state.closed ? <Icon.PlusCircle className='plus-icon' /> : <Icon.MinusCircle />}
</button> </button>
</div> </div>
{!this.state.closed && <AssetInfo />} {!this.state.closed && <AssetInfo />}
</PageLayout> </PageLayout>
); );
} else {
return (
<PageLayout>
<div className="asset-main">
<AssetBlocked />
</div>
</PageLayout>
);
}
} }
return ( return (
<ErrorPage error={'loading asset data...'} /> <ErrorPage error={'loading asset data...'} />

View file

@ -5,6 +5,7 @@ const getClaimData = require('server/utils/getClaimData');
const chainquery = require('chainquery').default; const chainquery = require('chainquery').default;
const db = require('../../../../models'); const db = require('../../../../models');
const waitOn = require('wait-on'); const waitOn = require('wait-on');
const logger = require('winston');
/* /*
@ -17,32 +18,35 @@ const claimGet = async ({ ip, originalUrl, params }, res) => {
const claimId = params.claimId; const claimId = params.claimId;
try { try {
let claimData = await chainquery.claim.queries.resolveClaim(name, claimId).catch(() => {}); let claimInfo = await chainquery.claim.queries.resolveClaim(name, claimId).catch(() => {});
if (!claimData) { if (claimInfo) {
claimData = await db.Claim.resolveClaim(name, claimId); logger.info('claim/get: claim resolved in chainquery');
} }
if (!claimInfo) {
if (!claimData) { claimInfo = await db.Claim.resolveClaim(name, claimId);
throw new Error('No matching uri found in Claim table'); }
if (!claimInfo) {
throw new Error('claim/get: resolveClaim: No matching uri found in Claim table');
} }
let lbrynetResult = await getClaim(`${name}#${claimId}`); let lbrynetResult = await getClaim(`${name}#${claimId}`);
if (!lbrynetResult) { if (!lbrynetResult) {
throw new Error(`Unable to Get ${name}#${claimId}`); throw new Error(`claim/get: getClaim Unable to Get ${name}#${claimId}`);
}
const claimData = await getClaimData(claimInfo);
if (!claimData) {
throw new Error('claim/get: getClaimData failed to get file blobs');
}
await waitOn({
resources: [ lbrynetResult.download_path ],
timeout : 10000, // 10 seconds
window : 500,
});
const fileData = await createFileRecordDataAfterGet(claimData, lbrynetResult);
if (!fileData) {
throw new Error('claim/get: createFileRecordDataAfterGet failed to create file in time');
} }
let fileData = await createFileRecordDataAfterGet(await getClaimData(claimData), lbrynetResult);
const upsertCriteria = { name, claimId }; const upsertCriteria = { name, claimId };
await db.upsert(db.File, fileData, upsertCriteria, 'File'); await db.upsert(db.File, fileData, upsertCriteria, 'File');
try {
await waitOn({
resources: [ lbrynetResult.file_name ],
delay : 500,
timeout : 10000, // 10 seconds
});
} catch (e) {}
const { message, completed } = lbrynetResult; const { message, completed } = lbrynetResult;
res.status(200).json({ res.status(200).json({
success: true, success: true,
@ -53,5 +57,4 @@ const claimGet = async ({ ip, originalUrl, params }, res) => {
handleErrorResponse(originalUrl, ip, error, res); handleErrorResponse(originalUrl, ip, error, res);
} }
}; };
module.exports = claimGet; module.exports = claimGet;

View file

@ -16,6 +16,7 @@ const createDatabaseIfNotExists = require('./models/utils/createDatabaseIfNotExi
const { getWalletBalance } = require('./lbrynet/index'); const { getWalletBalance } = 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 speechPassport = require('./speechPassport'); const speechPassport = require('./speechPassport');
const processTrending = require('./utils/processTrending'); const processTrending = require('./utils/processTrending');
@ -25,7 +26,7 @@ const {
} = require('./middleware/logMetricsMiddleware'); } = require('./middleware/logMetricsMiddleware');
const { const {
details: { port: PORT }, details: { port: PORT, blockListEndpoint },
startup: { startup: {
performChecks, performChecks,
performUpdates, performUpdates,
@ -34,6 +35,9 @@ const {
const { sessionKey } = require('@private/authConfig.json'); const { sessionKey } = require('@private/authConfig.json');
// configure.js doesn't handle new keys in config.json files yet. Make sure it doens't break.
let bLE;
function Server () { function Server () {
this.initialize = () => { this.initialize = () => {
// configure logging // configure logging
@ -166,19 +170,37 @@ function Server () {
logger.info('Starting LBC balance:', walletBalance); logger.info('Starting LBC balance:', walletBalance);
}); });
}; };
this.performUpdates = () => { this.performUpdates = () => {
if (!performUpdates) { if (!performUpdates) {
return; return;
} }
if (blockListEndpoint) {
bLE = blockListEndpoint;
} else if (!blockListEndpoint) {
if (typeof (blockListEndpoint) !== 'string') {
logger.warn('blockListEndpoint is null due to outdated siteConfig file. \n' +
'Continuing with default LBRY blocklist api endpoint. \n ' +
'(Specify /"blockListEndpoint" : ""/ to disable.')
bLE = 'https://api.lbry.io/file/list_blocked';
}
}
logger.info(`Peforming updates...`); logger.info(`Peforming updates...`);
return Promise.all([ if (!bLE) {
db.Blocked.refreshTable(), logger.info('Configured for no Block List')
db.Tor.refreshTable(), db.Tor.refreshTable().then( (updatedTorList) => {
])
.then(([updatedBlockedList, updatedTorList]) => {
logger.info('Blocked list updated, length:', updatedBlockedList.length);
logger.info('Tor list updated, length:', updatedTorList.length); logger.info('Tor list updated, length:', updatedTorList.length);
}); });
} else {
return Promise.all([
db.Blocked.refreshTable(bLE),
db.Tor.refreshTable()])
.then(([updatedBlockedList, updatedTorList]) => {
logger.info('Blocked list updated, length:', updatedBlockedList.length);
logger.info('Tor list updated, length:', updatedTorList.length);
})
}
}; };
this.start = () => { this.start = () => {
this.initialize(); this.initialize();
@ -194,6 +216,9 @@ function Server () {
this.performUpdates(), this.performUpdates(),
]); ]);
}) })
.then(() => {
return setupBlockList();
})
.then(() => { .then(() => {
logger.info('Spee.ch startup is complete'); logger.info('Spee.ch startup is complete');

View file

@ -1,5 +1,4 @@
const logger = require('winston'); const logger = require('winston');
const BLOCKED_CLAIM = 'BLOCKED_CLAIM'; const BLOCKED_CLAIM = 'BLOCKED_CLAIM';
module.exports = (sequelize, { STRING }) => { module.exports = (sequelize, { STRING }) => {
@ -16,6 +15,14 @@ module.exports = (sequelize, { STRING }) => {
} }
); );
Blocked.getBlockList = function () {
logger.debug('returning full block list');
return new Promise((resolve, reject) => {
this.findAll()
.then(list => { return resolve(list) });
});
};
Blocked.isNotBlocked = function (outpoint) { Blocked.isNotBlocked = function (outpoint) {
logger.debug(`checking to see if ${outpoint} is not blocked`); logger.debug(`checking to see if ${outpoint} is not blocked`);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -37,9 +44,10 @@ module.exports = (sequelize, { STRING }) => {
}); });
}; };
Blocked.refreshTable = function () { Blocked.refreshTable = function (blockEndpoint) {
let blockedList = []; let blockedList = [];
return fetch('https://api.lbry.io/file/list_blocked')
return fetch(blockEndpoint)
.then(response => { .then(response => {
return response.json(); return response.json();
}) })
@ -50,9 +58,7 @@ module.exports = (sequelize, { STRING }) => {
if (!jsonResponse.data.outpoints) { if (!jsonResponse.data.outpoints) {
throw new Error('no outpoints in list_blocked response'); throw new Error('no outpoints in list_blocked response');
} }
return jsonResponse.data.outpoints; let outpoints = jsonResponse.data.outpoints;
})
.then(outpoints => {
logger.debug('total outpoints:', outpoints.length); logger.debug('total outpoints:', outpoints.length);
// prep the records // prep the records
for (let i = 0; i < outpoints.length; i++) { for (let i = 0; i < outpoints.length; i++) {

26
server/utils/blockList.js Normal file
View file

@ -0,0 +1,26 @@
const logger = require('winston');
const db = require('../models');
let blockList = new Set();
const setupBlockList = (intervalInSeconds = 60) => {
const fetchList = () => {
return new Promise((resolve, reject) => {
db.Blocked.getBlockList()
.then((result) => {
blockList.clear();
if (result.length > 0) {
result.map((item) => { blockList.add(item.dataValues.outpoint) });
resolve();
} else reject();
})
.catch(e => { console.error('list was empty', e) });
});
};
setInterval(() => { fetchList() }, intervalInSeconds * 1000);
return fetchList();
};
module.exports = {
isBlocked: (outpoint) => { return blockList.has(outpoint) },
setupBlockList,
};

View file

@ -1,6 +1,7 @@
const { details: { host } } = require('@config/siteConfig'); const { details: { host } } = require('@config/siteConfig');
const chainquery = require('chainquery').default; const chainquery = require('chainquery').default;
const { getClaim } = require('server/lbrynet'); const { getClaim } = require('server/lbrynet');
const { isBlocked } = require('./blockList');
module.exports = async (data, chName = null, chShortId = null) => { module.exports = async (data, chName = null, chShortId = null) => {
// TODO: Refactor getching the channel name out; requires invasive changes. // TODO: Refactor getching the channel name out; requires invasive changes.
@ -9,6 +10,11 @@ module.exports = async (data, chName = null, chShortId = null) => {
let lbrynetFileExt = null; let lbrynetFileExt = null;
let channelShortId = chShortId; let channelShortId = chShortId;
let channelName = chName; let channelName = chName;
let blocked;
const outPoint = `${data.transaction_hash_id}:${data.vout}`;
if (isBlocked(outPoint)) {
blocked = true;
}
if (!chName && certificateId && !channelName) { if (!chName && certificateId && !channelName) {
channelName = await chainquery.claim.queries.getClaimChannelName(certificateId).catch(() => { channelName = await chainquery.claim.queries.getClaimChannelName(certificateId).catch(() => {
@ -38,8 +44,9 @@ module.exports = async (data, chName = null, chShortId = null) => {
fileExt : data.generated_extension || data.fileExt || lbrynetFileExt, fileExt : data.generated_extension || data.fileExt || lbrynetFileExt,
description: data.description, description: data.description,
thumbnail : data.generated_thumbnail || data.thumbnail_url || data.thumbnail, thumbnail : data.generated_thumbnail || data.thumbnail_url || data.thumbnail,
outpoint : `${data.transaction_hash_id}:${data.vout}` || data.outpoint, outpoint : outPoint || data.outpoint,
host, host,
pending : Boolean(data.height === 0), pending : Boolean(data.height === 0),
blocked : blocked,
}); });
}; };