Merge branch 'master' into dependencies-upgrade
This commit is contained in:
commit
1757d586cb
38 changed files with 719 additions and 328 deletions
38
client/build/components/AssetShareButtons/index.js
Normal file
38
client/build/components/AssetShareButtons/index.js
Normal 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;
|
82
client/build/components/ClickToCopy/index.js
Normal file
82
client/build/components/ClickToCopy/index.js
Normal 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;
|
|
@ -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",
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}]);
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
@import 'containers/_dropzone';
|
||||
@import 'containers/_publish-url-input';
|
||||
@import 'containers/_publish-status';
|
||||
@import 'containers/_publish-disabled-message';
|
||||
|
||||
@import '_media-queries';
|
||||
|
||||
|
|
11
client/scss/containers/_publish-disabled-message.scss
Normal file
11
client/scss/containers/_publish-disabled-message.scss
Normal 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;
|
||||
}
|
||||
}
|
39
client/src/components/AssetShareButtons/index.js
Normal file
39
client/src/components/AssetShareButtons/index.js
Normal 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;
|
34
client/src/components/ClickToCopy/index.jsx
Normal file
34
client/src/components/ClickToCopy/index.jsx
Normal 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;
|
|
@ -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>
|
||||
|
|
|
@ -4,10 +4,12 @@ class PublishDisabledMessage extends React.Component {
|
|||
render () {
|
||||
const message = this.props.message;
|
||||
return (
|
||||
<div>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
|
|
63
index.js
63
index.js
|
@ -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);
|
||||
}
|
||||
|
|
22
server/controllers/api/blocked/index.js
Normal file
22
server/controllers/api/blocked/index.js
Normal 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;
|
|
@ -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;
|
|
@ -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});
|
||||
|
|
|
@ -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
|
||||
|
|
29
server/controllers/api/config/site/publishing/index.js
Normal file
29
server/controllers/api/config/site/publishing/index.js
Normal 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;
|
25
server/controllers/api/tor/index.js
Normal file
25
server/controllers/api/tor/index.js
Normal 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;
|
9
server/controllers/assets/constants/request_types.js
Normal file
9
server/controllers/assets/constants/request_types.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const EMBED = 'EMBED';
|
||||
const BROWSER = 'BROWSER';
|
||||
const SOCIAL = 'SOCIAL';
|
||||
|
||||
module.exports = {
|
||||
EMBED,
|
||||
BROWSER,
|
||||
SOCIAL,
|
||||
};
|
17
server/controllers/assets/serveAsset/index.js
Normal file
17
server/controllers/assets/serveAsset/index.js
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
60
server/controllers/assets/utils/determineRequestType.js
Normal file
60
server/controllers/assets/utils/determineRequestType.js
Normal 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;
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
|
@ -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());
|
||||
|
|
31
server/middleware/torCheckMiddleware.js
Normal file
31
server/middleware/torCheckMiddleware.js
Normal 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;
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
59
server/models/tor.js
Normal 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;
|
||||
};
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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
21
test/test.html
Normal 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>
|
Loading…
Reference in a new issue