Merge branch 'master' into dependencies-upgrade

This commit is contained in:
bill bittner 2018-07-08 11:30:42 -07:00
commit 1757d586cb
38 changed files with 719 additions and 328 deletions

View file

@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _SpaceBetween = _interopRequireDefault(require("@components/SpaceBetween"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var AssetShareButtons = function AssetShareButtons(_ref) {
var host = _ref.host,
name = _ref.name,
shortId = _ref.shortId;
return _react.default.createElement(_SpaceBetween.default, null, _react.default.createElement("a", {
className: "link--primary",
target: "_blank",
href: "https://twitter.com/intent/tweet?text=".concat(host, "/").concat(shortId, "/").concat(name)
}, "twitter"), _react.default.createElement("a", {
className: "link--primary",
target: "_blank",
href: "https://www.facebook.com/sharer/sharer.php?u=".concat(host, "/").concat(shortId, "/").concat(name)
}, "facebook"), _react.default.createElement("a", {
className: "link--primary",
target: "_blank",
href: "http://tumblr.com/widgets/share/tool?canonicalUrl=".concat(host, "/").concat(shortId, "/").concat(name)
}, "tumblr"), _react.default.createElement("a", {
className: "link--primary",
target: "_blank",
href: "https://www.reddit.com/submit?url=".concat(host, "/").concat(shortId, "/").concat(name, "&title=").concat(name)
}, "reddit"));
};
var _default = AssetShareButtons;
exports.default = _default;

View file

@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } _setPrototypeOf(subClass.prototype, superClass && superClass.prototype); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _getPrototypeOf(o) { _getPrototypeOf = Object.getPrototypeOf || function _getPrototypeOf(o) { return o.__proto__; }; return _getPrototypeOf(o); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
var ClickToCopy =
/*#__PURE__*/
function (_React$Component) {
function ClickToCopy(props) {
var _this;
_classCallCheck(this, ClickToCopy);
_this = _possibleConstructorReturn(this, _getPrototypeOf(ClickToCopy).call(this, props));
_this.copyToClipboard = _this.copyToClipboard.bind(_assertThisInitialized(_assertThisInitialized(_this)));
return _this;
}
_createClass(ClickToCopy, [{
key: "copyToClipboard",
value: function copyToClipboard(event) {
var elementToCopy = event.target.id;
var element = document.getElementById(elementToCopy);
element.select();
try {
document.execCommand('copy');
} catch (err) {
this.setState({
error: 'Oops, unable to copy'
});
}
}
}, {
key: "render",
value: function render() {
var _this$props = this.props,
id = _this$props.id,
value = _this$props.value;
return _react.default.createElement("input", {
id: id,
value: value,
onClick: this.copyToClipboard,
type: "text",
className: "click-to-copy",
readOnly: true,
spellCheck: "false"
});
}
}]);
_inherits(ClickToCopy, _React$Component);
return ClickToCopy;
}(_react.default.Component);
var _default = ClickToCopy;
exports.default = _default;

View file

@ -17,6 +17,10 @@ var _Row = _interopRequireDefault(require("@components/Row"));
var _SpaceBetween = _interopRequireDefault(require("@components/SpaceBetween"));
var _AssetShareButtons = _interopRequireDefault(require("@components/AssetShareButtons"));
var _ClickToCopy = _interopRequireDefault(require("@components/ClickToCopy"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
@ -33,80 +37,20 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _getPrototypeOf(o) { _getPrototypeOf = Object.getPrototypeOf || function _getPrototypeOf(o) { return o.__proto__; }; return _getPrototypeOf(o); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
var AssetShareButtons = function AssetShareButtons(_ref) {
var host = _ref.host,
name = _ref.name,
shortId = _ref.shortId;
return _react.default.createElement(_SpaceBetween.default, null, _react.default.createElement("a", {
className: "link--primary",
target: "_blank",
href: "https://twitter.com/intent/tweet?text=".concat(host, "/").concat(shortId, "/").concat(name)
}, "twitter"), _react.default.createElement("a", {
className: "link--primary",
target: "_blank",
href: "https://www.facebook.com/sharer/sharer.php?u=".concat(host, "/").concat(shortId, "/").concat(name)
}, "facebook"), _react.default.createElement("a", {
className: "link--primary",
target: "_blank",
href: "http://tumblr.com/widgets/share/tool?canonicalUrl=".concat(host, "/").concat(shortId, "/").concat(name)
}, "tumblr"), _react.default.createElement("a", {
className: "link--primary",
target: "_blank",
href: "https://www.reddit.com/submit?url=".concat(host, "/").concat(shortId, "/").concat(name, "&title=").concat(name)
}, "reddit"));
};
var ClickToCopy = function ClickToCopy(_ref2) {
var id = _ref2.id,
value = _ref2.value,
copyToClipboard = _ref2.copyToClipboard;
return _react.default.createElement("input", {
id: id,
value: value,
onClick: copyToClipboard,
type: "text",
className: "click-to-copy",
readOnly: true,
spellCheck: "false"
});
};
function _getPrototypeOf(o) { _getPrototypeOf = Object.getPrototypeOf || function _getPrototypeOf(o) { return o.__proto__; }; return _getPrototypeOf(o); }
var AssetInfo =
/*#__PURE__*/
function (_React$Component) {
function AssetInfo(props) {
var _this;
function AssetInfo() {
_classCallCheck(this, AssetInfo);
_this = _possibleConstructorReturn(this, _getPrototypeOf(AssetInfo).call(this, props));
_this.copyToClipboard = _this.copyToClipboard.bind(_assertThisInitialized(_assertThisInitialized(_this)));
return _this;
return _possibleConstructorReturn(this, _getPrototypeOf(AssetInfo).apply(this, arguments));
}
_createClass(AssetInfo, [{
key: "copyToClipboard",
value: function copyToClipboard(event) {
console.log('event:', event);
console.log('event.target:', event.target);
console.log('event.target.id:', event.target.id);
var elementToCopy = event.target.id;
var element = document.getElementById(elementToCopy);
element.select();
try {
document.execCommand('copy');
} catch (err) {
this.setState({
error: 'Oops, unable to copy'
});
}
}
}, {
key: "render",
value: function render() {
var _this$props$asset = this.props.asset,
@ -134,31 +78,29 @@ function (_React$Component) {
label: _react.default.createElement(_Label.default, {
value: 'Share:'
}),
content: _react.default.createElement(AssetShareButtons, {
content: _react.default.createElement(_AssetShareButtons.default, {
host: host,
name: name,
shortId: shortId
})
})), _react.default.createElement(_Row.default, null, _react.default.createElement(_RowLabeled.default, {
label: _react.default.createElement(_Label.default, {
value: 'Link:'
}),
content: _react.default.createElement(ClickToCopy, {
content: _react.default.createElement(_ClickToCopy.default, {
id: 'short-link',
value: "".concat(host, "/").concat(shortId, "/").concat(name, ".").concat(fileExt),
copyToClipboard: this.copyToClipboard
value: "".concat(host, "/").concat(shortId, "/").concat(name, ".").concat(fileExt)
})
})), _react.default.createElement(_Row.default, null, _react.default.createElement(_RowLabeled.default, {
label: _react.default.createElement(_Label.default, {
value: 'Embed:'
}),
content: _react.default.createElement("div", null, contentType === 'video/mp4' ? _react.default.createElement(ClickToCopy, {
content: _react.default.createElement("div", null, contentType === 'video/mp4' ? _react.default.createElement(_ClickToCopy.default, {
id: 'embed-text-video',
value: "<video width=\"100%\" controls poster=\"".concat(thumbnail, "\" src=\"").concat(host, "/").concat(claimId, "/").concat(name, ".").concat(fileExt, "\"/></video>"),
copyToClipboard: this.copyToClipboard
}) : _react.default.createElement(ClickToCopy, {
value: "<video width=\"100%\" controls poster=\"".concat(thumbnail, "\" src=\"").concat(host, "/").concat(claimId, "/").concat(name, ".").concat(fileExt, "\"/></video>")
}) : _react.default.createElement(_ClickToCopy.default, {
id: 'embed-text-image',
value: "<img src=\"".concat(host, "/").concat(claimId, "/").concat(name, ".").concat(fileExt, "\"/>"),
copyToClipboard: this.copyToClipboard
value: "<img src=\"".concat(host, "/").concat(claimId, "/").concat(name, ".").concat(fileExt, "\"/>")
}))
})), _react.default.createElement(_Row.default, null, _react.default.createElement(_SpaceBetween.default, null, _react.default.createElement(_reactRouterDom.Link, {
className: "link--primary",

View file

@ -40,11 +40,15 @@ function (_React$Component) {
key: "render",
value: function render() {
var message = this.props.message;
return _react.default.createElement("div", null, _react.default.createElement("p", {
return _react.default.createElement("div", {
className: 'publish-disabled-message'
}, _react.default.createElement("div", {
className: 'message'
}, _react.default.createElement("p", {
className: 'text--secondary'
}, "Publishing is currently disabled."), _react.default.createElement("p", {
className: 'text--secondary'
}, message));
}, message)));
}
}]);

View file

@ -101,9 +101,9 @@ var createAssetMetaTags = function createAssetMetaTags(_ref3) {
defaultThumbnail = _ref3.defaultThumbnail;
var claimData = asset.claimData;
var contentType = claimData.contentType;
var embedUrl = "".concat(siteHost, "/").concat(claimData.claimId, "/").concat(claimData.name);
var videoEmbedUrl = "".concat(siteHost, "/video-embed/").concat(claimData.name, "/").concat(claimData.claimId);
var showUrl = "".concat(siteHost, "/").concat(claimData.claimId, "/").concat(claimData.name);
var source = "".concat(siteHost, "/").concat(claimData.claimId, "/").concat(claimData.name, ".").concat(claimData.fileExt);
var source = "".concat(siteHost, "/asset/").concat(claimData.name, "/").concat(claimData.claimId);
var ogTitle = claimData.title || claimData.name;
var ogDescription = claimData.description || defaultDescription;
var ogThumbnailContentType = determineOgThumbnailContentType(claimData.thumbnail);
@ -154,7 +154,7 @@ var createAssetMetaTags = function createAssetMetaTags(_ref3) {
});
metaTags.push({
property: 'og:type',
content: 'video'
content: 'video.other'
});
metaTags.push({
property: 'twitter:card',
@ -162,7 +162,7 @@ var createAssetMetaTags = function createAssetMetaTags(_ref3) {
});
metaTags.push({
property: 'twitter:player',
content: embedUrl
content: videoEmbedUrl
});
metaTags.push({
property: 'twitter:player:width',

View file

@ -40,6 +40,7 @@
@import 'containers/_dropzone';
@import 'containers/_publish-url-input';
@import 'containers/_publish-status';
@import 'containers/_publish-disabled-message';
@import '_media-queries';

View file

@ -0,0 +1,11 @@
.publish-disabled-message {
// fill the parent flex container
flex: 1 0 auto;
// be a flex container for children
display: flex;
flex-direction: column;
justify-content: center;
.message {
text-align: center;
}
}

View file

@ -0,0 +1,39 @@
import React from 'react';
import SpaceBetween from '@components/SpaceBetween';
const AssetShareButtons = ({ host, name, shortId }) => {
return (
<SpaceBetween >
<a
className='link--primary'
target='_blank'
href={`https://twitter.com/intent/tweet?text=${host}/${shortId}/${name}`}
>
twitter
</a>
<a
className='link--primary'
target='_blank'
href={`https://www.facebook.com/sharer/sharer.php?u=${host}/${shortId}/${name}`}
>
facebook
</a>
<a
className='link--primary'
target='_blank'
href={`http://tumblr.com/widgets/share/tool?canonicalUrl=${host}/${shortId}/${name}`}
>
tumblr
</a>
<a
className='link--primary'
target='_blank'
href={`https://www.reddit.com/submit?url=${host}/${shortId}/${name}&title=${name}`}
>
reddit
</a>
</SpaceBetween>
);
};
export default AssetShareButtons;

View file

@ -0,0 +1,34 @@
import React from 'react';
class ClickToCopy extends React.Component {
constructor (props) {
super(props);
this.copyToClipboard = this.copyToClipboard.bind(this);
}
copyToClipboard (event) {
const elementToCopy = event.target.id;
const element = document.getElementById(elementToCopy);
element.select();
try {
document.execCommand('copy');
} catch (err) {
this.setState({error: 'Oops, unable to copy'});
}
}
render () {
const {id, value} = this.props;
return (
<input
id={id}
value={value}
onClick={this.copyToClipboard}
type='text'
className='click-to-copy'
readOnly
spellCheck='false'
/>
);
}
}
export default ClickToCopy;

View file

@ -4,74 +4,10 @@ import Label from '@components/Label';
import RowLabeled from '@components/RowLabeled';
import Row from '@components/Row';
import SpaceBetween from '@components/SpaceBetween';
const AssetShareButtons = ({ host, name, shortId }) => {
return (
<SpaceBetween >
<a
className='link--primary'
target='_blank'
href={`https://twitter.com/intent/tweet?text=${host}/${shortId}/${name}`}
>
twitter
</a>
<a
className='link--primary'
target='_blank'
href={`https://www.facebook.com/sharer/sharer.php?u=${host}/${shortId}/${name}`}
>
facebook
</a>
<a
className='link--primary'
target='_blank'
href={`http://tumblr.com/widgets/share/tool?canonicalUrl=${host}/${shortId}/${name}`}
>
tumblr
</a>
<a
className='link--primary'
target='_blank'
href={`https://www.reddit.com/submit?url=${host}/${shortId}/${name}&title=${name}`}
>
reddit
</a>
</SpaceBetween>
);
};
const ClickToCopy = ({id, value, copyToClipboard}) => {
return (
<input
id={id}
value={value}
onClick={copyToClipboard}
type='text'
className='click-to-copy'
readOnly
spellCheck='false'
/>
);
};
import AssetShareButtons from '@components/AssetShareButtons';
import ClickToCopy from '@components/ClickToCopy';
class AssetInfo extends React.Component {
constructor (props) {
super(props);
this.copyToClipboard = this.copyToClipboard.bind(this);
}
copyToClipboard (event) {
console.log('event:', event);
console.log('event.target:', event.target);
console.log('event.target.id:', event.target.id);
const elementToCopy = event.target.id;
const element = document.getElementById(elementToCopy);
element.select();
try {
document.execCommand('copy');
} catch (err) {
this.setState({error: 'Oops, unable to copy'});
}
}
render () {
const { asset: { shortId, claimData : { channelName, certificateId, description, name, claimId, fileExt, contentType, thumbnail, host } } } = this.props;
return (
@ -99,6 +35,7 @@ class AssetInfo extends React.Component {
content={
<AssetShareButtons
host={host}
name={name}
shortId={shortId}
/>
}
@ -114,7 +51,6 @@ class AssetInfo extends React.Component {
<ClickToCopy
id={'short-link'}
value={`${host}/${shortId}/${name}.${fileExt}`}
copyToClipboard={this.copyToClipboard}
/>
}
/>
@ -131,13 +67,11 @@ class AssetInfo extends React.Component {
<ClickToCopy
id={'embed-text-video'}
value={`<video width="100%" controls poster="${thumbnail}" src="${host}/${claimId}/${name}.${fileExt}"/></video>`}
copyToClipboard={this.copyToClipboard}
/>
) : (
<ClickToCopy
id={'embed-text-image'}
value={`<img src="${host}/${claimId}/${name}.${fileExt}"/>`}
copyToClipboard={this.copyToClipboard}
/>
)}
</div>

View file

@ -4,9 +4,11 @@ class PublishDisabledMessage extends React.Component {
render () {
const message = this.props.message;
return (
<div>
<p className={'text--secondary'}>Publishing is currently disabled.</p>
<p className={'text--secondary'}>{message}</p>
<div className={'publish-disabled-message'}>
<div className={'message'}>
<p className={'text--secondary'}>Publishing is currently disabled.</p>
<p className={'text--secondary'}>{message}</p>
</div>
</div>
);
}

View file

@ -46,9 +46,9 @@ const createChannelMetaTags = ({siteHost, siteTitle, siteTwitter, channel}) => {
const createAssetMetaTags = ({siteHost, siteTitle, siteTwitter, asset, defaultDescription, defaultThumbnail}) => {
const { claimData } = asset;
const { contentType } = claimData;
const embedUrl = `${siteHost}/${claimData.claimId}/${claimData.name}`;
const videoEmbedUrl = `${siteHost}/video-embed/${claimData.name}/${claimData.claimId}`;
const showUrl = `${siteHost}/${claimData.claimId}/${claimData.name}`;
const source = `${siteHost}/${claimData.claimId}/${claimData.name}.${claimData.fileExt}`;
const source = `${siteHost}/asset/${claimData.name}/${claimData.claimId}`;
const ogTitle = claimData.title || claimData.name;
const ogDescription = claimData.description || defaultDescription;
const ogThumbnailContentType = determineOgThumbnailContentType(claimData.thumbnail);
@ -68,9 +68,9 @@ const createAssetMetaTags = ({siteHost, siteTitle, siteTwitter, asset, defaultDe
metaTags.push({property: 'og:video:type', content: contentType});
metaTags.push({property: 'og:image', content: ogThumbnail});
metaTags.push({property: 'og:image:type', content: ogThumbnailContentType});
metaTags.push({property: 'og:type', content: 'video'});
metaTags.push({property: 'og:type', content: 'video.other'});
metaTags.push({property: 'twitter:card', content: 'player'});
metaTags.push({property: 'twitter:player', content: embedUrl});
metaTags.push({property: 'twitter:player', content: videoEmbedUrl});
metaTags.push({property: 'twitter:player:width', content: 600});
metaTags.push({property: 'twitter:text:player_width', content: 600});
metaTags.push({property: 'twitter:player:height', content: 337});

View file

@ -35,6 +35,10 @@ const speechPassport = require('./server/speechPassport');
const {
details: { port: PORT },
auth: { sessionKey },
startup: {
performChecks,
performUpdates,
},
} = require('@config/siteConfig');
function Server () {
@ -97,31 +101,70 @@ function Server () {
/* create server */
this.server = http.Server(this.app);
};
this.startServerListening = () => {
logger.info(`Starting server on ${PORT}...`);
return new Promise((resolve, reject) => {
this.server.listen(PORT, () => {
logger.info(`Server is listening on PORT ${PORT}`);
resolve();
})
});
};
this.syncDatabase = () => {
logger.info(`Syncing database...`);
return createDatabaseIfNotExists()
.then(() => {
db.sequelize.sync();
})
};
this.performChecks = () => {
if (!performChecks) {
return;
}
logger.info(`Performing checks...`);
return Promise.all([
getWalletBalance(),
])
.then(([walletBalance]) => {
logger.info('Starting LBC balance:', walletBalance);
})
};
this.performUpdates = () => {
if (!performUpdates) {
return;
}
logger.info(`Peforming updates...`);
return Promise.all([
db.Blocked.refreshTable(),
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.initialize();
this.createApp();
this.createServer();
/* start the server */
logger.info('getting LBC balance & syncing database...');
Promise.all([
this.syncDatabase(),
getWalletBalance(),
])
.then(([syncResult, walletBalance]) => {
logger.info('starting LBC balance:', walletBalance);
return this.server.listen(PORT, () => {
logger.info(`Server is listening on PORT ${PORT}`);
})
this.syncDatabase()
.then(() => {
return this.startServerListening();
})
.then(() => {
return Promise.all([
this.performChecks(),
this.performUpdates(),
])
})
.then(() => {
logger.info('Spee.ch startup is complete');
})
.catch(error => {
if (error.code === 'ECONNREFUSED') {
return logger.error('Connection refused. The daemon may not be running.')
} else if (error.code === 'EADDRINUSE') {
return logger.error('Server could not start listening. The port is already in use.');
} else if (error.message) {
logger.error(error.message);
}

View file

@ -0,0 +1,22 @@
const logger = require('winston');
const db = require('../../../models');
const updateBlockedList = (req, res) => {
db.Blocked.refreshTable()
.then(data => {
logger.info('finished updating blocked content list');
res.status(200).json({
success: true,
data,
});
})
.catch(error => {
logger.error(error);
res.status(500).json({
success: false,
error,
});
});
};
module.exports = updateBlockedList;

View file

@ -1,56 +0,0 @@
const logger = require('winston');
const db = require('../../../../models');
const updateBlockedList = (req, res) => {
return fetch('https://api.lbry.io/file/list_blocked')
.then(response => {
return response.json();
})
.then(jsonResponse => {
if (!jsonResponse.data) {
throw new Error('no data in list_blocked response');
}
if (!jsonResponse.data.outpoints) {
throw new Error('no outpoints in list_blocked response');
}
return jsonResponse.data.outpoints;
})
.then(outpoints => {
logger.info('number of blocked outpoints:', outpoints.length);
let updatePromises = [];
outpoints.forEach(outpoint => {
// logger.debug('outpoint:', outpoint);
updatePromises.push(db.Claim
.findOne({
where: {
outpoint,
},
})
.then(Claim => {
if (Claim) {
const { claimId, name } = Claim;
logger.debug(`creating record in Blocked for ${name}#${claimId}`);
const blocked = {
claimId,
name,
outpoint,
};
return db.upsert(db.Blocked, blocked, blocked, 'Blocked')
}
})
.catch(error => {
logger.error(error);
}));
});
return Promise.all(updatePromises);
})
.then(() => {
logger.info('finished updating blocked content list');
res.status(200).json({success: true, message: 'finished updating blocked content list'});
})
.catch((error) => {
logger.error(error);
});
};
module.exports = updateBlockedList;

View file

@ -22,7 +22,10 @@ const claimLongId = ({ ip, originalUrl, body, params }, res) => {
getClaimId(channelName, channelClaimId, claimName, claimId)
.then(fullClaimId => {
claimId = fullClaimId;
return db.Blocked.isNotBlocked(fullClaimId, claimName);
return db.Claim.getOutpoint(claimName, fullClaimId);
})
.then(outpoint => {
return db.Blocked.isNotBlocked(outpoint);
})
.then(() => {
res.status(200).json({success: true, data: claimId});

View file

@ -1,4 +1,6 @@
const { details: { host } } = require('@config/siteConfig');
const logger = require('winston');
const { details: { host }, publishing: { disabled, disabledMessage } } = require('@config/siteConfig');
const { sendGATimingEvent } = require('../../../../utils/googleAnalytics.js');
@ -19,7 +21,20 @@ const authenticateUser = require('./authentication.js');
*/
const claimPublish = ({ body, files, headers, ip, originalUrl, user }, res) => {
const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res) => {
// logging
logger.info('Publish request:', {
ip,
headers,
body,
});
// check for disabled publishing
if (disabled) {
return res.status(503).json({
success: false,
message: disabledMessage
});
}
// define variables
let channelName, channelId, channelPassword, description, fileName, filePath, fileType, gaStartTime, license, name, nsfw, thumbnail, thumbnailFileName, thumbnailFilePath, thumbnailFileType, title;
// record the start time of the request

View file

@ -0,0 +1,29 @@
const { publishing: {
primaryClaimAddress,
uploadDirectory,
thumbnailChannel,
thumbnailChannelId,
additionalClaimAddresses,
disabled,
disabledMessage
} } = require('@config/siteConfig');
/*
route to see if publishing is enabled
*/
const publishingConfig = (req, res) => {
return res.status(200).json({
primaryClaimAddress,
uploadDirectory,
thumbnailChannel,
thumbnailChannelId,
additionalClaimAddresses,
disabled,
disabledMessage
});
};
module.exports = publishingConfig;

View file

@ -0,0 +1,25 @@
const logger = require('winston');
const db = require('../../../models');
/*
Route to update and return tor exit nodes that can connect to this ip address
*/
const getTorList = (req, res) => {
db.Tor.refreshTable()
.then( result => {
logger.debug('number of records', result.length);
res.status(200).json(result);
})
.catch((error) => {
logger.error(error);
res.status(500).json({
success: false,
error,
})
});
};
module.exports = getTorList;

View file

@ -0,0 +1,9 @@
const EMBED = 'EMBED';
const BROWSER = 'BROWSER';
const SOCIAL = 'SOCIAL';
module.exports = {
EMBED,
BROWSER,
SOCIAL,
};

View file

@ -0,0 +1,17 @@
const { sendGAServeEvent } = require('../../../utils/googleAnalytics');
const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js');
/*
route to serve an asset directly
*/
const serveAsset = ({ headers, ip, originalUrl, params: { claimName, claimId } }, res) => {
// send google analytics
sendGAServeEvent(headers, ip, originalUrl);
// get the claim Id and then serve the asset
getClaimIdAndServeAsset(null, null, claimName, claimId, originalUrl, ip, res);
};
module.exports = serveAsset;

View file

@ -3,11 +3,10 @@ const handleShowRender = require('../../../render/build/handleShowRender.js');
const lbryUri = require('../utils/lbryUri.js');
const determineResponseType = require('../utils/determineResponseType.js');
const determineRequestType = require('../utils/determineRequestType.js');
const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js');
const logRequestData = require('../utils/logRequestData.js');
const SERVE = 'SERVE';
const { EMBED } = require('../constants/request_types.js');
/*
@ -15,7 +14,7 @@ const SERVE = 'SERVE';
*/
const serverAssetByClaim = (req, res) => {
const serveByClaim = (req, res) => {
const { headers, ip, originalUrl, params } = req;
// decide if this is a show request
let hasFileExtension;
@ -24,13 +23,11 @@ const serverAssetByClaim = (req, res) => {
} catch (error) {
return res.status(400).json({success: false, message: error.message});
}
let responseType = determineResponseType(hasFileExtension, headers);
if (responseType !== SERVE) {
// determine request type
let requestType = determineRequestType(hasFileExtension, headers);
if (requestType !== EMBED) {
return handleShowRender(req, res);
}
// handle serve request
// send google analytics
sendGAServeEvent(headers, ip, originalUrl);
// parse the claim
let claimName;
try {
@ -38,10 +35,10 @@ const serverAssetByClaim = (req, res) => {
} catch (error) {
return res.status(400).json({success: false, message: error.message});
}
// log the request data for debugging
logRequestData(responseType, claimName, null, null);
// send google analytics
sendGAServeEvent(headers, ip, originalUrl);
// get the claim Id and then serve the asset
getClaimIdAndServeAsset(null, null, claimName, null, originalUrl, ip, res);
};
module.exports = serverAssetByClaim;
module.exports = serveByClaim;

View file

@ -3,12 +3,11 @@ const handleShowRender = require('../../../render/build/handleShowRender.js');
const lbryUri = require('../utils/lbryUri.js');
const determineResponseType = require('../utils/determineResponseType.js');
const determineRequestType = require('../utils/determineRequestType.js');
const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js');
const flipClaimNameAndId = require('../utils/flipClaimNameAndId.js');
const logRequestData = require('../utils/logRequestData.js');
const SERVE = 'SERVE';
const { EMBED } = require('../constants/request_types.js');
/*
@ -16,22 +15,20 @@ const SERVE = 'SERVE';
*/
const serverAssetByIdentifierAndClaim = (req, res) => {
const serverByIdentifierAndClaim = (req, res) => {
const { headers, ip, originalUrl, params } = req;
// decide if this is a show request
// parse request
let hasFileExtension;
try {
({ hasFileExtension } = lbryUri.parseModifier(params.claim));
} catch (error) {
return res.status(400).json({success: false, message: error.message});
}
let responseType = determineResponseType(hasFileExtension, headers);
if (responseType !== SERVE) {
// determine request type
let requestType = determineRequestType(hasFileExtension, headers);
if (requestType !== EMBED) {
return handleShowRender(req, res);
}
// handle serve request
// send google analytics
sendGAServeEvent(headers, ip, originalUrl);
// parse the claim
let claimName;
try {
@ -50,10 +47,10 @@ const serverAssetByIdentifierAndClaim = (req, res) => {
if (!isChannel) {
[claimId, claimName] = flipClaimNameAndId(claimId, claimName);
}
// log the request data for debugging
logRequestData(responseType, claimName, channelName, claimId);
// send google analytics
sendGAServeEvent(headers, ip, originalUrl);
// get the claim Id and then serve the asset
getClaimIdAndServeAsset(channelName, channelClaimId, claimName, claimId, originalUrl, ip, res);
};
module.exports = serverAssetByIdentifierAndClaim;
module.exports = serverByIdentifierAndClaim;

View file

@ -0,0 +1,60 @@
const logger = require('winston');
const { EMBED, BROWSER, SOCIAL } = require('../constants/request_types.js');
function headersMatchesSocialBotList (headers) {
const userAgent = headers['user-agent'];
const socialBotList = [
'facebookexternalhit',
'Twitterbot',
];
for (let i = 0; i < socialBotList.length; i++) {
if (userAgent.includes(socialBotList[i])) {
logger.debug('request is from social bot:', socialBotList[i]);
return true;
}
}
return false;
}
function clientAcceptsHtml ({accept}) {
return accept && accept.match(/text\/html/);
}
function requestIsFromBrowser (headers) {
return headers['user-agent'] && headers['user-agent'].match(/Mozilla/);
}
function clientWantsAsset ({accept, range}) {
const imageIsWanted = accept && accept.match(/image\/.*/) && !accept.match(/text\/html/) && !accept.match(/text\/\*/);
const videoIsWanted = accept && range;
return imageIsWanted || videoIsWanted;
}
const determineRequestType = (hasFileExtension, headers) => {
let responseType;
logger.debug('headers:', headers);
// return early with 'show' if headers match the list
if (headersMatchesSocialBotList(headers)) {
return SOCIAL;
}
// if request is not from a social bot...
if (hasFileExtension) {
// assume embed,
responseType = EMBED;
// but change to browser if client accepts html.
if (clientAcceptsHtml(headers)) {
responseType = BROWSER;
}
// if request does not have file extentsion...
} else {
// assume browser,
responseType = BROWSER;
// but change to embed if someone embeded a show url...
if (clientWantsAsset(headers) && requestIsFromBrowser(headers)) {
responseType = EMBED;
}
}
return responseType;
};
module.exports = determineRequestType;

View file

@ -1,37 +0,0 @@
const logger = require('winston');
const SERVE = 'SERVE';
const SHOW = 'SHOW';
function clientAcceptsHtml ({accept}) {
return accept && accept.match(/text\/html/);
};
function requestIsFromBrowser (headers) {
return headers['user-agent'] && headers['user-agent'].match(/Mozilla/);
};
function clientWantsAsset ({accept, range}) {
const imageIsWanted = accept && accept.match(/image\/.*/) && !accept.match(/text\/html/) && !accept.match(/text\/\*/);
const videoIsWanted = accept && range;
return imageIsWanted || videoIsWanted;
};
const determineResponseType = (hasFileExtension, headers) => {
let responseType;
if (hasFileExtension) {
responseType = SERVE; // assume a serve request if file extension is present
if (clientAcceptsHtml(headers)) { // if the request comes from a browser, change it to a show request
responseType = SHOW;
}
} else {
responseType = SHOW;
if (clientWantsAsset(headers) && requestIsFromBrowser(headers)) { // this is in case someone embeds a show url
logger.debug('Show request came from browser but wants an image/video. Changing response to serve...');
responseType = SERVE;
}
}
return responseType;
};
module.exports = determineResponseType;

View file

@ -17,7 +17,12 @@ const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId
getClaimId(channelName, channelClaimId, claimName, claimId)
.then(fullClaimId => {
claimId = fullClaimId;
return db.Blocked.isNotBlocked(fullClaimId, claimName);
logger.debug('Full claim id:', fullClaimId);
return db.Claim.getOutpoint(claimName, fullClaimId);
})
.then(outpoint => {
logger.debug('Outpoint:', outpoint);
return db.Blocked.isNotBlocked(outpoint);
})
.then(() => {
return getLocalFileRecord(claimId, claimName);

View file

@ -1,10 +1,10 @@
const { details: { host } } = require('@config/siteConfig');
const sendEmbedPage = ({ params }, res) => {
const sendVideoEmbedPage = ({ params }, res) => {
const claimId = params.claimId;
const name = params.name;
// get and render the content
res.status(200).render('embed', { layout: 'embed', host, claimId, name });
};
module.exports = sendEmbedPage;
module.exports = sendVideoEmbedPage;

View file

@ -1,6 +1,6 @@
const axios = require('axios');
const logger = require('winston');
const { apiHost, apiPort } = require('@config/lbryConfig');
const { apiHost, apiPort, getTimeout } = require('@config/lbryConfig');
const lbrynetUri = 'http://' + apiHost + ':' + apiPort;
const { chooseGaLbrynetPublishLabel, sendGATimingEvent } = require('../utils/googleAnalytics.js');
const handleLbrynetResponse = require('./utils/handleLbrynetResponse.js');
@ -31,7 +31,10 @@ module.exports = {
axios
.post(lbrynetUri, {
method: 'get',
params: { uri, timeout: 20 },
params: {
uri,
timeout: getTimeout || 30,
},
})
.then(response => {
sendGATimingEvent('lbrynet', 'getClaim', 'GET', gaStartTime, Date.now());

View file

@ -0,0 +1,31 @@
const logger = require('winston');
const db = require('../models');
const torCheck = (req, res, next) => {
const { ip } = req;
logger.debug(`tor check for: ${ip}`);
return db.Tor.findAll(
{
where: {
address: ip,
},
raw: true,
})
.then(result => {
if (result.length >= 1) {
logger.info('Tor request blocked:', ip);
const failureResponse = {
success: false,
message: 'Unfortunately this api route is not currently available for tor users. We are working on a solution that will allow tor users to use this endpoint in the future.',
};
res.status(403).json(failureResponse);
} else {
return next();
}
})
.catch(error => {
logger.error(error);
});
};
module.exports = torCheck;

View file

@ -6,14 +6,6 @@ module.exports = (sequelize, { STRING }) => {
const Blocked = sequelize.define(
'Blocked',
{
claimId: {
type : STRING,
allowNull: false,
},
name: {
type : STRING,
allowNull: false,
},
outpoint: {
type : STRING,
allowNull: false,
@ -24,13 +16,12 @@ module.exports = (sequelize, { STRING }) => {
}
);
Blocked.isNotBlocked = function (claimId, name) {
logger.debug(`checking to see if ${name}#${claimId} is not blocked`);
Blocked.isNotBlocked = function (outpoint) {
logger.debug(`checking to see if ${outpoint} is not blocked`);
return new Promise((resolve, reject) => {
this.findOne({
where: {
claimId,
name,
outpoint,
},
})
.then(result => {
@ -46,5 +37,39 @@ module.exports = (sequelize, { STRING }) => {
});
};
Blocked.refreshTable = function () {
let blockedList = [];
return fetch('https://api.lbry.io/file/list_blocked')
.then(response => {
return response.json();
})
.then(jsonResponse => {
if (!jsonResponse.data) {
throw new Error('no data in list_blocked response');
}
if (!jsonResponse.data.outpoints) {
throw new Error('no outpoints in list_blocked response');
}
return jsonResponse.data.outpoints;
})
.then(outpoints => {
logger.debug('total outpoints:', outpoints.length);
// prep the records
for (let i = 0; i < outpoints.length; i++) {
blockedList.push({
outpoint: outpoints[i],
});
}
// clear the table
return this.destroy({
truncate: true,
});
})
.then(() => {
// fill the table
return this.bulkCreate(blockedList);
});
};
return Blocked;
};

View file

@ -378,5 +378,27 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
});
};
Claim.getOutpoint = function (name, claimId) {
return this
.findAll({
where : { name, claimId },
attributes: ['outpoint'],
})
.then(result => {
logger.debug('outpoint result');
switch (result.length) {
case 0:
throw new Error(`no record found for ${name}#${claimId}`);
case 1:
return result[0].dataValues.outpoint;
default:
throw new Error(`more than one record found for ${name}#${claimId}`);
}
})
.catch(error => {
throw error;
});
};
return Claim;
};

View file

@ -8,6 +8,7 @@ const File = require('./file.js');
const Request = require('./request.js');
const User = require('./user.js');
const Blocked = require('./blocked.js');
const Tor = require('./tor.js');
const {database, username, password} = require('@config/mysqlConfig');
if (!database || !username || !password) {
@ -50,6 +51,7 @@ db['File'] = sequelize.import('File', File);
db['Request'] = sequelize.import('Request', Request);
db['User'] = sequelize.import('User', User);
db['Blocked'] = sequelize.import('Blocked', Blocked);
db['Tor'] = sequelize.import('Tor', Tor);
// run model.association for each model in the db object that has an association
logger.info('associating db models...');

59
server/models/tor.js Normal file
View file

@ -0,0 +1,59 @@
const logger = require('winston');
const { details: { ipAddress } } = require('@config/siteConfig');
module.exports = (sequelize, { STRING }) => {
const Tor = sequelize.define(
'Tor',
{
address: {
type : STRING,
allowNull: false,
},
fingerprint: {
type : STRING,
allowNull: true,
},
},
{
freezeTableName: true,
}
);
Tor.refreshTable = function () {
let torList = [];
return fetch(`https://check.torproject.org/api/bulk?ip=${ipAddress}&port=80`)
.then(response => {
return response.json();
})
.then(jsonResponse => {
logger.debug('total tor nodes:', jsonResponse.length);
// prep the records
for (let i = 0; i < jsonResponse.length; i++) {
torList.push({
address : jsonResponse[i].Address,
fingerprint: jsonResponse[i].Fingerprint,
});
}
// clear the table
return this.destroy({
truncate: true,
});
})
.then(() => {
// fill the table
return this.bulkCreate(torList);
})
.then(() => {
// return the new table
return this.findAll({
attributes: ['address', 'fingerprint'],
raw : true,
});
})
.catch(error => {
throw error;
});
};
return Tor;
};

View file

@ -1,9 +1,12 @@
// middleware
const multipartMiddleware = require('../../middleware/multipartMiddleware');
const torCheckMiddleware = require('../../middleware/torCheckMiddleware');
// route handlers
const channelAvailability = require('../../controllers/api/channel/availability');
const channelClaims = require('../../controllers/api/channel/claims');
const channelData = require('../../controllers/api/channel/data');
const channelShortId = require('../../controllers/api/channel/shortId');
const claimAvailability = require('../../controllers/api/claim/availability');
const claimBlockedList = require('../../controllers/api/claim/blockedList');
const claimData = require('../../controllers/api/claim/data/');
const claimGet = require('../../controllers/api/claim/get');
const claimList = require('../../controllers/api/claim/list');
@ -13,27 +16,34 @@ const claimResolve = require('../../controllers/api/claim/resolve');
const claimShortId = require('../../controllers/api/claim/shortId');
const fileAvailability = require('../../controllers/api/file/availability');
const userPassword = require('../../controllers/api/user/password');
const publishingConfig = require('../../controllers/api/config/site/publishing');
const getTorList = require('../../controllers/api/tor');
const getBlockedList = require('../../controllers/api/blocked');
const multipartMiddleware = require('../utils/multipartMiddleware');
module.exports = (app) => {
// channel routes
app.get('/api/channel/availability/:name', channelAvailability);
app.get('/api/channel/short-id/:longId/:name', channelShortId);
app.get('/api/channel/data/:channelName/:channelClaimId', channelData);
app.get('/api/channel/claims/:channelName/:channelClaimId/:page', channelClaims);
app.get('/api/channel/availability/:name', torCheckMiddleware, channelAvailability);
app.get('/api/channel/short-id/:longId/:name', torCheckMiddleware, channelShortId);
app.get('/api/channel/data/:channelName/:channelClaimId', torCheckMiddleware, channelData);
app.get('/api/channel/claims/:channelName/:channelClaimId/:page', torCheckMiddleware, channelClaims);
// claim routes
app.get('/api/claim/availability/:name', claimAvailability);
app.get('/api/claim/blocked-list/', claimBlockedList);
app.get('/api/claim/data/:claimName/:claimId', claimData);
app.get('/api/claim/get/:name/:claimId', claimGet);
app.get('/api/claim/list/:name', claimList);
app.post('/api/claim/long-id', claimLongId);
app.post('/api/claim/publish', multipartMiddleware, claimPublish);
app.get('/api/claim/resolve/:name/:claimId', claimResolve);
app.get('/api/claim/short-id/:longId/:name', claimShortId);
app.get('/api/claim/availability/:name', torCheckMiddleware, claimAvailability);
app.get('/api/claim/data/:claimName/:claimId', torCheckMiddleware, claimData);
app.get('/api/claim/get/:name/:claimId', torCheckMiddleware, claimGet);
app.get('/api/claim/list/:name', torCheckMiddleware, claimList);
app.post('/api/claim/long-id', torCheckMiddleware, claimLongId); // note: should be a 'get'
app.post('/api/claim/publish', torCheckMiddleware, multipartMiddleware, claimPublish);
app.get('/api/claim/resolve/:name/:claimId', torCheckMiddleware, claimResolve);
app.get('/api/claim/short-id/:longId/:name', torCheckMiddleware, claimShortId);
// file routes
app.get('/api/file/availability/:name/:claimId', fileAvailability);
app.get('/api/file/availability/:name/:claimId', torCheckMiddleware, fileAvailability);
// user routes
app.put('/api/user/password/', userPassword);
app.put('/api/user/password/', torCheckMiddleware, userPassword);
// configs
app.get('/api/config/site/publishing', torCheckMiddleware, publishingConfig);
// tor
app.get('/api/tor', torCheckMiddleware, getTorList);
// blocked
app.get('/api/blocked', torCheckMiddleware, getBlockedList);
};

View file

@ -1,7 +1,9 @@
const serveAssetByClaim = require('../../controllers/assets/serveByClaim');
const serveAssetByIdentifierAndClaim = require('../../controllers/assets/serveByIdentifierAndClaim');
const serveByClaim = require('../../controllers/assets/serveByClaim');
const serveByIdentifierAndClaim = require('../../controllers/assets/serveByIdentifierAndClaim');
const serveAsset = require('../../controllers/assets/serveAsset');
module.exports = (app, db) => {
app.get('/:identifier/:claim', serveAssetByIdentifierAndClaim);
app.get('/:claim', serveAssetByClaim);
module.exports = (app) => {
app.get('/asset/:claimName/:claimId/', serveAsset);
app.get('/:identifier/:claim', serveByIdentifierAndClaim);
app.get('/:claim', serveByClaim);
};

View file

@ -1,5 +1,5 @@
const handlePageRequest = require('../../controllers/pages/sendReactApp');
const handleEmbedRequest = require('../../controllers/pages/sendEmbedPage');
const handleVideoEmbedRequest = require('../../controllers/pages/sendVideoEmbedPage');
const redirect = require('../../controllers/utils/redirect');
module.exports = (app) => {
@ -10,5 +10,5 @@ module.exports = (app) => {
app.get('/popular', handlePageRequest);
app.get('/new', handlePageRequest);
app.get('/multisite', handlePageRequest);
app.get('/embed/:claimId/:name', handleEmbedRequest); // route to send embedable video player (for twitter)
app.get('/video-embed/:name/:claimId', handleVideoEmbedRequest); // for twitter
};

21
test/test.html Normal file
View file

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<img src="https://media.giphy.com/media/vwEHGjx71HSytx5mY8/giphy-facebook_s.jpg" alt="test embed"/>
<p>no identifier, no ending</p>
<img src="https://dev1.spee.ch/typingcat" alt="no identifier, no ending"/>
<p>no identifier, yes ending</p>
<img src="https://dev1.spee.ch/typingcat.gif" alt="no identifier, yes ending"/>
<p>yes identifier, no ending</p>
<img src="https://dev1.spee.ch/8/typingcat" alt="yes identifier, no ending"/>
<p>yes identifier, yes ending</p>
<img src="https://dev1.spee.ch/8/typingcat.gif" alt="yes identifier, yes ending"/>
</body>
</html>