merge
This commit is contained in:
commit
259eb31b40
79 changed files with 1278 additions and 431 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -6,17 +6,11 @@ node_modules
|
|||
|
||||
client/build
|
||||
|
||||
client_custom/build
|
||||
client_custom/scss
|
||||
client_custom/src/components
|
||||
client_custom/src/containers
|
||||
client_custom/src/pages
|
||||
site/
|
||||
|
||||
devConfig/sequelizeCliConfig.js
|
||||
devConfig/testingConfig.js
|
||||
|
||||
config/
|
||||
|
||||
public/bundle/bundle.js
|
||||
public/bundle/bundle.js.map
|
||||
public/bundle/Lekton-*
|
||||
|
|
|
@ -155,9 +155,9 @@ Spee.ch also runs a sync tool, which decodes blocks from the `LBRY` blockchain a
|
|||
* `client/scss/` contains the CSS for the project
|
||||
*
|
||||
|
||||
* `client_custom` is a folder which can be used to override the default components in `client/`
|
||||
* `config/custom` is a folder which can be used to override the default components in `client/`
|
||||
* The folder structure mimics that of the `client/` folder
|
||||
* to customize spee.ch, place your own components and scss in the `client_custom/src/` and `client_custom/scss` folders.
|
||||
* to customize spee.ch, place your own components and scss in the `config/custom/src/` and `config/custom/scss` folders.
|
||||
|
||||
* `server/` contains all of the server code
|
||||
* `index.js` is the entry point for the server. It creates the [express app](https://expressjs.com/), requires the routes, syncs the database, and starts the server listening on the `PORT` designated in the config files.
|
||||
|
|
|
@ -13,16 +13,16 @@ let thumbnailChannel = '';
|
|||
let thumbnailChannelId = '';
|
||||
|
||||
const createConfigFile = (fileName, configObject) => { // siteConfig.json , siteConfig
|
||||
const fileLocation = Path.resolve(__dirname, `../config/${fileName}`);
|
||||
const fileLocation = Path.resolve(__dirname, `../site/config/${fileName}`);
|
||||
const fileContents = JSON.stringify(configObject, null, 2);
|
||||
fs.writeFileSync(fileLocation, fileContents, 'utf-8');
|
||||
console.log(`Successfully created /config/${fileName}\n`);
|
||||
console.log(`Successfully created ./site/config/${fileName}\n`);
|
||||
};
|
||||
|
||||
// import existing configs or import the defaults
|
||||
let mysqlConfig;
|
||||
try {
|
||||
mysqlConfig = require('../config/mysqlConfig.json');
|
||||
mysqlConfig = require('../site/config/mysqlConfig.json');
|
||||
} catch (error) {
|
||||
mysqlConfig = require('./defaults/mysqlConfig.json');
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ const { database: mysqlDatabase, username: mysqlUsername, password: mysqlPasswor
|
|||
|
||||
let siteConfig;
|
||||
try {
|
||||
siteConfig = require('../config/siteConfig.json');
|
||||
siteConfig = require('../site/config/siteConfig.json');
|
||||
} catch (error) {
|
||||
siteConfig = require('./defaults/siteConfig.json');
|
||||
}
|
||||
|
@ -39,30 +39,30 @@ const {
|
|||
port,
|
||||
title,
|
||||
host,
|
||||
channelClaimBidAmount: channelBid,
|
||||
},
|
||||
publishing: {
|
||||
uploadDirectory,
|
||||
channelClaimBidAmount: channelBid,
|
||||
},
|
||||
} = siteConfig;
|
||||
|
||||
let lbryConfig;
|
||||
try {
|
||||
lbryConfig = require('../config/lbryConfig.json');
|
||||
lbryConfig = require('../site/config/lbryConfig.json');
|
||||
} catch (error) {
|
||||
lbryConfig = require('./defaults/lbryConfig.json');
|
||||
}
|
||||
|
||||
let loggerConfig;
|
||||
try {
|
||||
loggerConfig = require('../config/loggerConfig.json');
|
||||
loggerConfig = require('../site/config/loggerConfig.json');
|
||||
} catch (error) {
|
||||
loggerConfig = require('./defaults/loggerConfig.json');
|
||||
}
|
||||
|
||||
let slackConfig;
|
||||
try {
|
||||
slackConfig = require('../config/slackConfig.json');
|
||||
slackConfig = require('../site/config/slackConfig.json');
|
||||
} catch (error) {
|
||||
slackConfig = require('./defaults/slackConfig.json');
|
||||
}
|
||||
|
|
|
@ -14,6 +14,19 @@ export function clearFile () {
|
|||
};
|
||||
}
|
||||
|
||||
export function setUpdateTrue () {
|
||||
return {
|
||||
type: actions.SET_UPDATE_TRUE,
|
||||
};
|
||||
}
|
||||
|
||||
export function setHasChanged (status) {
|
||||
return {
|
||||
type: actions.SET_HAS_CHANGED,
|
||||
data: status,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateMetadata (name, value) {
|
||||
return {
|
||||
type: actions.METADATA_UPDATE,
|
||||
|
@ -31,6 +44,13 @@ export function updateClaim (value) {
|
|||
};
|
||||
};
|
||||
|
||||
export function abandonClaim (data) {
|
||||
return {
|
||||
type: actions.ABANDON_CLAIM,
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
export function setPublishInChannel (channel) {
|
||||
return {
|
||||
type: actions.SET_PUBLISH_IN_CHANNEL,
|
||||
|
|
|
@ -105,6 +105,13 @@ export function updateAssetViewsInList (id, claimId, claimViews) {
|
|||
};
|
||||
}
|
||||
|
||||
export function removeAsset (data) {
|
||||
return {
|
||||
type: actions.ASSET_REMOVE,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
// channel actions
|
||||
|
||||
export function addNewChannelToChannelList (id, name, shortId, longId, claimsData) {
|
||||
|
@ -129,7 +136,7 @@ export function onUpdateChannelClaims (channelKey, name, longId, page) {
|
|||
|
||||
export function updateChannelClaims (channelListId, claimsData) {
|
||||
return {
|
||||
type: actions.CHANNEL_CLAIMS_UPDATE_SUCCESS,
|
||||
type: actions.CHANNEL_CLAIMS_UPDATE_SUCCEEDED,
|
||||
data: {channelListId, claimsData},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -42,3 +42,15 @@ export function getClaimViews (claimId) {
|
|||
const url = `/api/claim/views/${claimId}`;
|
||||
return Request(url);
|
||||
}
|
||||
|
||||
export function doAbandonClaim (claimId) {
|
||||
const params = {
|
||||
method : 'POST',
|
||||
body : JSON.stringify({claimId}),
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
credentials: 'include',
|
||||
};
|
||||
return Request('/api/claim/abandon', params);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import ContentPageWrapper from '@pages/ContentPageWrapper';
|
|||
import FourOhFourPage from '@pages/FourOhFourPage';
|
||||
import MultisitePage from '@pages/MultisitePage';
|
||||
import PopularPage from '@pages/PopularPage';
|
||||
import EditPage from '@pages/EditPage';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
|
@ -21,6 +22,7 @@ const App = () => {
|
|||
<Route exact path='/login' component={LoginPage} />
|
||||
<Route exact path='/multisite' component={MultisitePage} />
|
||||
<Route exact path='/popular' component={PopularPage} />
|
||||
<Route exact path='/edit/:identifier/:claim' component={EditPage} />
|
||||
<Route exact path='/:identifier/:claim' component={ContentPageWrapper} />
|
||||
<Route exact path='/:claim' component={ContentPageWrapper} />
|
||||
<Route component={FourOhFourPage} />
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {buffers, END, eventChannel} from 'redux-saga';
|
||||
|
||||
export const makePublishRequestChannel = (fd) => {
|
||||
export const makePublishRequestChannel = (fd, isUpdate) => {
|
||||
return eventChannel(emitter => {
|
||||
const uri = '/api/claim/publish';
|
||||
const uri = `/api/claim/${isUpdate ? 'update' : 'publish'}`;
|
||||
const xhr = new XMLHttpRequest();
|
||||
// add event listeners
|
||||
const onLoadStart = () => {
|
||||
|
|
|
@ -2,11 +2,14 @@ import React from 'react';
|
|||
import FormFeedbackDisplay from '@components/FormFeedbackDisplay';
|
||||
import Row from '@components/Row';
|
||||
|
||||
const DropzoneInstructionsDisplay = ({fileError}) => {
|
||||
const DropzoneInstructionsDisplay = ({fileError, message}) => {
|
||||
if (!message) {
|
||||
message = 'Drag & drop image or video here to publish';
|
||||
}
|
||||
return (
|
||||
<div className={'dropzone-instructions-display'}>
|
||||
<Row>
|
||||
<span className={'text--large'}>Drag & drop image or video here to publish</span>
|
||||
<span className={'text--large'}>{message}</span>
|
||||
</Row>
|
||||
<Row>
|
||||
<span className={'text--small text--secondary'}>OR</span>
|
||||
|
|
|
@ -10,7 +10,12 @@ class PublishPreview extends React.Component {
|
|||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
this.setPreviewImageSource(this.props.file);
|
||||
const { isUpdate, sourceUrl, file } = this.props;
|
||||
if (isUpdate && sourceUrl) {
|
||||
this.setState({ imgSource: sourceUrl });
|
||||
} else {
|
||||
this.setPreviewImageSource(file);
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps (newProps) {
|
||||
if (newProps.file !== this.props.file) {
|
||||
|
@ -54,8 +59,10 @@ class PublishPreview extends React.Component {
|
|||
|
||||
PublishPreview.propTypes = {
|
||||
dimPreview: PropTypes.bool.isRequired,
|
||||
file : PropTypes.object.isRequired,
|
||||
file : PropTypes.object,
|
||||
thumbnail : PropTypes.object,
|
||||
isUpdate : PropTypes.bool,
|
||||
sourceUrl : PropTypes.string,
|
||||
};
|
||||
|
||||
export default PublishPreview;
|
||||
|
|
|
@ -9,10 +9,12 @@ import Row from '@components/Row';
|
|||
|
||||
class PublishPreview extends React.Component {
|
||||
render () {
|
||||
const { isUpdate, uri } = this.props;
|
||||
return (
|
||||
<div className={'publish-form'}>
|
||||
<div className={'publish-form__title'}>
|
||||
<Row>
|
||||
{isUpdate && uri && (<p className='text--secondary'>{`Editing ${uri}`}</p>)}
|
||||
<PublishTitleInput />
|
||||
</Row>
|
||||
</div>
|
||||
|
|
1
client/src/constants/confirmation_messages.js
Normal file
1
client/src/constants/confirmation_messages.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const SAVE = 'Everything not saved will be lost. Are you sure you want to leave this page?';
|
|
@ -10,3 +10,6 @@ export const TOGGLE_METADATA_INPUTS = 'TOGGLE_METADATA_INPUTS';
|
|||
export const THUMBNAIL_NEW = 'THUMBNAIL_NEW';
|
||||
export const PUBLISH_START = 'PUBLISH_START';
|
||||
export const CLAIM_AVAILABILITY = 'CLAIM_AVAILABILITY';
|
||||
export const SET_UPDATE_TRUE = 'SET_UPDATE_TRUE';
|
||||
export const ABANDON_CLAIM = 'ABANDON_CLAIM';
|
||||
export const SET_HAS_CHANGED = 'SET_HAS_CHANGED';
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export const LOAD_START = 'LOAD_START';
|
||||
export const LOADING = 'LOADING';
|
||||
export const PUBLISHING = 'PUBLISHING';
|
||||
export const SUCCESS = 'SUCCESS';
|
||||
export const SUCCEEDED = 'SUCCEEDED';
|
||||
export const FAILED = 'FAILED';
|
||||
export const ABANDONING = 'ABANDONING';
|
||||
|
|
|
@ -11,12 +11,14 @@ export const REQUEST_LIST_ADD = 'REQUEST_LIST_ADD';
|
|||
// asset actions
|
||||
export const ASSET_ADD = 'ASSET_ADD';
|
||||
export const ASSET_VIEWS_UPDATE = 'ASSET_VIEWS_UPDATE';
|
||||
export const ASSET_UPDATE_CLAIMDATA = 'ASSET_UPDATE_CLAIMDATA';
|
||||
export const ASSET_REMOVE = 'ASSET_REMOVE';
|
||||
|
||||
// channel actions
|
||||
export const CHANNEL_ADD = 'CHANNEL_ADD';
|
||||
|
||||
export const CHANNEL_CLAIMS_UPDATE_ASYNC = 'CHANNEL_CLAIMS_UPDATE_ASYNC';
|
||||
export const CHANNEL_CLAIMS_UPDATE_SUCCESS = 'CHANNEL_CLAIMS_UPDATE_SUCCESS';
|
||||
export const CHANNEL_CLAIMS_UPDATE_SUCCEEDED = 'CHANNEL_CLAIMS_UPDATE_SUCCEEDED';
|
||||
|
||||
// asset/file display actions
|
||||
export const FILE_REQUESTED = 'FILE_REQUESTED';
|
||||
|
|
|
@ -3,7 +3,8 @@ import View from './view';
|
|||
import { fileRequested } from '../../actions/show';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
|
||||
const mapStateToProps = ({ show }) => {
|
||||
const mapStateToProps = (props) => {
|
||||
const {show} = props;
|
||||
// select error and status
|
||||
const error = show.displayAsset.error;
|
||||
const status = show.displayAsset.status;
|
||||
|
|
|
@ -3,6 +3,42 @@ import Row from '@components/Row';
|
|||
import AssetTitle from '@containers/AssetTitle';
|
||||
import ProgressBar from '@components/ProgressBar';
|
||||
import { LOCAL_CHECK, UNAVAILABLE, ERROR, AVAILABLE } from '../../constants/asset_display_states';
|
||||
import createCanonicalLink from '../../../../utils/createCanonicalLink';
|
||||
|
||||
class AvailableContent extends React.Component {
|
||||
render () {
|
||||
const {contentType, sourceUrl, name, thumbnail} = this.props;
|
||||
switch (contentType) {
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
case 'image/png':
|
||||
case 'image/gif':
|
||||
return (
|
||||
<img
|
||||
className='asset-image'
|
||||
src={sourceUrl}
|
||||
alt={name}
|
||||
/>
|
||||
);
|
||||
case 'video/mp4':
|
||||
return (
|
||||
<video
|
||||
className='asset-video'
|
||||
controls poster={thumbnail}
|
||||
>
|
||||
<source
|
||||
src={sourceUrl}
|
||||
/>
|
||||
<p>Your browser does not support the <code>video</code> element.</p>
|
||||
</video>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<p>Unsupported content type</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AssetDisplay extends React.Component {
|
||||
componentDidMount () {
|
||||
|
@ -10,10 +46,18 @@ class AssetDisplay extends React.Component {
|
|||
this.props.onFileRequest(name, claimId);
|
||||
}
|
||||
render () {
|
||||
const { status, error, asset: { claimData: { name, claimId, contentType, fileExt, thumbnail } } } = this.props;
|
||||
const sourceUrl = `/${claimId}/${name}.${fileExt}`;
|
||||
const { status, error, asset } = this.props;
|
||||
const { name, claimData: { claimId, contentType, thumbnail, outpoint } } = asset;
|
||||
// the outpoint is added to force the browser to re-download the asset after an update
|
||||
// issue: https://github.com/lbryio/spee.ch/issues/607
|
||||
let fileExt;
|
||||
if (typeof contentType === 'string') {
|
||||
fileExt = contentType.split('/')[1] || 'jpg';
|
||||
}
|
||||
const sourceUrl = `${createCanonicalLink({ asset: asset.claimData })}.${fileExt}?${outpoint}`;
|
||||
return (
|
||||
<div className={'asset-display-wrap'}>
|
||||
<div className={'asset-display'}>
|
||||
<div className={'asset-display'}>
|
||||
{(status === LOCAL_CHECK) &&
|
||||
<div>
|
||||
|
@ -38,38 +82,12 @@ class AssetDisplay extends React.Component {
|
|||
</div>
|
||||
}
|
||||
{(status === AVAILABLE) &&
|
||||
(() => {
|
||||
switch (contentType) {
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
case 'image/png':
|
||||
case 'image/gif':
|
||||
return (
|
||||
<img
|
||||
className='asset-image'
|
||||
src={sourceUrl}
|
||||
alt={name}
|
||||
<AvailableContent
|
||||
contentType={contentType}
|
||||
sourceUrl={sourceUrl}
|
||||
name={name}
|
||||
thumbnail={thumbnail}
|
||||
/>
|
||||
);
|
||||
case 'video/mp4':
|
||||
return (
|
||||
<video
|
||||
className='asset-video'
|
||||
controls poster={thumbnail}
|
||||
>
|
||||
<source
|
||||
src={sourceUrl}
|
||||
alt={name}
|
||||
/>
|
||||
<p>Your browser does not support the <code>video</code> element.</p>
|
||||
</video>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<p>Unsupported content type</p>
|
||||
);
|
||||
}
|
||||
})()
|
||||
}
|
||||
</div>
|
||||
<AssetTitle />
|
||||
|
|
|
@ -2,12 +2,20 @@ import { connect } from 'react-redux';
|
|||
import View from './view';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
|
||||
const mapStateToProps = ({ show }) => {
|
||||
const mapStateToProps = (props) => {
|
||||
const {show} = props;
|
||||
// select asset
|
||||
const asset = selectAsset(show);
|
||||
const editable = Boolean(
|
||||
asset &&
|
||||
asset.claimData &&
|
||||
asset.claimData.channelName &&
|
||||
props.channel.loggedInChannel.name === asset.claimData.channelName
|
||||
);
|
||||
// return props
|
||||
return {
|
||||
asset,
|
||||
editable,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -15,10 +15,11 @@ import AssetInfoFooter from '../../components/AssetInfoFooter/index';
|
|||
|
||||
class AssetInfo extends React.Component {
|
||||
render () {
|
||||
const { asset } = this.props;
|
||||
const { claimViews, claimData: { channelName, channelShortId, description, name, fileExt, contentType, thumbnail, host } } = asset;
|
||||
const { editable, asset } = this.props;
|
||||
const { claimViews, claimData } = asset;
|
||||
const { channelName, claimId, channelShortId, description, name, fileExt, contentType, host } = claimData;
|
||||
|
||||
const canonicalUrl = createCanonicalLink({ asset: { ...asset.claimData, shortId: asset.shortId }});
|
||||
const canonicalUrl = createCanonicalLink({ asset: { ...claimData, shortId: asset.shortId }});
|
||||
const assetCanonicalUrl = `${host}${canonicalUrl}`;
|
||||
|
||||
let channelCanonicalUrl;
|
||||
|
@ -39,6 +40,12 @@ class AssetInfo extends React.Component {
|
|||
}
|
||||
rightSide={
|
||||
<div>
|
||||
{editable && (
|
||||
<RowLabeled
|
||||
label={<Label value={'Edit:'} />}
|
||||
content={<Link to={`/edit${canonicalUrl}`}>{name}</Link>}
|
||||
/>
|
||||
)}
|
||||
{channelName && (
|
||||
<RowLabeled
|
||||
label={
|
||||
|
@ -51,7 +58,6 @@ class AssetInfo extends React.Component {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{claimViews ? (
|
||||
<RowLabeled
|
||||
label={
|
||||
|
|
|
@ -2,8 +2,8 @@ import { connect } from 'react-redux';
|
|||
import View from './view';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
|
||||
const mapStateToProps = ({ show }) => {
|
||||
const { claimData: { title } } = selectAsset(show);
|
||||
const mapStateToProps = (props) => {
|
||||
const { claimData: { title } } = selectAsset(props.show);
|
||||
return {
|
||||
title,
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@ class ChannelClaimsDisplay extends React.Component {
|
|||
<AssetPreview
|
||||
defaultThumbnail={defaultThumbnail}
|
||||
claimData={claim}
|
||||
key={`${claim.name}-${claim.id}`}
|
||||
key={claim.claimId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectFile, updateError, clearFile } from '../../actions/publish';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
import View from './view';
|
||||
import siteConfig from '@config/siteConfig.json';
|
||||
import createCanonicalLink from '../../../../utils/createCanonicalLink';
|
||||
|
||||
const mapStateToProps = ({ publish }) => {
|
||||
return {
|
||||
file : publish.file,
|
||||
thumbnail: publish.thumbnail,
|
||||
fileError: publish.error.file,
|
||||
};
|
||||
const { assetDefaults: { thumbnail: defaultThumbnail } } = siteConfig;
|
||||
|
||||
const mapStateToProps = ({ show, publish: { file, thumbnail, fileError, isUpdate } }) => {
|
||||
const obj = { file, thumbnail, fileError, isUpdate };
|
||||
let asset, name, claimId, fileExt, outpoint, sourceUrl;
|
||||
if (isUpdate) {
|
||||
asset = selectAsset(show);
|
||||
const { claimData } = asset;
|
||||
if (asset) {
|
||||
obj.fileExt = claimData.contentType.split('/')[1];
|
||||
if (obj.fileExt === 'mp4') {
|
||||
obj.sourceUrl = claimData.thumbnail ? claimData.thumbnail : defaultThumbnail;
|
||||
} else {
|
||||
({fileExt, outpoint} = claimData);
|
||||
obj.sourceUrl = `${createCanonicalLink({ asset: claimData })}.${fileExt}?${outpoint}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
|
|
|
@ -81,7 +81,13 @@ class Dropzone extends React.Component {
|
|||
}
|
||||
}
|
||||
render () {
|
||||
const { dragOver, mouseOver, dimPreview } = this.state;
|
||||
const { file, thumbnail, fileError, isUpdate, sourceUrl, fileExt } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{isUpdate && fileExt === 'mp4' ? (
|
||||
<p>Video updates are currently disabled. This feature will be available soon. You can edit metadata.</p>
|
||||
) : (
|
||||
<div className='dropzone-wrapper'>
|
||||
<form>
|
||||
<input
|
||||
|
@ -95,7 +101,7 @@ class Dropzone extends React.Component {
|
|||
/>
|
||||
</form>
|
||||
<div
|
||||
className={'dropzone' + (this.state.dragOver ? ' dropzone--drag-over' : '')}
|
||||
className={'dropzone' + (dragOver ? ' dropzone--drag-over' : '')}
|
||||
onDrop={this.handleDrop}
|
||||
onDragOver={this.handleDragOver}
|
||||
onDragEnd={this.handleDragEnd}
|
||||
|
@ -104,31 +110,42 @@ class Dropzone extends React.Component {
|
|||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onClick={this.handleClick}>
|
||||
{this.props.file ? (
|
||||
{file || isUpdate ? (
|
||||
<div className={'dropzone-preview-wrapper'}>
|
||||
{file ? (
|
||||
<DropzonePreviewImage
|
||||
dimPreview={this.state.dimPreview}
|
||||
file={this.props.file}
|
||||
thumbnail={this.props.thumbnail}
|
||||
dimPreview={dimPreview}
|
||||
file={file}
|
||||
thumbnail={thumbnail}
|
||||
/>
|
||||
) : (
|
||||
<DropzonePreviewImage
|
||||
dimPreview
|
||||
isUpdate
|
||||
sourceUrl={sourceUrl}
|
||||
/>
|
||||
)}
|
||||
<div className={'dropzone-preview-overlay'}>
|
||||
{ this.state.dragOver ? <DropzoneDropItDisplay /> : null }
|
||||
{ this.state.mouseOver ? (
|
||||
{ dragOver ? <DropzoneDropItDisplay /> : null }
|
||||
{ mouseOver ? (
|
||||
<DropzoneInstructionsDisplay
|
||||
fileError={this.props.fileError}
|
||||
fileError={fileError}
|
||||
message={fileExt === 'mp4' ? 'Drag & drop new thumbnail' : null}
|
||||
/>
|
||||
) : null }
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
this.state.dragOver ? <DropzoneDropItDisplay /> : (
|
||||
dragOver ? <DropzoneDropItDisplay /> : (
|
||||
<DropzoneInstructionsDisplay
|
||||
fileError={this.props.fileError}
|
||||
fileError={fileError}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { clearFile, startPublish } from '../../actions/publish';
|
||||
import { clearFile, startPublish, abandonClaim } from '../../actions/publish';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
import View from './view';
|
||||
|
||||
const mapStateToProps = ({ channel, publish }) => {
|
||||
const mapStateToProps = ({ show, publish }) => {
|
||||
return {
|
||||
file: publish.file,
|
||||
isUpdate: publish.isUpdate,
|
||||
hasChanged: publish.hasChanged,
|
||||
asset: selectAsset(show),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
clearFile,
|
||||
startPublish,
|
||||
abandonClaim,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
||||
|
|
|
@ -1,26 +1,64 @@
|
|||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import {Link, withRouter} from 'react-router-dom';
|
||||
import PublishUrlInput from '@containers/PublishUrlInput';
|
||||
import PublishThumbnailInput from '@containers/PublishThumbnailInput';
|
||||
import PublishMetadataInputs from '@containers/PublishMetadataInputs';
|
||||
import ChannelSelect from '@containers/ChannelSelect';
|
||||
import Row from '@components/Row';
|
||||
import Label from '@components/Label';
|
||||
import RowLabeled from '@components/RowLabeled';
|
||||
import ButtonPrimaryJumbo from '@components/ButtonPrimaryJumbo';
|
||||
import ButtonSecondary from '@components/ButtonSecondary';
|
||||
import SpaceAround from '@components/SpaceAround';
|
||||
import PublishFinePrint from '@components/PublishFinePrint';
|
||||
import { SAVE } from '../../constants/confirmation_messages';
|
||||
|
||||
class PublishDetails extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.onPublishSubmit = this.onPublishSubmit.bind(this);
|
||||
this.abandonClaim = this.abandonClaim.bind(this);
|
||||
this.onCancel = this.onCancel.bind(this);
|
||||
}
|
||||
onPublishSubmit () {
|
||||
this.props.startPublish(this.props.history);
|
||||
}
|
||||
abandonClaim () {
|
||||
const {asset, history} = this.props;
|
||||
if (asset) {
|
||||
const {claimData} = asset;
|
||||
this.props.abandonClaim({claimData, history});
|
||||
}
|
||||
}
|
||||
onCancel () {
|
||||
const { isUpdate, hasChanged, clearFile, history } = this.props;
|
||||
if (isUpdate || !hasChanged) {
|
||||
history.push('/');
|
||||
} else {
|
||||
if (confirm(SAVE)) {
|
||||
clearFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
render () {
|
||||
const {file, isUpdate, asset} = this.props;
|
||||
return (
|
||||
<div>
|
||||
{isUpdate ? (asset && (
|
||||
<Row>
|
||||
<RowLabeled
|
||||
label={
|
||||
<Label value={'Channel:'} />
|
||||
}
|
||||
content={
|
||||
<span className='text'>
|
||||
{asset.claimData.channelName}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Row>
|
||||
)) : (
|
||||
<React.Fragment>
|
||||
<Row>
|
||||
<PublishUrlInput />
|
||||
</Row>
|
||||
|
@ -28,8 +66,10 @@ class PublishDetails extends React.Component {
|
|||
<Row>
|
||||
<ChannelSelect />
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{ this.props.file.type === 'video/mp4' && (
|
||||
{ file && file.type === 'video/mp4' && (
|
||||
<Row>
|
||||
<PublishThumbnailInput />
|
||||
</Row>
|
||||
|
@ -41,16 +81,27 @@ class PublishDetails extends React.Component {
|
|||
|
||||
<Row>
|
||||
<ButtonPrimaryJumbo
|
||||
value={'Publish'}
|
||||
value={isUpdate ? 'Update' : 'Publish'}
|
||||
onClickHandler={this.onPublishSubmit}
|
||||
/>
|
||||
</Row>
|
||||
|
||||
{isUpdate && (
|
||||
<Row>
|
||||
<SpaceAround>
|
||||
<ButtonSecondary
|
||||
value={'Abandon Claim'}
|
||||
onClickHandler={this.abandonClaim}
|
||||
/>
|
||||
</SpaceAround>
|
||||
</Row>
|
||||
)}
|
||||
|
||||
<Row>
|
||||
<SpaceAround>
|
||||
<ButtonSecondary
|
||||
value={'Cancel'}
|
||||
onClickHandler={this.props.clearFile}
|
||||
onClickHandler={this.onCancel}
|
||||
/>
|
||||
</SpaceAround>
|
||||
</Row>
|
||||
|
|
|
@ -8,6 +8,7 @@ const mapStateToProps = ({ publish }) => {
|
|||
description : publish.metadata.description,
|
||||
license : publish.metadata.license,
|
||||
nsfw : publish.metadata.nsfw,
|
||||
isUpdate : publish.isUpdate,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -27,9 +27,10 @@ class PublishMetadataInputs extends React.Component {
|
|||
this.props.onMetadataChange(name, selectedOption);
|
||||
}
|
||||
render () {
|
||||
const { showMetadataInputs, description, isUpdate, nsfw } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{this.props.showMetadataInputs && (
|
||||
{(showMetadataInputs || isUpdate) && (
|
||||
<React.Fragment>
|
||||
<Row>
|
||||
<PublishDescriptionInput
|
||||
|
@ -50,10 +51,12 @@ class PublishMetadataInputs extends React.Component {
|
|||
</Row>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{!isUpdate && (
|
||||
<ButtonSecondary
|
||||
value={this.props.showMetadataInputs ? 'less' : 'more'}
|
||||
value={showMetadataInputs ? 'less' : 'more'}
|
||||
onClickHandler={this.toggleShowInputs}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ class PublishStatus extends React.Component {
|
|||
{status === publishStates.LOAD_START &&
|
||||
<div className={'status'}>
|
||||
<Row>
|
||||
<p>le is loading to server</p>
|
||||
<p>File is loading to server</p>
|
||||
</Row>
|
||||
<Row>
|
||||
<p className={'text--secondary'}>0%</p>
|
||||
|
@ -42,7 +42,7 @@ class PublishStatus extends React.Component {
|
|||
</Row>
|
||||
</div>
|
||||
}
|
||||
{status === publishStates.SUCCESS &&
|
||||
{status === publishStates.SUCCEEDED &&
|
||||
<div className={'status'}>
|
||||
<Row>
|
||||
<p>Your publish is complete! You are being redirected to it now.</p>
|
||||
|
@ -71,6 +71,13 @@ class PublishStatus extends React.Component {
|
|||
</Row>
|
||||
</div>
|
||||
}
|
||||
{status === publishStates.ABANDONING &&
|
||||
<div className={'status'}>
|
||||
<Row>
|
||||
<p>Your claim is being abandoned.</p>
|
||||
</Row>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
import {connect} from 'react-redux';
|
||||
import View from './view';
|
||||
import {selectAsset} from "../../selectors/show";
|
||||
import {buildURI} from "../../utils/buildURI";
|
||||
|
||||
const mapStateToProps = ({ publish }) => {
|
||||
const mapStateToProps = props => {
|
||||
const { show, publish } = props;
|
||||
const asset = selectAsset(show);
|
||||
let uri;
|
||||
if (asset) {
|
||||
uri = `lbry://${buildURI(asset)}`;
|
||||
}
|
||||
return {
|
||||
disabled: publish.disabled,
|
||||
file: publish.file,
|
||||
status: publish.status.status,
|
||||
isUpdate: publish.isUpdate,
|
||||
hasChanged: publish.hasChanged,
|
||||
uri,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,23 +1,34 @@
|
|||
import React from 'react';
|
||||
import { withRouter, Prompt } from 'react-router';
|
||||
import Dropzone from '@containers/Dropzone';
|
||||
import PublishPreview from '@components/PublishPreview';
|
||||
import PublishStatus from '@containers/PublishStatus';
|
||||
import PublishDisabledMessage from '@containers/PublishDisabledMessage';
|
||||
import { SAVE } from '../../constants/confirmation_messages';
|
||||
|
||||
class PublishTool extends React.Component {
|
||||
render () {
|
||||
if (this.props.disabled) {
|
||||
const {disabled, file, isUpdate, hasChanged, uri, status, location: currentLocation} = this.props;
|
||||
if (disabled) {
|
||||
return (
|
||||
<PublishDisabledMessage />
|
||||
);
|
||||
} else {
|
||||
if (this.props.file) {
|
||||
if (this.props.status) {
|
||||
if (file || isUpdate) {
|
||||
if (status) {
|
||||
return (
|
||||
<PublishStatus />
|
||||
);
|
||||
} else {
|
||||
return <PublishPreview />;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Prompt
|
||||
when={hasChanged}
|
||||
message={(location) => location.pathname === currentLocation.pathname ? false : SAVE}
|
||||
/>
|
||||
<PublishPreview isUpdate={isUpdate} uri={uri} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
return <Dropzone />;
|
||||
|
@ -25,4 +36,4 @@ class PublishTool extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
export default PublishTool;
|
||||
export default withRouter(PublishTool);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import PageLayout from '@components/PageLayout';
|
||||
import HorizontalSplit from '@components/HorizontalSplit';
|
||||
import AboutSpeechOverview from '@components/AboutSpeechOverview';
|
||||
|
@ -20,4 +21,4 @@ class AboutPage extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default AboutPage;
|
||||
export default withRouter(AboutPage);
|
||||
|
|
24
client/src/pages/EditPage/index.js
Normal file
24
client/src/pages/EditPage/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { setUpdateTrue, setHasChanged, updateMetadata, clearFile } from '../../actions/publish';
|
||||
import { onHandleShowPageUri } from '../../actions/show';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
import View from './view';
|
||||
|
||||
const mapStateToProps = (props) => {
|
||||
const { show } = props;
|
||||
return {
|
||||
asset : selectAsset(show),
|
||||
myChannel: props.channel.loggedInChannel.name,
|
||||
isUpdate : props.publish.isUpdate,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
updateMetadata,
|
||||
onHandleShowPageUri,
|
||||
setUpdateTrue,
|
||||
setHasChanged,
|
||||
clearFile,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
43
client/src/pages/EditPage/view.jsx
Normal file
43
client/src/pages/EditPage/view.jsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import PageLayout from '@components/PageLayout';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import PublishTool from '@containers/PublishTool';
|
||||
|
||||
class EditPage extends React.Component {
|
||||
componentDidMount () {
|
||||
const {asset, match, onHandleShowPageUri, setUpdateTrue, setHasChanged, updateMetadata} = this.props;
|
||||
onHandleShowPageUri(match.params);
|
||||
setUpdateTrue();
|
||||
if (asset) {
|
||||
['title', 'description', 'license', 'nsfw'].forEach(meta => updateMetadata(meta, asset.claimData[meta]));
|
||||
}
|
||||
setHasChanged(false);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.props.clearFile();
|
||||
}
|
||||
render () {
|
||||
const { myChannel, asset } = this.props;
|
||||
// redirect if user does not own this claim
|
||||
if (
|
||||
!myChannel || (
|
||||
asset &&
|
||||
asset.claimsData &&
|
||||
asset.claimsData.channelName &&
|
||||
asset.claimsData.channelName !== myChannel
|
||||
)
|
||||
) {
|
||||
return (<Redirect to={'/'} />);
|
||||
}
|
||||
return (
|
||||
<PageLayout
|
||||
pageTitle={'Edit claim'}
|
||||
pageUri={'edit'}
|
||||
>
|
||||
<PublishTool />
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default EditPage;
|
|
@ -1,17 +1,20 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { onHandleShowHomepage } from '../../actions/show';
|
||||
import { clearFile } from '../../actions/publish';
|
||||
import View from './view';
|
||||
|
||||
const mapStateToProps = ({ show, site, channel }) => {
|
||||
const mapStateToProps = ({ show, site, channel, publish }) => {
|
||||
return {
|
||||
error : show.request.error,
|
||||
requestType: show.request.type,
|
||||
homeChannel: site.publishOnlyApproved && !channel.loggedInChannel.name ? `${site.approvedChannels[0].name}:${site.approvedChannels[0].longId}` : null,
|
||||
isUpdate : publish.isUpdate,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onHandleShowHomepage,
|
||||
clearFile,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
|
@ -4,16 +4,9 @@ import PublishTool from '@containers/PublishTool';
|
|||
import ContentPageWrapper from '@pages/ContentPageWrapper';
|
||||
|
||||
class HomePage extends React.Component {
|
||||
componentDidMount () {
|
||||
this.props.onHandleShowHomepage(this.props.match.params);
|
||||
componentWillUnmount () {
|
||||
this.props.clearFile();
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.match.params !== this.props.match.params) {
|
||||
this.props.onHandleShowHomepage(nextProps.match.params);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { homeChannel } = this.props;
|
||||
return homeChannel ? (
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
import View from './view';
|
||||
|
||||
const mapStateToProps = ({ show }) => {
|
||||
// select request info
|
||||
const requestId = show.request.id;
|
||||
// select asset info
|
||||
let asset;
|
||||
const request = show.requestList[requestId] || null;
|
||||
const assetList = show.assetList;
|
||||
if (request && assetList) {
|
||||
const assetKey = request.key; // note: just store this in the request
|
||||
asset = assetList[assetKey] || null;
|
||||
};
|
||||
// return props
|
||||
return {
|
||||
asset,
|
||||
asset: selectAsset(show),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from 'react';
|
||||
import PageLayout from '@components/PageLayout';
|
||||
|
||||
import VerticalCollapsibleSplit from '@components/VerticalCollapsibleSplit';
|
||||
import AssetDisplay from '@containers/AssetDisplay';
|
||||
import AssetInfo from '@containers/AssetInfo';
|
||||
import ErrorPage from '@pages/ErrorPage';
|
||||
|
||||
/*
|
||||
|
|
|
@ -41,6 +41,8 @@ const initialState = {
|
|||
license : '',
|
||||
nsfw : false,
|
||||
},
|
||||
isUpdate: false,
|
||||
hasChanged: false,
|
||||
thumbnail: null,
|
||||
thumbnailChannel,
|
||||
thumbnailChannelId,
|
||||
|
@ -49,8 +51,9 @@ const initialState = {
|
|||
export default function (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actions.FILE_SELECTED:
|
||||
return Object.assign({}, initialState, { // note: clears to initial state
|
||||
return Object.assign({}, state.isUpdate ? state : initialState, { // note: clears to initial state
|
||||
file: action.data,
|
||||
hasChanged: true,
|
||||
});
|
||||
case actions.FILE_CLEAR:
|
||||
return initialState;
|
||||
|
@ -59,14 +62,17 @@ export default function (state = initialState, action) {
|
|||
metadata: Object.assign({}, state.metadata, {
|
||||
[action.data.name]: action.data.value,
|
||||
}),
|
||||
hasChanged: true,
|
||||
});
|
||||
case actions.CLAIM_UPDATE:
|
||||
return Object.assign({}, state, {
|
||||
claim: action.data,
|
||||
hasChanged: true,
|
||||
});
|
||||
case actions.SET_PUBLISH_IN_CHANNEL:
|
||||
return Object.assign({}, state, {
|
||||
publishInChannel: action.channel,
|
||||
hasChanged: true,
|
||||
});
|
||||
case actions.PUBLISH_STATUS_UPDATE:
|
||||
return Object.assign({}, state, {
|
||||
|
@ -83,13 +89,26 @@ export default function (state = initialState, action) {
|
|||
selectedChannel: action.data,
|
||||
});
|
||||
case actions.TOGGLE_METADATA_INPUTS:
|
||||
return Object.assign({}, state, {
|
||||
return {
|
||||
...state,
|
||||
showMetadataInputs: action.data,
|
||||
});
|
||||
};
|
||||
case actions.THUMBNAIL_NEW:
|
||||
return Object.assign({}, state, {
|
||||
return {
|
||||
...state,
|
||||
thumbnail: action.data,
|
||||
});
|
||||
hasChanged: true,
|
||||
};
|
||||
case actions.SET_UPDATE_TRUE:
|
||||
return {
|
||||
...state,
|
||||
isUpdate: true,
|
||||
};
|
||||
case actions.SET_HAS_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
hasChanged: action.data,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -65,6 +65,43 @@ export default function (state = initialState, action) {
|
|||
},
|
||||
}),
|
||||
});
|
||||
case actions.ASSET_REMOVE:
|
||||
const claim = action.data;
|
||||
const newAssetList = state.assetList;
|
||||
delete newAssetList[`a#${claim.name}#${claim.claimId}`];
|
||||
|
||||
const channelId = `c#${claim.channelName}#${claim.certificateId}`;
|
||||
const channelClaims = state.channelList[channelId].claimsData.claims;
|
||||
const newClaimsData = channelClaims.filter(c => c.claimId !== claim.claimId);
|
||||
|
||||
return {
|
||||
...state,
|
||||
assetList : newAssetList,
|
||||
channelList: {
|
||||
...state.channelList,
|
||||
[channelId]: {
|
||||
...state.channelList[channelId],
|
||||
claimsData: {
|
||||
...state.channelList[channelId].claimsData,
|
||||
claims: newClaimsData,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
case actions.ASSET_UPDATE_CLAIMDATA:
|
||||
return {
|
||||
...state,
|
||||
assetList: {
|
||||
...state.assetList,
|
||||
[action.data.id]: {
|
||||
...state.assetList[action.data.id],
|
||||
claimData: {
|
||||
...state.assetList[action.data.id].claimData,
|
||||
...action.data.claimData,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
// channel data
|
||||
case actions.CHANNEL_ADD:
|
||||
return Object.assign({}, state, {
|
||||
|
@ -77,7 +114,7 @@ export default function (state = initialState, action) {
|
|||
},
|
||||
}),
|
||||
});
|
||||
case actions.CHANNEL_CLAIMS_UPDATE_SUCCESS:
|
||||
case actions.CHANNEL_CLAIMS_UPDATE_SUCCEEDED:
|
||||
return Object.assign({}, state, {
|
||||
channelList: Object.assign({}, state.channelList, {
|
||||
[action.data.channelListId]: Object.assign({}, state.channelList[action.data.channelListId], {
|
||||
|
|
30
client/src/sagas/abandon.js
Normal file
30
client/src/sagas/abandon.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||
import * as actions from '../constants/publish_action_types';
|
||||
import * as publishStates from '../constants/publish_claim_states';
|
||||
import { updatePublishStatus, clearFile } from '../actions/publish';
|
||||
import { removeAsset } from '../actions/show';
|
||||
import { doAbandonClaim } from '../api/assetApi';
|
||||
|
||||
function * abandonClaim (action) {
|
||||
const { claimData, history } = action.data;
|
||||
const { claimId } = claimData;
|
||||
|
||||
const confirm = window.confirm('Are you sure you want to abandon this claim? This action cannot be undone.');
|
||||
if (!confirm) return;
|
||||
|
||||
yield put(updatePublishStatus(publishStates.ABANDONING, 'Your claim is being abandoned...'));
|
||||
|
||||
try {
|
||||
yield call(doAbandonClaim, claimId);
|
||||
} catch (error) {
|
||||
return console.log('abandon error:', error.message);
|
||||
}
|
||||
|
||||
yield put(clearFile());
|
||||
yield put(removeAsset(claimData));
|
||||
return history.push('/');
|
||||
}
|
||||
|
||||
export function * watchAbandonClaim () {
|
||||
yield takeLatest(actions.ABANDON_CLAIM, abandonClaim);
|
||||
};
|
|
@ -5,37 +5,57 @@ import { updateError, updatePublishStatus, clearFile } from '../actions/publish'
|
|||
import { selectPublishState } from '../selectors/publish';
|
||||
import { selectChannelState } from '../selectors/channel';
|
||||
import { selectSiteState } from '../selectors/site';
|
||||
import { selectShowState, selectAsset } from '../selectors/show';
|
||||
import { validateChannelSelection, validateNoPublishErrors } from '../utils/validate';
|
||||
import { createPublishMetadata, createPublishFormData, createThumbnailUrl } from '../utils/publish';
|
||||
import { makePublishRequestChannel } from '../channels/publish';
|
||||
|
||||
function * publishFile (action) {
|
||||
const { history } = action.data;
|
||||
const { publishInChannel, selectedChannel, file, claim, metadata, thumbnailChannel, thumbnailChannelId, thumbnail, error: publishToolErrors } = yield select(selectPublishState);
|
||||
const publishState = yield select(selectPublishState);
|
||||
const { publishInChannel, selectedChannel, file, claim, metadata, thumbnailChannel, thumbnailChannelId, thumbnail, isUpdate, error: publishToolErrors } = publishState;
|
||||
const { loggedInChannel } = yield select(selectChannelState);
|
||||
const { host } = yield select(selectSiteState);
|
||||
|
||||
let show, asset;
|
||||
if (isUpdate) {
|
||||
show = yield select(selectShowState);
|
||||
asset = selectAsset(show);
|
||||
}
|
||||
// validate the channel selection
|
||||
try {
|
||||
validateChannelSelection(publishInChannel, selectedChannel, loggedInChannel);
|
||||
} catch (error) {
|
||||
return yield put(updateError('channel', error.message));
|
||||
};
|
||||
}
|
||||
// validate publish parameters
|
||||
try {
|
||||
validateNoPublishErrors(publishToolErrors);
|
||||
} catch (error) {
|
||||
return console.log('publish error:', error.message);
|
||||
}
|
||||
|
||||
let publishMetadata, publishFormData, publishChannel;
|
||||
// create metadata
|
||||
let publishMetadata = createPublishMetadata(claim, file, metadata, publishInChannel, selectedChannel);
|
||||
publishMetadata = createPublishMetadata(
|
||||
isUpdate ? asset.name : claim,
|
||||
isUpdate ? {type: asset.claimData.contentType} : file,
|
||||
metadata,
|
||||
publishInChannel,
|
||||
selectedChannel
|
||||
);
|
||||
if (isUpdate) {
|
||||
publishMetadata['channelName'] = asset.claimData.channelName;
|
||||
}
|
||||
if (thumbnail) {
|
||||
// add thumbnail to publish metadata
|
||||
publishMetadata['thumbnail'] = createThumbnailUrl(thumbnailChannel, thumbnailChannelId, claim, host);
|
||||
}
|
||||
// create form data for main publish
|
||||
const publishFormData = createPublishFormData(file, thumbnail, publishMetadata);
|
||||
publishFormData = createPublishFormData(file, thumbnail, publishMetadata);
|
||||
// make the publish request
|
||||
const publishChannel = yield call(makePublishRequestChannel, publishFormData);
|
||||
publishChannel = yield call(makePublishRequestChannel, publishFormData, isUpdate);
|
||||
|
||||
while (true) {
|
||||
const {loadStart, progress, load, success, error: publishError} = yield take(publishChannel);
|
||||
if (publishError) {
|
||||
|
@ -43,7 +63,21 @@ function * publishFile (action) {
|
|||
}
|
||||
if (success) {
|
||||
yield put(clearFile());
|
||||
return history.push(`/${success.data.claimId}/${success.data.name}`);
|
||||
if (isUpdate) {
|
||||
yield put({
|
||||
type: 'ASSET_UPDATE_CLAIMDATA',
|
||||
data: {
|
||||
id : `a#${success.data.name}#${success.data.claimId}`,
|
||||
claimData: success.data.claimData,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (success.data.claimId) {
|
||||
return history.push(success.data.pushTo);
|
||||
} else {
|
||||
// this returns to the homepage, needs work
|
||||
return yield put(updatePublishStatus(publishStates.FAILED, 'ERROR'));
|
||||
}
|
||||
}
|
||||
if (loadStart) {
|
||||
yield put(updatePublishStatus(publishStates.LOAD_START, null));
|
||||
|
@ -55,7 +89,7 @@ function * publishFile (action) {
|
|||
yield put(updatePublishStatus(publishStates.PUBLISHING, null));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function * watchPublishStart () {
|
||||
yield takeLatest(actions.PUBLISH_START, publishFile);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { watchUpdateChannelAvailability } from './updateChannelAvailability';
|
|||
import { watchChannelCreate } from './createChannel';
|
||||
import { watchChannelLoginCheck } from './checkForLoggedInChannel';
|
||||
import { watchChannelLogout } from './logoutChannel';
|
||||
import { watchAbandonClaim } from './abandon';
|
||||
|
||||
export function * rootSaga () {
|
||||
yield all([
|
||||
|
@ -27,5 +28,6 @@ export function * rootSaga () {
|
|||
watchChannelLoginCheck(),
|
||||
watchChannelLogout(),
|
||||
watchUpdateAssetViews(),
|
||||
watchAbandonClaim(),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
export const selectAsset = (show) => {
|
||||
const request = show.requestList[show.request.id];
|
||||
const assetKey = request.key;
|
||||
return show.assetList[assetKey];
|
||||
export const selectAsset = show => {
|
||||
const requestId = show.request.id;
|
||||
let asset;
|
||||
const request = show.requestList[requestId] || null;
|
||||
const assetList = show.assetList;
|
||||
if (request && assetList) {
|
||||
const assetKey = request.key; // note: just store this in the request
|
||||
asset = assetList[assetKey] || null;
|
||||
}
|
||||
return asset;
|
||||
};
|
||||
|
||||
export const selectShowState = (state) => {
|
||||
|
|
10
client/src/utils/buildURI.js
Normal file
10
client/src/utils/buildURI.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export const buildURI = asset => {
|
||||
let channelName, certificateId, name, claimId;
|
||||
if (asset.claimData) {
|
||||
({ channelName, certificateId, name, claimId } = asset.claimData);
|
||||
}
|
||||
if (channelName) {
|
||||
return `${channelName}:${certificateId}/${name}`;
|
||||
}
|
||||
return `${claimId}/${name}`;
|
||||
};
|
|
@ -16,7 +16,9 @@ export const createPublishMetadata = (claim, { type }, { title, description, lic
|
|||
export const createPublishFormData = (file, thumbnail, metadata) => {
|
||||
let fd = new FormData();
|
||||
// append file
|
||||
if (file) {
|
||||
fd.append('file', file);
|
||||
}
|
||||
// append thumbnail
|
||||
if (thumbnail) {
|
||||
fd.append('thumbnail', thumbnail);
|
||||
|
@ -31,5 +33,5 @@ export const createPublishFormData = (file, thumbnail, metadata) => {
|
|||
};
|
||||
|
||||
export const createThumbnailUrl = (channel, channelId, claim, host) => {
|
||||
return `${host}/${channel}:${channelId}/${claim}-thumb.png`;
|
||||
return `${host}/${channel}:${channelId}/${claim}-thumb.jpg`;
|
||||
};
|
||||
|
|
BIN
config/.siteConfig.json.swp
Normal file
BIN
config/.siteConfig.json.swp
Normal file
Binary file not shown.
8
config/chainqueryConfig.json
Normal file
8
config/chainqueryConfig.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"host": "chainquery.lbry.io",
|
||||
"port": "3306",
|
||||
"timeout": 30,
|
||||
"database": "chainquery",
|
||||
"username": "speechapi",
|
||||
"password": "1304486ca42ecdbf419f667fdc02c31e7d03b1e3"
|
||||
}
|
5
config/lbryConfig.json
Normal file
5
config/lbryConfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"apiHost": "localhost",
|
||||
"apiPort": "5279",
|
||||
"getTimeout": 30
|
||||
}
|
3
config/loggerConfig.json
Normal file
3
config/loggerConfig.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"logLevel": "verbose"
|
||||
}
|
5
config/mysqlConfig.json
Normal file
5
config/mysqlConfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"database": "lbry",
|
||||
"username": "root",
|
||||
"password": ""
|
||||
}
|
37
config/siteConfig.json
Normal file
37
config/siteConfig.json
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"analytics": {
|
||||
"googleId": null
|
||||
},
|
||||
"assetDefaults": {
|
||||
"title": "Default Content Title",
|
||||
"description": "Default Content Description",
|
||||
"thumbnail": "https://spee.ch/0e5d4e8f4086e13f5b9ca3f9648f518e5f524402/speechflag.png"
|
||||
},
|
||||
"auth": {
|
||||
"sessionKey": "mysecretkeyword",
|
||||
"masterPassword": "myMasterPassword"
|
||||
},
|
||||
"details": {
|
||||
"port": 3000,
|
||||
"title": "dev1",
|
||||
"ipAddress":"127.0.0.1",
|
||||
"host": "localhost",
|
||||
"description": "A decentralized hosting platform built on LBRY",
|
||||
"twitter": false
|
||||
},
|
||||
"publishing": {
|
||||
"primaryClaimAddress": "bYscURD34PVAfYY7FdyY1W4TjdwQYXrDkN",
|
||||
"uploadDirectory": "/home/lbry/Uploads",
|
||||
"thumbnailChannel": "@thumbnails",
|
||||
"thumbnailChannelId": "85d5f2fd814979dec6b082c2f88b07bc155e4e79",
|
||||
"additionalClaimAddresses": [],
|
||||
"disabled": false,
|
||||
"disabledMessage": "Default publishing disabled message",
|
||||
"channelClaimBidAmount": "0.1",
|
||||
"fileClaimBidAmount": "0.01"
|
||||
},
|
||||
"startup": {
|
||||
"performChecks": true,
|
||||
"performUpdates": true
|
||||
}
|
||||
}
|
5
config/slackConfig.json
Normal file
5
config/slackConfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"slackWebHook": false,
|
||||
"slackErrorChannel": false,
|
||||
"slackInfoChannel": false
|
||||
}
|
|
@ -3,14 +3,14 @@
|
|||
_note: this guide assumes you have done the []quickstart](https://github.com/lbryio/spee.ch/blob/readme-update/README.md) or [fullstart](https://github.com/lbryio/spee.ch/blob/readme-update/fullstart.md) guide and have a working spee.ch server_
|
||||
|
||||
## Custom Components
|
||||
The components used by spee.ch are taken from the `client/` folder, but you can override those components by defining your own in the `client_custom/` folder.
|
||||
The components used by spee.ch are taken from the `client/` folder, but you can override those components by defining your own in the `site/custom/` folder.
|
||||
|
||||
### Add a new custom Logo component.
|
||||
|
||||
To create your own custom component to override the defaults, create a folder and an `index.jsx` file for the component in the `client_custom/src/components/` folder.
|
||||
To create your own custom component to override the defaults, create a folder and an `index.jsx` file for the component in the `site/custom/src/components/` folder.
|
||||
|
||||
```
|
||||
$ cd client_custom/src/components/
|
||||
$ cd site/custom/src/components/
|
||||
$ mkdir Logo
|
||||
$ cd Logo
|
||||
$ touch index.jsx
|
||||
|
|
177
package-lock.json
generated
177
package-lock.json
generated
|
@ -1003,6 +1003,7 @@
|
|||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"co": "4.6.0",
|
||||
"fast-deep-equal": "1.1.0",
|
||||
|
@ -2005,14 +2006,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"boom": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
|
||||
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
|
||||
"requires": {
|
||||
"hoek": "4.2.1"
|
||||
}
|
||||
},
|
||||
"boxen": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
|
||||
|
@ -2711,7 +2704,8 @@
|
|||
"co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
|
||||
"dev": true
|
||||
},
|
||||
"coa": {
|
||||
"version": "1.0.4",
|
||||
|
@ -3035,24 +3029,6 @@
|
|||
"which": "1.3.1"
|
||||
}
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
|
||||
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
|
||||
"requires": {
|
||||
"boom": "5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"boom": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
|
||||
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
|
||||
"requires": {
|
||||
"hoek": "4.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"crypto-browserify": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||
|
@ -4556,7 +4532,8 @@
|
|||
"fast-deep-equal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
|
||||
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
|
||||
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-json-stable-stringify": {
|
||||
"version": "2.0.0",
|
||||
|
@ -5639,6 +5616,7 @@
|
|||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
||||
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "5.5.2",
|
||||
"har-schema": "2.0.0"
|
||||
|
@ -5733,17 +5711,6 @@
|
|||
"minimalistic-assert": "1.0.1"
|
||||
}
|
||||
},
|
||||
"hawk": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
|
||||
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
|
||||
"requires": {
|
||||
"boom": "4.3.1",
|
||||
"cryptiles": "3.1.2",
|
||||
"hoek": "4.2.1",
|
||||
"sntp": "2.1.0"
|
||||
}
|
||||
},
|
||||
"he": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
|
||||
|
@ -5823,11 +5790,6 @@
|
|||
"minimalistic-crypto-utils": "1.0.1"
|
||||
}
|
||||
},
|
||||
"hoek": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
|
||||
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA=="
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||
|
@ -6523,7 +6485,8 @@
|
|||
"json-schema-traverse": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
|
||||
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
|
||||
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
|
||||
"dev": true
|
||||
},
|
||||
"json-stable-stringify-without-jsonify": {
|
||||
"version": "1.0.1",
|
||||
|
@ -10402,7 +10365,8 @@
|
|||
"oauth-sign": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
|
||||
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -11411,8 +11375,7 @@
|
|||
"psl": {
|
||||
"version": "1.1.29",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
|
||||
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
|
||||
},
|
||||
"pstree.remy": {
|
||||
"version": "1.1.0",
|
||||
|
@ -11958,34 +11921,6 @@
|
|||
"is-finite": "1.0.2"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"version": "2.86.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.86.0.tgz",
|
||||
"integrity": "sha512-BQZih67o9r+Ys94tcIW4S7Uu8pthjrQVxhsZ/weOwHbDfACxvIyvnAbzFQxjy1jMtvFSzv5zf4my6cZsJBbVzw==",
|
||||
"requires": {
|
||||
"aws-sign2": "0.7.0",
|
||||
"aws4": "1.8.0",
|
||||
"caseless": "0.12.0",
|
||||
"combined-stream": "1.0.6",
|
||||
"extend": "3.0.2",
|
||||
"forever-agent": "0.6.1",
|
||||
"form-data": "2.3.2",
|
||||
"har-validator": "5.0.3",
|
||||
"hawk": "6.0.2",
|
||||
"http-signature": "1.2.0",
|
||||
"is-typedarray": "1.0.0",
|
||||
"isstream": "0.1.2",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"mime-types": "2.1.20",
|
||||
"oauth-sign": "0.8.2",
|
||||
"performance-now": "2.1.0",
|
||||
"qs": "6.5.2",
|
||||
"safe-buffer": "5.1.2",
|
||||
"tough-cookie": "2.3.4",
|
||||
"tunnel-agent": "0.6.0",
|
||||
"uuid": "3.3.2"
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
|
@ -12837,14 +12772,6 @@
|
|||
"kind-of": "3.2.2"
|
||||
}
|
||||
},
|
||||
"sntp": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
|
||||
"integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==",
|
||||
"requires": {
|
||||
"hoek": "4.2.1"
|
||||
}
|
||||
},
|
||||
"sort-keys": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||
|
@ -13454,6 +13381,7 @@
|
|||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
|
||||
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"punycode": "1.4.1"
|
||||
}
|
||||
|
@ -13747,13 +13675,86 @@
|
|||
}
|
||||
},
|
||||
"universal-analytics": {
|
||||
"version": "0.4.17",
|
||||
"resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.17.tgz",
|
||||
"integrity": "sha512-N2JFymxv4q2N5Wmftc5JCcM5t1tp+sc1kqeDRhDL4XLY5X6PBZ0kav2wvVUZJJMvmSq3WXrmzDu062p+cSFYfQ==",
|
||||
"version": "0.4.20",
|
||||
"resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz",
|
||||
"integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==",
|
||||
"requires": {
|
||||
"debug": "3.1.0",
|
||||
"request": "2.86.0",
|
||||
"request": "2.88.0",
|
||||
"uuid": "3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.5.5",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz",
|
||||
"integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "2.0.1",
|
||||
"fast-json-stable-stringify": "2.0.0",
|
||||
"json-schema-traverse": "0.4.1",
|
||||
"uri-js": "4.2.2"
|
||||
}
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.2.tgz",
|
||||
"integrity": "sha512-OFxb5MZXCUMx43X7O8LK4FKggEQx6yC5QPmOcBnYbJ9UjxEcMcrMbaR0af5HZpqeFopw2GwQRQi34ZXI7YLM5w==",
|
||||
"requires": {
|
||||
"ajv": "6.5.5",
|
||||
"har-schema": "2.0.0"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||
},
|
||||
"request": {
|
||||
"version": "2.88.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||
"requires": {
|
||||
"aws-sign2": "0.7.0",
|
||||
"aws4": "1.8.0",
|
||||
"caseless": "0.12.0",
|
||||
"combined-stream": "1.0.6",
|
||||
"extend": "3.0.2",
|
||||
"forever-agent": "0.6.1",
|
||||
"form-data": "2.3.2",
|
||||
"har-validator": "5.1.2",
|
||||
"http-signature": "1.2.0",
|
||||
"is-typedarray": "1.0.0",
|
||||
"isstream": "0.1.2",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"mime-types": "2.1.20",
|
||||
"oauth-sign": "0.9.0",
|
||||
"performance-now": "2.1.0",
|
||||
"qs": "6.5.2",
|
||||
"safe-buffer": "5.1.2",
|
||||
"tough-cookie": "2.4.3",
|
||||
"tunnel-agent": "0.6.0",
|
||||
"uuid": "3.3.2"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||
"requires": {
|
||||
"psl": "1.1.29",
|
||||
"punycode": "1.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
|
|
10
package.json
10
package.json
|
@ -22,14 +22,14 @@
|
|||
"test": "mocha --recursive",
|
||||
"test:no-lbc": "npm test -- --grep @usesLbc --invert",
|
||||
"test:server": "mocha --recursive './server/**/*.test.js'",
|
||||
"transpile": "builder concurrent transpile:server transpile:client transpile:client_custom",
|
||||
"transpile:dev": "builder concurrent transpile:server:dev transpile:client:dev transpile:client_custom:dev",
|
||||
"transpile": "builder concurrent transpile:server transpile:client transpile:custom",
|
||||
"transpile:dev": "builder concurrent transpile:server:dev transpile:client:dev transpile:custom:dev",
|
||||
"transpile:server": "babel server/render/src -d server/render/build",
|
||||
"transpile:server:dev": "babel server/render/src -w -d server/render/build",
|
||||
"transpile:client": "babel client/src -d client/build",
|
||||
"transpile:client:dev": "babel client/src -w -d client/build",
|
||||
"transpile:client_custom": "babel site/client_custom/src -d site/client_custom/build",
|
||||
"transpile:client_custom:dev": "babel site/client_custom/src -w -d site/client_custom/build"
|
||||
"transpile:custom": "babel site/custom/src -d site/custom/build",
|
||||
"transpile:custom:dev": "babel site/custom/src -w -d site/custom/build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -78,7 +78,7 @@
|
|||
"redux-saga": "^0.16.0",
|
||||
"sequelize": "^4.38.0",
|
||||
"sequelize-cli": "^4.0.0",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"universal-analytics": "^0.4.20",
|
||||
"webpack": "^3.10.0",
|
||||
"webpack-merge": "^4.1.4",
|
||||
"webpack-node-externals": "^1.7.2",
|
||||
|
|
|
@ -850,7 +850,7 @@ var claimQueries = (db, table, sequelize) => ({
|
|||
});
|
||||
},
|
||||
|
||||
getShortClaimIdFromLongClaimId: async (claimId, claimName) => {
|
||||
getShortClaimIdFromLongClaimId: async (claimId, claimName, pendingClaim) => {
|
||||
logger$1.debug(`claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
|
||||
return await table.findAll({
|
||||
where: { name: claimName },
|
||||
|
@ -860,7 +860,12 @@ var claimQueries = (db, table, sequelize) => ({
|
|||
throw new Error('No claim(s) found with that claim name');
|
||||
}
|
||||
|
||||
return returnShortId(result, claimId);
|
||||
let list = result.map(claim => claim.dataValues);
|
||||
if (pendingClaim) {
|
||||
list = list.concat(pendingClaim);
|
||||
}
|
||||
|
||||
return returnShortId(list, claimId);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -981,6 +986,24 @@ var claimQueries = (db, table, sequelize) => ({
|
|||
});
|
||||
},
|
||||
|
||||
resolveClaimInChannel: async (claimName, channelId) => {
|
||||
logger$1.debug(`Claim.resolveClaimByNames: ${claimName} in ${channelId}`);
|
||||
return table.findAll({
|
||||
where: {
|
||||
name: claimName,
|
||||
publisher_id: channelId,
|
||||
},
|
||||
}).then(claimArray => {
|
||||
if (claimArray.length === 0) {
|
||||
return null;
|
||||
} else if (claimArray.length !== 1) {
|
||||
logger$1.warn(`more than one record matches ${claimName} in ${channelId}`);
|
||||
}
|
||||
|
||||
return claimArray[0];
|
||||
});
|
||||
},
|
||||
|
||||
getOutpoint: async (name, claimId) => {
|
||||
logger$1.debug(`finding outpoint for ${name}#${claimId}`);
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ export default (db, table, sequelize) => ({
|
|||
});
|
||||
},
|
||||
|
||||
getShortClaimIdFromLongClaimId: async (claimId, claimName) => {
|
||||
getShortClaimIdFromLongClaimId: async (claimId, claimName, pendingClaim) => {
|
||||
logger.debug(`claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
|
||||
return await table.findAll({
|
||||
where: { name: claimName },
|
||||
|
@ -59,7 +59,12 @@ export default (db, table, sequelize) => ({
|
|||
throw new Error('No claim(s) found with that claim name');
|
||||
}
|
||||
|
||||
return returnShortId(result, claimId);
|
||||
let list = result.map(claim => claim.dataValues);
|
||||
if (pendingClaim) {
|
||||
list = list.concat(pendingClaim);
|
||||
}
|
||||
|
||||
return returnShortId(list, claimId);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -180,6 +185,24 @@ export default (db, table, sequelize) => ({
|
|||
});
|
||||
},
|
||||
|
||||
resolveClaimInChannel: async (claimName, channelId) => {
|
||||
logger.debug(`Claim.resolveClaimByNames: ${claimName} in ${channelId}`);
|
||||
return table.findAll({
|
||||
where: {
|
||||
name: claimName,
|
||||
publisher_id: channelId,
|
||||
},
|
||||
}).then(claimArray => {
|
||||
if (claimArray.length === 0) {
|
||||
return null;
|
||||
} else if (claimArray.length !== 1) {
|
||||
logger.warn(`more than one record matches ${claimName} in ${channelId}`);
|
||||
}
|
||||
|
||||
return claimArray[0];
|
||||
});
|
||||
},
|
||||
|
||||
getOutpoint: async (name, claimId) => {
|
||||
logger.debug(`finding outpoint for ${name}#${claimId}`);
|
||||
|
||||
|
|
|
@ -5,7 +5,11 @@ const { returnPaginatedChannelClaims } = require('./channelPagination.js');
|
|||
|
||||
const getChannelClaims = async (channelName, channelShortId, page) => {
|
||||
const channelId = await chainquery.claim.queries.getLongClaimId(channelName, channelShortId);
|
||||
const channelClaims = await chainquery.claim.queries.getAllChannelClaims(channelId);
|
||||
|
||||
let channelClaims;
|
||||
if (channelId) {
|
||||
channelClaims = await chainquery.claim.queries.getAllChannelClaims(channelId);
|
||||
}
|
||||
|
||||
const processingChannelClaims = channelClaims ? channelClaims.map((claim) => getClaimData(claim)) : [];
|
||||
const processedChannelClaims = await Promise.all(processingChannelClaims);
|
||||
|
|
44
server/controllers/api/claim/abandon/index.js
Normal file
44
server/controllers/api/claim/abandon/index.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
const logger = require('winston');
|
||||
const db = require('server/models');
|
||||
const { abandonClaim } = require('server/lbrynet');
|
||||
const deleteFile = require('../publish/deleteFile.js');
|
||||
const authenticateUser = require('../publish/authentication.js');
|
||||
|
||||
/*
|
||||
route to abandon a claim through the daemon
|
||||
*/
|
||||
|
||||
const claimAbandon = async (req, res) => {
|
||||
const {claimId} = req.body;
|
||||
const {user} = req;
|
||||
try {
|
||||
const [channel, claim] = await Promise.all([
|
||||
authenticateUser(user.channelName, null, null, user),
|
||||
db.Claim.findOne({where: {claimId}}),
|
||||
]);
|
||||
|
||||
if (!claim) throw new Error('That channel does not exist');
|
||||
if (!channel.channelName) throw new Error('You don\'t own this channel');
|
||||
|
||||
await abandonClaim({claimId});
|
||||
const file = await db.File.findOne({where: {claimId}});
|
||||
await Promise.all([
|
||||
deleteFile(file.filePath),
|
||||
db.File.destroy({where: {claimId}}),
|
||||
db.Claim.destroy({where: {claimId}}),
|
||||
]);
|
||||
logger.debug(`Claim abandoned: ${claimId}`);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: `Claim with id ${claimId} abandonded`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('abandon claim error:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = claimAbandon;
|
|
@ -1,8 +1,8 @@
|
|||
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
|
||||
const getClaimData = require('server/utils/getClaimData');
|
||||
const fetchClaimData = require('server/utils/fetchClaimData');
|
||||
const chainquery = require('chainquery');
|
||||
const db = require('server/models');
|
||||
|
||||
/*
|
||||
|
||||
route to return data for a claim
|
||||
|
@ -10,16 +10,9 @@ const db = require('server/models');
|
|||
*/
|
||||
|
||||
const claimData = async ({ ip, originalUrl, body, params }, res) => {
|
||||
const claimName = params.claimName;
|
||||
let claimId = params.claimId;
|
||||
if (claimId === 'none') claimId = null;
|
||||
|
||||
try {
|
||||
let resolvedClaim = await chainquery.claim.queries.resolveClaim(claimName, claimId).catch(() => {});
|
||||
|
||||
if(!resolvedClaim) {
|
||||
resolvedClaim = await db.Claim.resolveClaim(claimName, claimId);
|
||||
}
|
||||
const resolvedClaim = await fetchClaimData(params);
|
||||
|
||||
if (!resolvedClaim) {
|
||||
return res.status(404).json({
|
||||
|
|
|
@ -17,6 +17,9 @@ const parsePublishApiRequestBody = require('./parsePublishApiRequestBody.js');
|
|||
const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
|
||||
const authenticateUser = require('./authentication.js');
|
||||
|
||||
const chainquery = require('chainquery');
|
||||
const createCanonicalLink = require('../../../../../utils/createCanonicalLink');
|
||||
|
||||
const CLAIM_TAKEN = 'CLAIM_TAKEN';
|
||||
const UNAPPROVED_CHANNEL = 'UNAPPROVED_CHANNEL';
|
||||
|
||||
|
@ -42,7 +45,25 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
|
|||
});
|
||||
}
|
||||
// define variables
|
||||
let channelName, channelId, channelPassword, description, fileName, filePath, fileExtension, fileType, gaStartTime, license, name, nsfw, thumbnail, thumbnailFileName, thumbnailFilePath, thumbnailFileType, title;
|
||||
let channelName,
|
||||
channelId,
|
||||
channelPassword,
|
||||
description,
|
||||
fileName,
|
||||
filePath,
|
||||
fileExtension,
|
||||
fileType,
|
||||
gaStartTime,
|
||||
license,
|
||||
name,
|
||||
nsfw,
|
||||
thumbnail,
|
||||
thumbnailFileName,
|
||||
thumbnailFilePath,
|
||||
thumbnailFileType,
|
||||
title,
|
||||
claimData,
|
||||
claimId;
|
||||
// record the start time of the request
|
||||
gaStartTime = Date.now();
|
||||
// validate the body and files of the request
|
||||
|
@ -64,6 +85,7 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
|
|||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
checkClaimAvailability(name),
|
||||
createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId),
|
||||
|
@ -83,19 +105,40 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
|
|||
publish(thumbnailPublishParams, thumbnailFileName, thumbnailFileType);
|
||||
}
|
||||
// publish the asset
|
||||
return publish(publishParams, fileName, fileType);
|
||||
return publish(publishParams, fileName, fileType, filePath);
|
||||
})
|
||||
.then(result => {
|
||||
.then(publishResults => {
|
||||
logger.info('Publish success >', publishResults);
|
||||
claimData = publishResults;
|
||||
({claimId} = claimData);
|
||||
|
||||
if (channelName) {
|
||||
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(claimData.certificateId, channelName);
|
||||
} else {
|
||||
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(claimId, name, claimData).catch(error => {
|
||||
return claimId.slice(0, 1);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(shortId => {
|
||||
let canonicalUrl;
|
||||
if (channelName) {
|
||||
canonicalUrl = createCanonicalLink({ asset: { ...claimData, channelShortId: shortId } });
|
||||
} else {
|
||||
canonicalUrl = createCanonicalLink({ asset: { ...claimData, shortId } })
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'publish completed successfully',
|
||||
data : {
|
||||
name,
|
||||
claimId : result.claim_id,
|
||||
url : `${host}/${result.claim_id}/${name}`, // for backwards compatability with app
|
||||
showUrl : `${host}/${result.claim_id}/${name}`,
|
||||
serveUrl: `${host}/${result.claim_id}/${name}${fileExtension}`,
|
||||
lbryTx : result,
|
||||
claimId,
|
||||
url : `${host}${canonicalUrl}`, // for backwards compatability with app
|
||||
showUrl : `${host}${canonicalUrl}`,
|
||||
serveUrl: `${host}${canonicalUrl}${fileExtension}`,
|
||||
pushTo : canonicalUrl,
|
||||
claimData,
|
||||
},
|
||||
});
|
||||
// record the publish end time and send to google analytics
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
const path = require('path');
|
||||
const validateFileTypeAndSize = require('./validateFileTypeAndSize.js');
|
||||
|
||||
const parsePublishApiRequestFiles = ({file, thumbnail}) => {
|
||||
const parsePublishApiRequestFiles = ({file, thumbnail}, isUpdate) => {
|
||||
// make sure a file was provided
|
||||
if (!file) {
|
||||
if (isUpdate) {
|
||||
if (thumbnail) {
|
||||
const obj = {};
|
||||
obj.thumbnailFileName = thumbnail.name;
|
||||
obj.thumbnailFilePath = thumbnail.path;
|
||||
obj.thumbnailFileType = thumbnail.type;
|
||||
return obj;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
throw new Error('no file with key of [file] found in request');
|
||||
}
|
||||
if (!file.path) {
|
||||
|
@ -28,18 +38,24 @@ const parsePublishApiRequestFiles = ({file, thumbnail}) => {
|
|||
if (/'/.test(file.name)) {
|
||||
throw new Error('apostrophes are not allowed in the file name');
|
||||
}
|
||||
|
||||
// validate the file
|
||||
validateFileTypeAndSize(file);
|
||||
if (file) validateFileTypeAndSize(file);
|
||||
// return results
|
||||
return {
|
||||
const obj = {
|
||||
fileName : file.name,
|
||||
filePath : file.path,
|
||||
fileExtension: path.extname(file.path),
|
||||
fileType : file.type,
|
||||
thumbnailFileName: (thumbnail ? thumbnail.name : null),
|
||||
thumbnailFilePath: (thumbnail ? thumbnail.path : null),
|
||||
thumbnailFileType: (thumbnail ? thumbnail.type : null),
|
||||
};
|
||||
|
||||
if (thumbnail) {
|
||||
obj.thumbnailFileName = thumbnail.name;
|
||||
obj.thumbnailFilePath = thumbnail.path;
|
||||
obj.thumbnailFileType = thumbnail.type;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
module.exports = parsePublishApiRequestFiles;
|
||||
|
|
|
@ -1,81 +1,72 @@
|
|||
const logger = require('winston');
|
||||
const { publishClaim } = require('../../../../lbrynet');
|
||||
const db = require('../../../../models');
|
||||
const { publishClaim } = require('../../../../lbrynet');
|
||||
const { createFileRecordDataAfterPublish } = require('../../../../models/utils/createFileRecordData.js');
|
||||
const { createClaimRecordDataAfterPublish } = require('../../../../models/utils/createClaimRecordData.js');
|
||||
const deleteFile = require('./deleteFile.js');
|
||||
|
||||
const publish = (publishParams, fileName, fileType) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let publishResults, certificateId, channelName;
|
||||
// publish the file
|
||||
return publishClaim(publishParams)
|
||||
.then(result => {
|
||||
logger.info(`Successfully published ${publishParams.name} ${fileName}`, result);
|
||||
|
||||
// Support new daemon, TODO: remove
|
||||
publishResults = result.output && result.output.claim_id ? result.output : result;
|
||||
const publish = async (publishParams, fileName, fileType) => {
|
||||
let publishResults;
|
||||
let channel;
|
||||
let fileRecord;
|
||||
let newFile = Boolean(publishParams.file_path);
|
||||
|
||||
try {
|
||||
publishResults = await publishClaim(publishParams);
|
||||
logger.info(`Successfully published ${publishParams.name} ${fileName}`, publishResults);
|
||||
const outpoint = `${publishResults.output.txid}:${publishResults.output.nout}`;
|
||||
// get the channel information
|
||||
if (publishParams.channel_name) {
|
||||
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
|
||||
return db.Channel.findOne({
|
||||
channel = await db.Channel.findOne({
|
||||
where: {
|
||||
channelName: publishParams.channel_name,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
logger.debug('this claim was not published in a channel');
|
||||
return null;
|
||||
channel = null;
|
||||
}
|
||||
})
|
||||
.then(channel => {
|
||||
// set channel information
|
||||
certificateId = null;
|
||||
channelName = null;
|
||||
if (channel) {
|
||||
certificateId = channel.channelClaimId;
|
||||
channelName = channel.channelName;
|
||||
const certificateId = channel ? channel.channelClaimId : null;
|
||||
const channelName = channel ? channel.channelName : null;
|
||||
|
||||
const claimRecord = await createClaimRecordDataAfterPublish(certificateId, channelName, fileName, fileType, publishParams, publishResults);
|
||||
const {claimId} = claimRecord;
|
||||
const upsertCriteria = {name: publishParams.name, claimId};
|
||||
if (newFile) {
|
||||
// this is the problem
|
||||
//
|
||||
fileRecord = await createFileRecordDataAfterPublish(fileName, fileType, publishParams, publishResults);
|
||||
} else {
|
||||
fileRecord = await db.File.findOne({where: {claimId}}).then(result => result.dataValues);
|
||||
}
|
||||
logger.debug(`certificateId: ${certificateId}`);
|
||||
})
|
||||
.then(() => {
|
||||
return Promise.all([
|
||||
createFileRecordDataAfterPublish(fileName, fileType, publishParams, publishResults),
|
||||
createClaimRecordDataAfterPublish(certificateId, channelName, fileName, fileType, publishParams, publishResults),
|
||||
]);
|
||||
})
|
||||
.then(([fileRecord, claimRecord]) => {
|
||||
// upsert the records
|
||||
const {name} = publishParams;
|
||||
const {claim_id: claimId} = publishResults;
|
||||
const upsertCriteria = {
|
||||
name,
|
||||
claimId,
|
||||
};
|
||||
return Promise.all([
|
||||
|
||||
const [file, claim] = await Promise.all([
|
||||
db.upsert(db.File, fileRecord, upsertCriteria, 'File'),
|
||||
db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim'),
|
||||
]);
|
||||
})
|
||||
.then(([file, claim]) => {
|
||||
logger.debug('File and Claim records successfully created');
|
||||
return Promise.all([
|
||||
logger.info(`File and Claim records successfully created (${publishParams.name})`);
|
||||
|
||||
await Promise.all([
|
||||
file.setClaim(claim),
|
||||
claim.setFile(file),
|
||||
]);
|
||||
})
|
||||
.then(() => {
|
||||
logger.debug('File and Claim records successfully associated');
|
||||
// resolve the promise with the result from lbryApi publishClaim;
|
||||
resolve(publishResults);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('PUBLISH ERROR', error);
|
||||
deleteFile(publishParams.file_path); // delete the local file
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
logger.info(`File and Claim records successfully associated (${publishParams.name})`);
|
||||
|
||||
return Object.assign({}, claimRecord, {outpoint});
|
||||
} catch (err) {
|
||||
// parse daemon response when err is a string
|
||||
// this needs work
|
||||
logger.info('publish/publish err:', err);
|
||||
const error = typeof err === 'string' ? JSON.parse(err) : err;
|
||||
if (publishParams.file_path) {
|
||||
await deleteFile(publishParams.file_path);
|
||||
}
|
||||
const message = error.error && error.error.message ? error.error.message : 'Unknown publish error';
|
||||
return {
|
||||
error: true,
|
||||
message,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = publish;
|
||||
|
|
199
server/controllers/api/claim/update/index.js
Normal file
199
server/controllers/api/claim/update/index.js
Normal file
|
@ -0,0 +1,199 @@
|
|||
const logger = require('winston');
|
||||
const db = require('server/models');
|
||||
const { details, publishing: { disabled, disabledMessage, primaryClaimAddress } } = require('@config/siteConfig');
|
||||
const { resolveUri } = require('server/lbrynet');
|
||||
const { sendGATimingEvent } = require('../../../../utils/googleAnalytics.js');
|
||||
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
|
||||
const publish = require('../publish/publish.js');
|
||||
const parsePublishApiRequestBody = require('../publish/parsePublishApiRequestBody');
|
||||
const parsePublishApiRequestFiles = require('../publish/parsePublishApiRequestFiles.js');
|
||||
const authenticateUser = require('../publish/authentication.js');
|
||||
const createThumbnailPublishParams = require('../publish/createThumbnailPublishParams.js');
|
||||
const chainquery = require('chainquery');
|
||||
const createCanonicalLink = require('../../../../../utils/createCanonicalLink');
|
||||
|
||||
/*
|
||||
route to update a claim through the daemon
|
||||
*/
|
||||
|
||||
const updateMetadata = ({nsfw, license, title, description}) => {
|
||||
const update = {};
|
||||
if (nsfw) update['nsfw'] = nsfw;
|
||||
if (license) update['license'] = license;
|
||||
if (title) update['title'] = title;
|
||||
if (description) update['description'] = description;
|
||||
return update;
|
||||
};
|
||||
|
||||
const rando = () => {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 6; i += 1) text += possible.charAt(Math.floor(Math.random() * 62));
|
||||
return text;
|
||||
};
|
||||
|
||||
const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res) => {
|
||||
// logging
|
||||
logger.info('Claim update request:', {
|
||||
ip,
|
||||
headers,
|
||||
body,
|
||||
files,
|
||||
user,
|
||||
});
|
||||
|
||||
// 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,
|
||||
thumbnail,
|
||||
fileExtension,
|
||||
license,
|
||||
name,
|
||||
nsfw,
|
||||
thumbnailFileName,
|
||||
thumbnailFilePath,
|
||||
thumbnailFileType,
|
||||
title,
|
||||
claimRecord,
|
||||
metadata,
|
||||
publishResult,
|
||||
thumbnailUpdate = false;
|
||||
// record the start time of the request
|
||||
gaStartTime = Date.now();
|
||||
|
||||
try {
|
||||
({name, nsfw, license, title, description, thumbnail} = parsePublishApiRequestBody(body));
|
||||
({fileName, filePath, fileExtension, fileType, thumbnailFileName, thumbnailFilePath, thumbnailFileType} = parsePublishApiRequestFiles(files, true));
|
||||
({channelName, channelId, channelPassword} = body);
|
||||
} catch (error) {
|
||||
return res.status(400).json({success: false, message: error.message});
|
||||
}
|
||||
|
||||
// check channel authorization
|
||||
authenticateUser(channelName, channelId, channelPassword, user)
|
||||
.then(({ channelName, channelClaimId }) => {
|
||||
if (!channelId) {
|
||||
channelId = channelClaimId;
|
||||
}
|
||||
return chainquery.claim.queries.resolveClaimInChannel(name, channelClaimId).then(claim => claim.dataValues);
|
||||
})
|
||||
.then(claim => {
|
||||
claimRecord = claim;
|
||||
if (claimRecord.content_type === 'video/mp4' && files.file) {
|
||||
thumbnailUpdate = true;
|
||||
}
|
||||
|
||||
if (!files.file || thumbnailUpdate) {
|
||||
return Promise.all([
|
||||
db.File.findOne({ where: { name, claimId: claim.claim_id } }),
|
||||
resolveUri(`${claim.name}#${claim.claim_id}`),
|
||||
]);
|
||||
}
|
||||
|
||||
return [null, null];
|
||||
})
|
||||
.then(([fileResult, resolution]) => {
|
||||
|
||||
metadata = Object.assign({}, {
|
||||
title : claimRecord.title,
|
||||
description: claimRecord.description,
|
||||
nsfw : claimRecord.nsfw,
|
||||
license : claimRecord.license,
|
||||
language : 'en',
|
||||
author : details.title,
|
||||
}, updateMetadata({title, description, nsfw, license}));
|
||||
const publishParams = {
|
||||
name,
|
||||
bid : '0.01',
|
||||
claim_address: primaryClaimAddress,
|
||||
channel_name : channelName,
|
||||
channel_id : channelId,
|
||||
metadata,
|
||||
};
|
||||
|
||||
if (files.file) {
|
||||
if (thumbnailUpdate) {
|
||||
// publish new thumbnail
|
||||
const newThumbnailName = `${name}-${rando()}`;
|
||||
const newThumbnailParams = createThumbnailPublishParams(filePath, newThumbnailName, license, nsfw);
|
||||
newThumbnailParams['file_path'] = filePath;
|
||||
publish(newThumbnailParams, fileName, fileType);
|
||||
|
||||
publishParams['sources'] = resolution.claim.value.stream.source;
|
||||
publishParams['thumbnail'] = `${details.host}/${newThumbnailParams.channel_name}:${newThumbnailParams.channel_id}/${newThumbnailName}-thumb.jpg`;
|
||||
} else {
|
||||
publishParams['file_path'] = filePath;
|
||||
}
|
||||
} else {
|
||||
fileName = fileResult.fileName;
|
||||
fileType = fileResult.fileType;
|
||||
publishParams['sources'] = resolution.claim.value.stream.source;
|
||||
publishParams['thumbnail'] = claimRecord.thumbnail_url;
|
||||
}
|
||||
|
||||
const fp = files && files.file && files.file.path ? files.file.path : undefined;
|
||||
return publish(publishParams, fileName, fileType, fp);
|
||||
})
|
||||
.then(result => {
|
||||
publishResult = result;
|
||||
|
||||
if (channelName) {
|
||||
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(result.certificateId, channelName);
|
||||
} else {
|
||||
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(result.claimId, name, result).catch(error => {
|
||||
return result.claimId.slice(0, 1);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(shortId => {
|
||||
let canonicalUrl;
|
||||
if (channelName) {
|
||||
canonicalUrl = createCanonicalLink({ asset: { ...publishResult, channelShortId: shortId } });
|
||||
} else {
|
||||
canonicalUrl = createCanonicalLink({ asset: { ...publishResult, shortId } })
|
||||
}
|
||||
|
||||
if (publishResult.error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: publishResult.message,
|
||||
});
|
||||
}
|
||||
|
||||
const {claimId} = publishResult;
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'update successful',
|
||||
data : {
|
||||
name,
|
||||
claimId,
|
||||
url : `${details.host}${canonicalUrl}`, // for backwards compatability with app
|
||||
showUrl : `${details.host}${canonicalUrl}`,
|
||||
serveUrl: `${details.host}${canonicalUrl}${fileExtension}`,
|
||||
pushTo : canonicalUrl,
|
||||
claimData: publishResult,
|
||||
},
|
||||
});
|
||||
// record the publish end time and send to google analytics
|
||||
sendGATimingEvent('end-to-end', 'update', fileType, gaStartTime, Date.now());
|
||||
})
|
||||
.catch(error => {
|
||||
handleErrorResponse(originalUrl, ip, error, res);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = claimUpdate;
|
|
@ -2,6 +2,7 @@ const axios = require('axios');
|
|||
const logger = require('winston');
|
||||
const { apiHost, apiPort, getTimeout } = require('@config/lbryConfig');
|
||||
const lbrynetUri = 'http://' + apiHost + ':' + apiPort;
|
||||
const db = require('../models');
|
||||
const { chooseGaLbrynetPublishLabel, sendGATimingEvent } = require('../utils/googleAnalytics.js');
|
||||
const handleLbrynetResponse = require('./utils/handleLbrynetResponse.js');
|
||||
const { publishing } = require('@config/siteConfig');
|
||||
|
@ -46,6 +47,21 @@ module.exports = {
|
|||
});
|
||||
});
|
||||
},
|
||||
async abandonClaim ({claimId}) {
|
||||
logger.debug(`lbryApi >> Abandon claim "${claimId}"`);
|
||||
const gaStartTime = Date.now();
|
||||
try {
|
||||
const abandon = await axios.post(lbrynetUri, {
|
||||
method: 'claim_abandon',
|
||||
params: { claim_id: claimId },
|
||||
});
|
||||
sendGATimingEvent('lbrynet', 'abandonClaim', 'ABANDON_CLAIM', gaStartTime, Date.now());
|
||||
return abandon.data;
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return error;
|
||||
}
|
||||
},
|
||||
getClaimList (claimName) {
|
||||
logger.debug(`lbryApi >> Getting claim_list for "${claimName}"`);
|
||||
const gaStartTime = Date.now();
|
||||
|
@ -75,7 +91,13 @@ module.exports = {
|
|||
})
|
||||
.then(({ data }) => {
|
||||
sendGATimingEvent('lbrynet', 'resolveUri', 'RESOLVE', gaStartTime, Date.now());
|
||||
if (data.result[uri].error) { // check for errors
|
||||
if (Object.keys(data.result).length === 0 && data.result.constructor === Object) {
|
||||
// workaround for daemon returning empty result object
|
||||
// https://github.com/lbryio/lbry/issues/1485
|
||||
db.Claim.findOne({ where: { claimId: uri.split('#')[1] } })
|
||||
.then(() => reject('This claim has not yet been confirmed on the LBRY blockchain'))
|
||||
.catch(() => reject(`Claim ${uri} does not exist`));
|
||||
} else if (data.result[uri].error) { // check for errors
|
||||
reject(data.result[uri].error);
|
||||
} else { // if no errors, resolve
|
||||
resolve(data.result[uri]);
|
||||
|
|
|
@ -10,8 +10,14 @@ function logMetricsMiddleware(req, res, next) {
|
|||
let referrer = req.get('referrer');
|
||||
|
||||
if(referrer && referrer.length > 255) {
|
||||
try {
|
||||
// Attempt to "safely" clamp long URLs
|
||||
referrer = /(.*?)#.*/.exec(referrer)[1];
|
||||
} catch(e) {
|
||||
// Cheap forced string conversion & clamp
|
||||
referrer = new String(referrer);
|
||||
referrer = referrer.substr(0, 255);
|
||||
}
|
||||
|
||||
if(referrer.length > 255) {
|
||||
logger.warn('Request refferer exceeds 255 characters:', referrer);
|
||||
|
|
|
@ -28,7 +28,7 @@ async function createFileRecordDataAfterGet (resolveResult, getResult) {
|
|||
filePath,
|
||||
fileType,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async function createFileRecordDataAfterPublish (fileName, fileType, publishParams, publishResults) {
|
||||
const {
|
||||
|
|
|
@ -13,6 +13,8 @@ const claimGet = require('../../controllers/api/claim/get');
|
|||
const claimList = require('../../controllers/api/claim/list');
|
||||
const claimLongId = require('../../controllers/api/claim/longId');
|
||||
const claimPublish = require('../../controllers/api/claim/publish');
|
||||
const claimAbandon = require('../../controllers/api/claim/abandon');
|
||||
const claimUpdate = require('../../controllers/api/claim/update');
|
||||
const claimResolve = require('../../controllers/api/claim/resolve');
|
||||
const claimShortId = require('../../controllers/api/claim/shortId');
|
||||
const claimViews = require('../../controllers/api/claim/views');
|
||||
|
@ -29,12 +31,10 @@ const getOEmbedData = require('../../controllers/api/oEmbed');
|
|||
module.exports = {
|
||||
// homepage routes
|
||||
'/api/homepage/data/channels': { controller: [ torCheckMiddleware, channelData ] },
|
||||
|
||||
// channel routes
|
||||
'/api/channel/availability/:name': { controller: [ torCheckMiddleware, channelAvailability ] },
|
||||
'/api/channel/short-id/:longId/:name': { controller: [ torCheckMiddleware, channelShortId ] },
|
||||
'/api/channel/data/:channelName/:channelClaimId': { controller: [ torCheckMiddleware, channelData ] },
|
||||
'/api/channel/data/:channelName/:channelClaimId': { controller: [ torCheckMiddleware, channelData ] },
|
||||
'/api/channel/claims/:channelName/:channelClaimId/:page': { controller: [ torCheckMiddleware, channelClaims ] },
|
||||
|
||||
// sepcial routes
|
||||
|
@ -47,6 +47,8 @@ module.exports = {
|
|||
'/api/claim/list/:name': { controller: [ torCheckMiddleware, claimList ] },
|
||||
'/api/claim/long-id': { method: 'post', controller: [ torCheckMiddleware, claimLongId ] }, // note: should be a 'get'
|
||||
'/api/claim/publish': { method: 'post', controller: [ torCheckMiddleware, autoblockPublishMiddleware, multipartMiddleware, autoblockPublishBodyMiddleware, claimPublish ] },
|
||||
'/api/claim/update': { method: 'post', controller: [ torCheckMiddleware, multipartMiddleware, claimUpdate ] },
|
||||
'/api/claim/abandon': { method: 'post', controller: [ torCheckMiddleware, multipartMiddleware, claimAbandon ] },
|
||||
'/api/claim/resolve/:name/:claimId': { controller: [ torCheckMiddleware, claimResolve ] },
|
||||
'/api/claim/short-id/:longId/:name': { controller: [ torCheckMiddleware, claimShortId ] },
|
||||
'/api/claim/views/:claimId': { controller: [ torCheckMiddleware, claimViews ] },
|
||||
|
|
|
@ -15,6 +15,7 @@ module.exports = {
|
|||
'/trending': { controller: redirect('/popular') },
|
||||
'/popular': { controller: handlePageRequest },
|
||||
'/new': { controller: handlePageRequest },
|
||||
'/edit/:claimId': { controller: handlePageRequest },
|
||||
'/multisite': { controller: handlePageRequest },
|
||||
'/video-embed/:name/:claimId/:config?': { controller: handleVideoEmbedRequest }, // for twitter
|
||||
};
|
||||
|
|
25
server/utils/fetchClaimData.js
Normal file
25
server/utils/fetchClaimData.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
const chainquery = require('chainquery');
|
||||
const db = require('server/models');
|
||||
|
||||
const fetchClaimData = async (params) => {
|
||||
let { claimId, claimName: name } = params;
|
||||
if (claimId === 'none') claimId = null;
|
||||
|
||||
const [cq, local] = await Promise.all([
|
||||
chainquery.claim.queries.resolveClaim(name, claimId).then(res => res.dataValues).catch(() => {}),
|
||||
db.Claim.resolveClaim(name, claimId).catch(() => {}),
|
||||
]);
|
||||
|
||||
if (!cq && !local) {
|
||||
return null;
|
||||
}
|
||||
if (cq && cq.name === name && !local) {
|
||||
return cq;
|
||||
}
|
||||
if (local && local.name === name && !cq) {
|
||||
return local;
|
||||
}
|
||||
return local.updatedAt > cq.modified_at ? local : cq;
|
||||
};
|
||||
|
||||
module.exports = fetchClaimData;
|
|
@ -25,7 +25,7 @@ module.exports = async (data) => {
|
|||
claimId: data.claim_id || data.claimId,
|
||||
fileExt: data.generated_extension || data.fileExt,
|
||||
description: data.description,
|
||||
thumbnail: data.generated_thumbnail || data.thumbnail,
|
||||
thumbnail: data.generated_thumbnail || data.thumbnail_url || data.thumbnail,
|
||||
outpoint: data.transaction_hash_id || data.outpoint,
|
||||
host,
|
||||
})
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
word-wrap: break-word;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
const { statSync, existsSync, readdirSync } = require('fs');
|
||||
const { join, resolve } = require('path');
|
||||
const DEFAULT_ROOT = 'client/build';
|
||||
const CUSTOM_ROOT = 'site/client_custom/build';
|
||||
const CUSTOM_SCSS_ROOT = 'site/client_custom/scss';
|
||||
const CUSTOM_ROOT = 'site/custom/build';
|
||||
const CUSTOM_SCSS_ROOT = 'site/custom/scss';
|
||||
|
||||
const getFolders = path => {
|
||||
if (existsSync(path)) {
|
||||
|
|
Loading…
Reference in a new issue