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/build
|
||||||
|
|
||||||
client_custom/build
|
site/
|
||||||
client_custom/scss
|
|
||||||
client_custom/src/components
|
|
||||||
client_custom/src/containers
|
|
||||||
client_custom/src/pages
|
|
||||||
|
|
||||||
devConfig/sequelizeCliConfig.js
|
devConfig/sequelizeCliConfig.js
|
||||||
devConfig/testingConfig.js
|
devConfig/testingConfig.js
|
||||||
|
|
||||||
config/
|
|
||||||
|
|
||||||
public/bundle/bundle.js
|
public/bundle/bundle.js
|
||||||
public/bundle/bundle.js.map
|
public/bundle/bundle.js.map
|
||||||
public/bundle/Lekton-*
|
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/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
|
* 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
|
* `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.
|
* `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 = '';
|
let thumbnailChannelId = '';
|
||||||
|
|
||||||
const createConfigFile = (fileName, configObject) => { // siteConfig.json , siteConfig
|
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);
|
const fileContents = JSON.stringify(configObject, null, 2);
|
||||||
fs.writeFileSync(fileLocation, fileContents, 'utf-8');
|
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
|
// import existing configs or import the defaults
|
||||||
let mysqlConfig;
|
let mysqlConfig;
|
||||||
try {
|
try {
|
||||||
mysqlConfig = require('../config/mysqlConfig.json');
|
mysqlConfig = require('../site/config/mysqlConfig.json');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mysqlConfig = require('./defaults/mysqlConfig.json');
|
mysqlConfig = require('./defaults/mysqlConfig.json');
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ const { database: mysqlDatabase, username: mysqlUsername, password: mysqlPasswor
|
||||||
|
|
||||||
let siteConfig;
|
let siteConfig;
|
||||||
try {
|
try {
|
||||||
siteConfig = require('../config/siteConfig.json');
|
siteConfig = require('../site/config/siteConfig.json');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
siteConfig = require('./defaults/siteConfig.json');
|
siteConfig = require('./defaults/siteConfig.json');
|
||||||
}
|
}
|
||||||
|
@ -39,30 +39,30 @@ const {
|
||||||
port,
|
port,
|
||||||
title,
|
title,
|
||||||
host,
|
host,
|
||||||
channelClaimBidAmount: channelBid,
|
|
||||||
},
|
},
|
||||||
publishing: {
|
publishing: {
|
||||||
uploadDirectory,
|
uploadDirectory,
|
||||||
|
channelClaimBidAmount: channelBid,
|
||||||
},
|
},
|
||||||
} = siteConfig;
|
} = siteConfig;
|
||||||
|
|
||||||
let lbryConfig;
|
let lbryConfig;
|
||||||
try {
|
try {
|
||||||
lbryConfig = require('../config/lbryConfig.json');
|
lbryConfig = require('../site/config/lbryConfig.json');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lbryConfig = require('./defaults/lbryConfig.json');
|
lbryConfig = require('./defaults/lbryConfig.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
let loggerConfig;
|
let loggerConfig;
|
||||||
try {
|
try {
|
||||||
loggerConfig = require('../config/loggerConfig.json');
|
loggerConfig = require('../site/config/loggerConfig.json');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
loggerConfig = require('./defaults/loggerConfig.json');
|
loggerConfig = require('./defaults/loggerConfig.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
let slackConfig;
|
let slackConfig;
|
||||||
try {
|
try {
|
||||||
slackConfig = require('../config/slackConfig.json');
|
slackConfig = require('../site/config/slackConfig.json');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
slackConfig = require('./defaults/slackConfig.json');
|
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) {
|
export function updateMetadata (name, value) {
|
||||||
return {
|
return {
|
||||||
type: actions.METADATA_UPDATE,
|
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) {
|
export function setPublishInChannel (channel) {
|
||||||
return {
|
return {
|
||||||
type: actions.SET_PUBLISH_IN_CHANNEL,
|
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
|
// channel actions
|
||||||
|
|
||||||
export function addNewChannelToChannelList (id, name, shortId, longId, claimsData) {
|
export function addNewChannelToChannelList (id, name, shortId, longId, claimsData) {
|
||||||
|
@ -129,7 +136,7 @@ export function onUpdateChannelClaims (channelKey, name, longId, page) {
|
||||||
|
|
||||||
export function updateChannelClaims (channelListId, claimsData) {
|
export function updateChannelClaims (channelListId, claimsData) {
|
||||||
return {
|
return {
|
||||||
type: actions.CHANNEL_CLAIMS_UPDATE_SUCCESS,
|
type: actions.CHANNEL_CLAIMS_UPDATE_SUCCEEDED,
|
||||||
data: {channelListId, claimsData},
|
data: {channelListId, claimsData},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,3 +42,15 @@ export function getClaimViews (claimId) {
|
||||||
const url = `/api/claim/views/${claimId}`;
|
const url = `/api/claim/views/${claimId}`;
|
||||||
return Request(url);
|
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 FourOhFourPage from '@pages/FourOhFourPage';
|
||||||
import MultisitePage from '@pages/MultisitePage';
|
import MultisitePage from '@pages/MultisitePage';
|
||||||
import PopularPage from '@pages/PopularPage';
|
import PopularPage from '@pages/PopularPage';
|
||||||
|
import EditPage from '@pages/EditPage';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -21,6 +22,7 @@ const App = () => {
|
||||||
<Route exact path='/login' component={LoginPage} />
|
<Route exact path='/login' component={LoginPage} />
|
||||||
<Route exact path='/multisite' component={MultisitePage} />
|
<Route exact path='/multisite' component={MultisitePage} />
|
||||||
<Route exact path='/popular' component={PopularPage} />
|
<Route exact path='/popular' component={PopularPage} />
|
||||||
|
<Route exact path='/edit/:identifier/:claim' component={EditPage} />
|
||||||
<Route exact path='/:identifier/:claim' component={ContentPageWrapper} />
|
<Route exact path='/:identifier/:claim' component={ContentPageWrapper} />
|
||||||
<Route exact path='/:claim' component={ContentPageWrapper} />
|
<Route exact path='/:claim' component={ContentPageWrapper} />
|
||||||
<Route component={FourOhFourPage} />
|
<Route component={FourOhFourPage} />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {buffers, END, eventChannel} from 'redux-saga';
|
import {buffers, END, eventChannel} from 'redux-saga';
|
||||||
|
|
||||||
export const makePublishRequestChannel = (fd) => {
|
export const makePublishRequestChannel = (fd, isUpdate) => {
|
||||||
return eventChannel(emitter => {
|
return eventChannel(emitter => {
|
||||||
const uri = '/api/claim/publish';
|
const uri = `/api/claim/${isUpdate ? 'update' : 'publish'}`;
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
// add event listeners
|
// add event listeners
|
||||||
const onLoadStart = () => {
|
const onLoadStart = () => {
|
||||||
|
|
|
@ -2,11 +2,14 @@ import React from 'react';
|
||||||
import FormFeedbackDisplay from '@components/FormFeedbackDisplay';
|
import FormFeedbackDisplay from '@components/FormFeedbackDisplay';
|
||||||
import Row from '@components/Row';
|
import Row from '@components/Row';
|
||||||
|
|
||||||
const DropzoneInstructionsDisplay = ({fileError}) => {
|
const DropzoneInstructionsDisplay = ({fileError, message}) => {
|
||||||
|
if (!message) {
|
||||||
|
message = 'Drag & drop image or video here to publish';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className={'dropzone-instructions-display'}>
|
<div className={'dropzone-instructions-display'}>
|
||||||
<Row>
|
<Row>
|
||||||
<span className={'text--large'}>Drag & drop image or video here to publish</span>
|
<span className={'text--large'}>{message}</span>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<span className={'text--small text--secondary'}>OR</span>
|
<span className={'text--small text--secondary'}>OR</span>
|
||||||
|
|
|
@ -10,7 +10,12 @@ class PublishPreview extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
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) {
|
componentWillReceiveProps (newProps) {
|
||||||
if (newProps.file !== this.props.file) {
|
if (newProps.file !== this.props.file) {
|
||||||
|
@ -54,8 +59,10 @@ class PublishPreview extends React.Component {
|
||||||
|
|
||||||
PublishPreview.propTypes = {
|
PublishPreview.propTypes = {
|
||||||
dimPreview: PropTypes.bool.isRequired,
|
dimPreview: PropTypes.bool.isRequired,
|
||||||
file : PropTypes.object.isRequired,
|
file : PropTypes.object,
|
||||||
thumbnail : PropTypes.object,
|
thumbnail : PropTypes.object,
|
||||||
|
isUpdate : PropTypes.bool,
|
||||||
|
sourceUrl : PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PublishPreview;
|
export default PublishPreview;
|
||||||
|
|
|
@ -16,7 +16,7 @@ const PublishLicenseInput = ({ handleSelect }) => {
|
||||||
className='select select--primary'
|
className='select select--primary'
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
>
|
>
|
||||||
<option value=' '>Unspecified</option>
|
<option value=''>Unspecified</option>
|
||||||
<option value='Public Domain'>Public Domain</option>
|
<option value='Public Domain'>Public Domain</option>
|
||||||
<option value='Creative Commons'>Creative Commons</option>
|
<option value='Creative Commons'>Creative Commons</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -9,10 +9,12 @@ import Row from '@components/Row';
|
||||||
|
|
||||||
class PublishPreview extends React.Component {
|
class PublishPreview extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
|
const { isUpdate, uri } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={'publish-form'}>
|
<div className={'publish-form'}>
|
||||||
<div className={'publish-form__title'}>
|
<div className={'publish-form__title'}>
|
||||||
<Row>
|
<Row>
|
||||||
|
{isUpdate && uri && (<p className='text--secondary'>{`Editing ${uri}`}</p>)}
|
||||||
<PublishTitleInput />
|
<PublishTitleInput />
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</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 THUMBNAIL_NEW = 'THUMBNAIL_NEW';
|
||||||
export const PUBLISH_START = 'PUBLISH_START';
|
export const PUBLISH_START = 'PUBLISH_START';
|
||||||
export const CLAIM_AVAILABILITY = 'CLAIM_AVAILABILITY';
|
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 LOAD_START = 'LOAD_START';
|
||||||
export const LOADING = 'LOADING';
|
export const LOADING = 'LOADING';
|
||||||
export const PUBLISHING = 'PUBLISHING';
|
export const PUBLISHING = 'PUBLISHING';
|
||||||
export const SUCCESS = 'SUCCESS';
|
export const SUCCEEDED = 'SUCCEEDED';
|
||||||
export const FAILED = 'FAILED';
|
export const FAILED = 'FAILED';
|
||||||
|
export const ABANDONING = 'ABANDONING';
|
||||||
|
|
|
@ -11,12 +11,14 @@ export const REQUEST_LIST_ADD = 'REQUEST_LIST_ADD';
|
||||||
// asset actions
|
// asset actions
|
||||||
export const ASSET_ADD = 'ASSET_ADD';
|
export const ASSET_ADD = 'ASSET_ADD';
|
||||||
export const ASSET_VIEWS_UPDATE = 'ASSET_VIEWS_UPDATE';
|
export const ASSET_VIEWS_UPDATE = 'ASSET_VIEWS_UPDATE';
|
||||||
|
export const ASSET_UPDATE_CLAIMDATA = 'ASSET_UPDATE_CLAIMDATA';
|
||||||
|
export const ASSET_REMOVE = 'ASSET_REMOVE';
|
||||||
|
|
||||||
// channel actions
|
// channel actions
|
||||||
export const CHANNEL_ADD = 'CHANNEL_ADD';
|
export const CHANNEL_ADD = 'CHANNEL_ADD';
|
||||||
|
|
||||||
export const CHANNEL_CLAIMS_UPDATE_ASYNC = 'CHANNEL_CLAIMS_UPDATE_ASYNC';
|
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
|
// asset/file display actions
|
||||||
export const FILE_REQUESTED = 'FILE_REQUESTED';
|
export const FILE_REQUESTED = 'FILE_REQUESTED';
|
||||||
|
|
|
@ -3,7 +3,8 @@ import View from './view';
|
||||||
import { fileRequested } from '../../actions/show';
|
import { fileRequested } from '../../actions/show';
|
||||||
import { selectAsset } from '../../selectors/show';
|
import { selectAsset } from '../../selectors/show';
|
||||||
|
|
||||||
const mapStateToProps = ({ show }) => {
|
const mapStateToProps = (props) => {
|
||||||
|
const {show} = props;
|
||||||
// select error and status
|
// select error and status
|
||||||
const error = show.displayAsset.error;
|
const error = show.displayAsset.error;
|
||||||
const status = show.displayAsset.status;
|
const status = show.displayAsset.status;
|
||||||
|
|
|
@ -3,6 +3,42 @@ import Row from '@components/Row';
|
||||||
import AssetTitle from '@containers/AssetTitle';
|
import AssetTitle from '@containers/AssetTitle';
|
||||||
import ProgressBar from '@components/ProgressBar';
|
import ProgressBar from '@components/ProgressBar';
|
||||||
import { LOCAL_CHECK, UNAVAILABLE, ERROR, AVAILABLE } from '../../constants/asset_display_states';
|
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 {
|
class AssetDisplay extends React.Component {
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
@ -10,67 +46,49 @@ class AssetDisplay extends React.Component {
|
||||||
this.props.onFileRequest(name, claimId);
|
this.props.onFileRequest(name, claimId);
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const { status, error, asset: { claimData: { name, claimId, contentType, fileExt, thumbnail } } } = this.props;
|
const { status, error, asset } = this.props;
|
||||||
const sourceUrl = `/${claimId}/${name}.${fileExt}`;
|
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 (
|
return (
|
||||||
<div className={'asset-display-wrap'}>
|
<div className={'asset-display-wrap'}>
|
||||||
<div className={'asset-display'}>
|
<div className={'asset-display'}>
|
||||||
{(status === LOCAL_CHECK) &&
|
<div className={'asset-display'}>
|
||||||
<div>
|
{(status === LOCAL_CHECK) &&
|
||||||
<p>Checking to see if Spee.ch has your asset locally...</p>
|
<div>
|
||||||
</div>
|
<p>Checking to see if Spee.ch has your asset locally...</p>
|
||||||
}
|
</div>
|
||||||
{(status === UNAVAILABLE) &&
|
}
|
||||||
<div>
|
{(status === UNAVAILABLE) &&
|
||||||
<p>Sit tight, we're searching the LBRY blockchain for your asset!</p>
|
<div>
|
||||||
<ProgressBar size={12} />
|
<p>Sit tight, we're searching the LBRY blockchain for your asset!</p>
|
||||||
<p>Curious what magic is happening here? <a className='link--primary' target='blank' href='https://lbry.io/faq/what-is-lbry'>Learn more.</a></p>
|
<ProgressBar size={12} />
|
||||||
</div>
|
<p>Curious what magic is happening here? <a className='link--primary' target='blank' href='https://lbry.io/faq/what-is-lbry'>Learn more.</a></p>
|
||||||
}
|
</div>
|
||||||
{(status === ERROR) &&
|
}
|
||||||
<div>
|
{(status === ERROR) &&
|
||||||
<Row>
|
<div>
|
||||||
<p>Unfortunately, we couldn't download your asset from LBRY. You can help us out by sharing the following error message in the <a className='link--primary' href='https://chat.lbry.io' target='_blank'>LBRY discord</a>.</p>
|
<Row>
|
||||||
</Row>
|
<p>Unfortunately, we couldn't download your asset from LBRY. You can help us out by sharing the following error message in the <a className='link--primary' href='https://chat.lbry.io' target='_blank'>LBRY discord</a>.</p>
|
||||||
<Row>
|
</Row>
|
||||||
<p id='error-message'><i>{error}</i></p>
|
<Row>
|
||||||
</Row>
|
<p id='error-message'><i>{error}</i></p>
|
||||||
</div>
|
</Row>
|
||||||
}
|
</div>
|
||||||
{(status === AVAILABLE) &&
|
}
|
||||||
(() => {
|
{(status === AVAILABLE) &&
|
||||||
switch (contentType) {
|
<AvailableContent
|
||||||
case 'image/jpeg':
|
contentType={contentType}
|
||||||
case 'image/jpg':
|
sourceUrl={sourceUrl}
|
||||||
case 'image/png':
|
name={name}
|
||||||
case 'image/gif':
|
thumbnail={thumbnail}
|
||||||
return (
|
/>
|
||||||
<img
|
|
||||||
className='asset-image'
|
|
||||||
src={sourceUrl}
|
|
||||||
alt={name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
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>
|
</div>
|
||||||
<AssetTitle />
|
<AssetTitle />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,12 +2,20 @@ import { connect } from 'react-redux';
|
||||||
import View from './view';
|
import View from './view';
|
||||||
import { selectAsset } from '../../selectors/show';
|
import { selectAsset } from '../../selectors/show';
|
||||||
|
|
||||||
const mapStateToProps = ({ show }) => {
|
const mapStateToProps = (props) => {
|
||||||
|
const {show} = props;
|
||||||
// select asset
|
// select asset
|
||||||
const asset = selectAsset(show);
|
const asset = selectAsset(show);
|
||||||
|
const editable = Boolean(
|
||||||
|
asset &&
|
||||||
|
asset.claimData &&
|
||||||
|
asset.claimData.channelName &&
|
||||||
|
props.channel.loggedInChannel.name === asset.claimData.channelName
|
||||||
|
);
|
||||||
// return props
|
// return props
|
||||||
return {
|
return {
|
||||||
asset,
|
asset,
|
||||||
|
editable,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,11 @@ import AssetInfoFooter from '../../components/AssetInfoFooter/index';
|
||||||
|
|
||||||
class AssetInfo extends React.Component {
|
class AssetInfo extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const { asset } = this.props;
|
const { editable, asset } = this.props;
|
||||||
const { claimViews, claimData: { channelName, channelShortId, description, name, fileExt, contentType, thumbnail, host } } = asset;
|
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}`;
|
const assetCanonicalUrl = `${host}${canonicalUrl}`;
|
||||||
|
|
||||||
let channelCanonicalUrl;
|
let channelCanonicalUrl;
|
||||||
|
@ -39,6 +40,12 @@ class AssetInfo extends React.Component {
|
||||||
}
|
}
|
||||||
rightSide={
|
rightSide={
|
||||||
<div>
|
<div>
|
||||||
|
{editable && (
|
||||||
|
<RowLabeled
|
||||||
|
label={<Label value={'Edit:'} />}
|
||||||
|
content={<Link to={`/edit${canonicalUrl}`}>{name}</Link>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{channelName && (
|
{channelName && (
|
||||||
<RowLabeled
|
<RowLabeled
|
||||||
label={
|
label={
|
||||||
|
@ -51,7 +58,6 @@ class AssetInfo extends React.Component {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{claimViews ? (
|
{claimViews ? (
|
||||||
<RowLabeled
|
<RowLabeled
|
||||||
label={
|
label={
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { connect } from 'react-redux';
|
||||||
import View from './view';
|
import View from './view';
|
||||||
import { selectAsset } from '../../selectors/show';
|
import { selectAsset } from '../../selectors/show';
|
||||||
|
|
||||||
const mapStateToProps = ({ show }) => {
|
const mapStateToProps = (props) => {
|
||||||
const { claimData: { title } } = selectAsset(show);
|
const { claimData: { title } } = selectAsset(props.show);
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,7 +36,7 @@ class ChannelClaimsDisplay extends React.Component {
|
||||||
<AssetPreview
|
<AssetPreview
|
||||||
defaultThumbnail={defaultThumbnail}
|
defaultThumbnail={defaultThumbnail}
|
||||||
claimData={claim}
|
claimData={claim}
|
||||||
key={`${claim.name}-${claim.id}`}
|
key={claim.claimId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,29 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectFile, updateError, clearFile } from '../../actions/publish';
|
import { selectFile, updateError, clearFile } from '../../actions/publish';
|
||||||
|
import { selectAsset } from '../../selectors/show';
|
||||||
import View from './view';
|
import View from './view';
|
||||||
|
import siteConfig from '@config/siteConfig.json';
|
||||||
|
import createCanonicalLink from '../../../../utils/createCanonicalLink';
|
||||||
|
|
||||||
const mapStateToProps = ({ publish }) => {
|
const { assetDefaults: { thumbnail: defaultThumbnail } } = siteConfig;
|
||||||
return {
|
|
||||||
file : publish.file,
|
const mapStateToProps = ({ show, publish: { file, thumbnail, fileError, isUpdate } }) => {
|
||||||
thumbnail: publish.thumbnail,
|
const obj = { file, thumbnail, fileError, isUpdate };
|
||||||
fileError: publish.error.file,
|
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 => {
|
const mapDispatchToProps = dispatch => {
|
||||||
|
|
|
@ -81,53 +81,70 @@ class Dropzone extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
|
const { dragOver, mouseOver, dimPreview } = this.state;
|
||||||
|
const { file, thumbnail, fileError, isUpdate, sourceUrl, fileExt } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className='dropzone-wrapper'>
|
<div>
|
||||||
<form>
|
{isUpdate && fileExt === 'mp4' ? (
|
||||||
<input
|
<p>Video updates are currently disabled. This feature will be available soon. You can edit metadata.</p>
|
||||||
className='input-file'
|
) : (
|
||||||
type='file'
|
<div className='dropzone-wrapper'>
|
||||||
id='file_input'
|
<form>
|
||||||
name='file_input'
|
<input
|
||||||
accept='video/*,image/*'
|
className='input-file'
|
||||||
onChange={this.handleFileInput}
|
type='file'
|
||||||
encType='multipart/form-data'
|
id='file_input'
|
||||||
/>
|
name='file_input'
|
||||||
</form>
|
accept='video/*,image/*'
|
||||||
<div
|
onChange={this.handleFileInput}
|
||||||
className={'dropzone' + (this.state.dragOver ? ' dropzone--drag-over' : '')}
|
encType='multipart/form-data'
|
||||||
onDrop={this.handleDrop}
|
|
||||||
onDragOver={this.handleDragOver}
|
|
||||||
onDragEnd={this.handleDragEnd}
|
|
||||||
onDragEnter={this.handleDragEnter}
|
|
||||||
onDragLeave={this.handleDragLeave}
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
onClick={this.handleClick}>
|
|
||||||
{this.props.file ? (
|
|
||||||
<div className={'dropzone-preview-wrapper'}>
|
|
||||||
<DropzonePreviewImage
|
|
||||||
dimPreview={this.state.dimPreview}
|
|
||||||
file={this.props.file}
|
|
||||||
thumbnail={this.props.thumbnail}
|
|
||||||
/>
|
/>
|
||||||
<div className={'dropzone-preview-overlay'}>
|
</form>
|
||||||
{ this.state.dragOver ? <DropzoneDropItDisplay /> : null }
|
<div
|
||||||
{ this.state.mouseOver ? (
|
className={'dropzone' + (dragOver ? ' dropzone--drag-over' : '')}
|
||||||
|
onDrop={this.handleDrop}
|
||||||
|
onDragOver={this.handleDragOver}
|
||||||
|
onDragEnd={this.handleDragEnd}
|
||||||
|
onDragEnter={this.handleDragEnter}
|
||||||
|
onDragLeave={this.handleDragLeave}
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseLeave={this.handleMouseLeave}
|
||||||
|
onClick={this.handleClick}>
|
||||||
|
{file || isUpdate ? (
|
||||||
|
<div className={'dropzone-preview-wrapper'}>
|
||||||
|
{file ? (
|
||||||
|
<DropzonePreviewImage
|
||||||
|
dimPreview={dimPreview}
|
||||||
|
file={file}
|
||||||
|
thumbnail={thumbnail}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DropzonePreviewImage
|
||||||
|
dimPreview
|
||||||
|
isUpdate
|
||||||
|
sourceUrl={sourceUrl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={'dropzone-preview-overlay'}>
|
||||||
|
{ dragOver ? <DropzoneDropItDisplay /> : null }
|
||||||
|
{ mouseOver ? (
|
||||||
|
<DropzoneInstructionsDisplay
|
||||||
|
fileError={fileError}
|
||||||
|
message={fileExt === 'mp4' ? 'Drag & drop new thumbnail' : null}
|
||||||
|
/>
|
||||||
|
) : null }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
dragOver ? <DropzoneDropItDisplay /> : (
|
||||||
<DropzoneInstructionsDisplay
|
<DropzoneInstructionsDisplay
|
||||||
fileError={this.props.fileError}
|
fileError={fileError}
|
||||||
/>
|
/>
|
||||||
) : null }
|
)
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
this.state.dragOver ? <DropzoneDropItDisplay /> : (
|
)}
|
||||||
<DropzoneInstructionsDisplay
|
|
||||||
fileError={this.props.fileError}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import { connect } from 'react-redux';
|
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';
|
import View from './view';
|
||||||
|
|
||||||
const mapStateToProps = ({ channel, publish }) => {
|
const mapStateToProps = ({ show, publish }) => {
|
||||||
return {
|
return {
|
||||||
file: publish.file,
|
file: publish.file,
|
||||||
|
isUpdate: publish.isUpdate,
|
||||||
|
hasChanged: publish.hasChanged,
|
||||||
|
asset: selectAsset(show),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
clearFile,
|
clearFile,
|
||||||
startPublish,
|
startPublish,
|
||||||
|
abandonClaim,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
||||||
|
|
|
@ -1,35 +1,75 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withRouter } from 'react-router-dom';
|
import {Link, withRouter} from 'react-router-dom';
|
||||||
import PublishUrlInput from '@containers/PublishUrlInput';
|
import PublishUrlInput from '@containers/PublishUrlInput';
|
||||||
import PublishThumbnailInput from '@containers/PublishThumbnailInput';
|
import PublishThumbnailInput from '@containers/PublishThumbnailInput';
|
||||||
import PublishMetadataInputs from '@containers/PublishMetadataInputs';
|
import PublishMetadataInputs from '@containers/PublishMetadataInputs';
|
||||||
import ChannelSelect from '@containers/ChannelSelect';
|
import ChannelSelect from '@containers/ChannelSelect';
|
||||||
import Row from '@components/Row';
|
import Row from '@components/Row';
|
||||||
|
import Label from '@components/Label';
|
||||||
|
import RowLabeled from '@components/RowLabeled';
|
||||||
import ButtonPrimaryJumbo from '@components/ButtonPrimaryJumbo';
|
import ButtonPrimaryJumbo from '@components/ButtonPrimaryJumbo';
|
||||||
import ButtonSecondary from '@components/ButtonSecondary';
|
import ButtonSecondary from '@components/ButtonSecondary';
|
||||||
import SpaceAround from '@components/SpaceAround';
|
import SpaceAround from '@components/SpaceAround';
|
||||||
import PublishFinePrint from '@components/PublishFinePrint';
|
import PublishFinePrint from '@components/PublishFinePrint';
|
||||||
|
import { SAVE } from '../../constants/confirmation_messages';
|
||||||
|
|
||||||
class PublishDetails extends React.Component {
|
class PublishDetails extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.onPublishSubmit = this.onPublishSubmit.bind(this);
|
this.onPublishSubmit = this.onPublishSubmit.bind(this);
|
||||||
|
this.abandonClaim = this.abandonClaim.bind(this);
|
||||||
|
this.onCancel = this.onCancel.bind(this);
|
||||||
}
|
}
|
||||||
onPublishSubmit () {
|
onPublishSubmit () {
|
||||||
this.props.startPublish(this.props.history);
|
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 () {
|
render () {
|
||||||
|
const {file, isUpdate, asset} = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row>
|
{isUpdate ? (asset && (
|
||||||
<PublishUrlInput />
|
<Row>
|
||||||
</Row>
|
<RowLabeled
|
||||||
|
label={
|
||||||
|
<Label value={'Channel:'} />
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<span className='text'>
|
||||||
|
{asset.claimData.channelName}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
)) : (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row>
|
||||||
|
<PublishUrlInput />
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<ChannelSelect />
|
<ChannelSelect />
|
||||||
</Row>
|
</Row>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
|
||||||
{ this.props.file.type === 'video/mp4' && (
|
{ file && file.type === 'video/mp4' && (
|
||||||
<Row>
|
<Row>
|
||||||
<PublishThumbnailInput />
|
<PublishThumbnailInput />
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -41,16 +81,27 @@ class PublishDetails extends React.Component {
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<ButtonPrimaryJumbo
|
<ButtonPrimaryJumbo
|
||||||
value={'Publish'}
|
value={isUpdate ? 'Update' : 'Publish'}
|
||||||
onClickHandler={this.onPublishSubmit}
|
onClickHandler={this.onPublishSubmit}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{isUpdate && (
|
||||||
|
<Row>
|
||||||
|
<SpaceAround>
|
||||||
|
<ButtonSecondary
|
||||||
|
value={'Abandon Claim'}
|
||||||
|
onClickHandler={this.abandonClaim}
|
||||||
|
/>
|
||||||
|
</SpaceAround>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<SpaceAround>
|
<SpaceAround>
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
value={'Cancel'}
|
value={'Cancel'}
|
||||||
onClickHandler={this.props.clearFile}
|
onClickHandler={this.onCancel}
|
||||||
/>
|
/>
|
||||||
</SpaceAround>
|
</SpaceAround>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -8,6 +8,7 @@ const mapStateToProps = ({ publish }) => {
|
||||||
description : publish.metadata.description,
|
description : publish.metadata.description,
|
||||||
license : publish.metadata.license,
|
license : publish.metadata.license,
|
||||||
nsfw : publish.metadata.nsfw,
|
nsfw : publish.metadata.nsfw,
|
||||||
|
isUpdate : publish.isUpdate,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,10 @@ class PublishMetadataInputs extends React.Component {
|
||||||
this.props.onMetadataChange(name, selectedOption);
|
this.props.onMetadataChange(name, selectedOption);
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
|
const { showMetadataInputs, description, isUpdate, nsfw } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.props.showMetadataInputs && (
|
{(showMetadataInputs || isUpdate) && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Row>
|
<Row>
|
||||||
<PublishDescriptionInput
|
<PublishDescriptionInput
|
||||||
|
@ -50,10 +51,12 @@ class PublishMetadataInputs extends React.Component {
|
||||||
</Row>
|
</Row>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
<ButtonSecondary
|
{!isUpdate && (
|
||||||
value={this.props.showMetadataInputs ? 'less' : 'more'}
|
<ButtonSecondary
|
||||||
onClickHandler={this.toggleShowInputs}
|
value={showMetadataInputs ? 'less' : 'more'}
|
||||||
/>
|
onClickHandler={this.toggleShowInputs}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ class PublishStatus extends React.Component {
|
||||||
{status === publishStates.LOAD_START &&
|
{status === publishStates.LOAD_START &&
|
||||||
<div className={'status'}>
|
<div className={'status'}>
|
||||||
<Row>
|
<Row>
|
||||||
<p>le is loading to server</p>
|
<p>File is loading to server</p>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<p className={'text--secondary'}>0%</p>
|
<p className={'text--secondary'}>0%</p>
|
||||||
|
@ -42,7 +42,7 @@ class PublishStatus extends React.Component {
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{status === publishStates.SUCCESS &&
|
{status === publishStates.SUCCEEDED &&
|
||||||
<div className={'status'}>
|
<div className={'status'}>
|
||||||
<Row>
|
<Row>
|
||||||
<p>Your publish is complete! You are being redirected to it now.</p>
|
<p>Your publish is complete! You are being redirected to it now.</p>
|
||||||
|
@ -71,6 +71,13 @@ class PublishStatus extends React.Component {
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
{status === publishStates.ABANDONING &&
|
||||||
|
<div className={'status'}>
|
||||||
|
<Row>
|
||||||
|
<p>Your claim is being abandoned.</p>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import View from './view';
|
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 {
|
return {
|
||||||
disabled: publish.disabled,
|
disabled: publish.disabled,
|
||||||
file : publish.file,
|
file: publish.file,
|
||||||
status : publish.status.status,
|
status: publish.status.status,
|
||||||
|
isUpdate: publish.isUpdate,
|
||||||
|
hasChanged: publish.hasChanged,
|
||||||
|
uri,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,34 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withRouter, Prompt } from 'react-router';
|
||||||
import Dropzone from '@containers/Dropzone';
|
import Dropzone from '@containers/Dropzone';
|
||||||
import PublishPreview from '@components/PublishPreview';
|
import PublishPreview from '@components/PublishPreview';
|
||||||
import PublishStatus from '@containers/PublishStatus';
|
import PublishStatus from '@containers/PublishStatus';
|
||||||
import PublishDisabledMessage from '@containers/PublishDisabledMessage';
|
import PublishDisabledMessage from '@containers/PublishDisabledMessage';
|
||||||
|
import { SAVE } from '../../constants/confirmation_messages';
|
||||||
|
|
||||||
class PublishTool extends React.Component {
|
class PublishTool extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
if (this.props.disabled) {
|
const {disabled, file, isUpdate, hasChanged, uri, status, location: currentLocation} = this.props;
|
||||||
|
if (disabled) {
|
||||||
return (
|
return (
|
||||||
<PublishDisabledMessage />
|
<PublishDisabledMessage />
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (this.props.file) {
|
if (file || isUpdate) {
|
||||||
if (this.props.status) {
|
if (status) {
|
||||||
return (
|
return (
|
||||||
<PublishStatus />
|
<PublishStatus />
|
||||||
);
|
);
|
||||||
} else {
|
} 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 />;
|
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 React from 'react';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import PageLayout from '@components/PageLayout';
|
import PageLayout from '@components/PageLayout';
|
||||||
import HorizontalSplit from '@components/HorizontalSplit';
|
import HorizontalSplit from '@components/HorizontalSplit';
|
||||||
import AboutSpeechOverview from '@components/AboutSpeechOverview';
|
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 { connect } from 'react-redux';
|
||||||
import { onHandleShowHomepage } from '../../actions/show';
|
import { onHandleShowHomepage } from '../../actions/show';
|
||||||
|
import { clearFile } from '../../actions/publish';
|
||||||
import View from './view';
|
import View from './view';
|
||||||
|
|
||||||
const mapStateToProps = ({ show, site, channel }) => {
|
const mapStateToProps = ({ show, site, channel, publish }) => {
|
||||||
return {
|
return {
|
||||||
error : show.request.error,
|
error : show.request.error,
|
||||||
requestType: show.request.type,
|
requestType: show.request.type,
|
||||||
homeChannel: site.publishOnlyApproved && !channel.loggedInChannel.name ? `${site.approvedChannels[0].name}:${site.approvedChannels[0].longId}` : null,
|
homeChannel: site.publishOnlyApproved && !channel.loggedInChannel.name ? `${site.approvedChannels[0].name}:${site.approvedChannels[0].longId}` : null,
|
||||||
|
isUpdate : publish.isUpdate,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
onHandleShowHomepage,
|
onHandleShowHomepage,
|
||||||
|
clearFile,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
|
@ -4,16 +4,9 @@ import PublishTool from '@containers/PublishTool';
|
||||||
import ContentPageWrapper from '@pages/ContentPageWrapper';
|
import ContentPageWrapper from '@pages/ContentPageWrapper';
|
||||||
|
|
||||||
class HomePage extends React.Component {
|
class HomePage extends React.Component {
|
||||||
componentDidMount () {
|
componentWillUnmount () {
|
||||||
this.props.onHandleShowHomepage(this.props.match.params);
|
this.props.clearFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (nextProps.match.params !== this.props.match.params) {
|
|
||||||
this.props.onHandleShowHomepage(nextProps.match.params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { homeChannel } = this.props;
|
const { homeChannel } = this.props;
|
||||||
return homeChannel ? (
|
return homeChannel ? (
|
||||||
|
|
|
@ -1,20 +1,10 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { selectAsset } from '../../selectors/show';
|
||||||
import View from './view';
|
import View from './view';
|
||||||
|
|
||||||
const mapStateToProps = ({ show }) => {
|
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 {
|
return {
|
||||||
asset,
|
asset: selectAsset(show),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PageLayout from '@components/PageLayout';
|
import PageLayout from '@components/PageLayout';
|
||||||
|
|
||||||
import VerticalCollapsibleSplit from '@components/VerticalCollapsibleSplit';
|
import VerticalCollapsibleSplit from '@components/VerticalCollapsibleSplit';
|
||||||
import AssetDisplay from '@containers/AssetDisplay';
|
import AssetDisplay from '@containers/AssetDisplay';
|
||||||
import AssetInfo from '@containers/AssetInfo';
|
|
||||||
import ErrorPage from '@pages/ErrorPage';
|
import ErrorPage from '@pages/ErrorPage';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -41,6 +41,8 @@ const initialState = {
|
||||||
license : '',
|
license : '',
|
||||||
nsfw : false,
|
nsfw : false,
|
||||||
},
|
},
|
||||||
|
isUpdate: false,
|
||||||
|
hasChanged: false,
|
||||||
thumbnail: null,
|
thumbnail: null,
|
||||||
thumbnailChannel,
|
thumbnailChannel,
|
||||||
thumbnailChannelId,
|
thumbnailChannelId,
|
||||||
|
@ -49,8 +51,9 @@ const initialState = {
|
||||||
export default function (state = initialState, action) {
|
export default function (state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.FILE_SELECTED:
|
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,
|
file: action.data,
|
||||||
|
hasChanged: true,
|
||||||
});
|
});
|
||||||
case actions.FILE_CLEAR:
|
case actions.FILE_CLEAR:
|
||||||
return initialState;
|
return initialState;
|
||||||
|
@ -59,14 +62,17 @@ export default function (state = initialState, action) {
|
||||||
metadata: Object.assign({}, state.metadata, {
|
metadata: Object.assign({}, state.metadata, {
|
||||||
[action.data.name]: action.data.value,
|
[action.data.name]: action.data.value,
|
||||||
}),
|
}),
|
||||||
|
hasChanged: true,
|
||||||
});
|
});
|
||||||
case actions.CLAIM_UPDATE:
|
case actions.CLAIM_UPDATE:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
claim: action.data,
|
claim: action.data,
|
||||||
|
hasChanged: true,
|
||||||
});
|
});
|
||||||
case actions.SET_PUBLISH_IN_CHANNEL:
|
case actions.SET_PUBLISH_IN_CHANNEL:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
publishInChannel: action.channel,
|
publishInChannel: action.channel,
|
||||||
|
hasChanged: true,
|
||||||
});
|
});
|
||||||
case actions.PUBLISH_STATUS_UPDATE:
|
case actions.PUBLISH_STATUS_UPDATE:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
|
@ -83,13 +89,26 @@ export default function (state = initialState, action) {
|
||||||
selectedChannel: action.data,
|
selectedChannel: action.data,
|
||||||
});
|
});
|
||||||
case actions.TOGGLE_METADATA_INPUTS:
|
case actions.TOGGLE_METADATA_INPUTS:
|
||||||
return Object.assign({}, state, {
|
return {
|
||||||
|
...state,
|
||||||
showMetadataInputs: action.data,
|
showMetadataInputs: action.data,
|
||||||
});
|
};
|
||||||
case actions.THUMBNAIL_NEW:
|
case actions.THUMBNAIL_NEW:
|
||||||
return Object.assign({}, state, {
|
return {
|
||||||
|
...state,
|
||||||
thumbnail: action.data,
|
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:
|
default:
|
||||||
return state;
|
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
|
// channel data
|
||||||
case actions.CHANNEL_ADD:
|
case actions.CHANNEL_ADD:
|
||||||
return Object.assign({}, state, {
|
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, {
|
return Object.assign({}, state, {
|
||||||
channelList: Object.assign({}, state.channelList, {
|
channelList: Object.assign({}, state.channelList, {
|
||||||
[action.data.channelListId]: Object.assign({}, state.channelList[action.data.channelListId], {
|
[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 { selectPublishState } from '../selectors/publish';
|
||||||
import { selectChannelState } from '../selectors/channel';
|
import { selectChannelState } from '../selectors/channel';
|
||||||
import { selectSiteState } from '../selectors/site';
|
import { selectSiteState } from '../selectors/site';
|
||||||
|
import { selectShowState, selectAsset } from '../selectors/show';
|
||||||
import { validateChannelSelection, validateNoPublishErrors } from '../utils/validate';
|
import { validateChannelSelection, validateNoPublishErrors } from '../utils/validate';
|
||||||
import { createPublishMetadata, createPublishFormData, createThumbnailUrl } from '../utils/publish';
|
import { createPublishMetadata, createPublishFormData, createThumbnailUrl } from '../utils/publish';
|
||||||
import { makePublishRequestChannel } from '../channels/publish';
|
import { makePublishRequestChannel } from '../channels/publish';
|
||||||
|
|
||||||
function * publishFile (action) {
|
function * publishFile (action) {
|
||||||
const { history } = action.data;
|
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 { loggedInChannel } = yield select(selectChannelState);
|
||||||
const { host } = yield select(selectSiteState);
|
const { host } = yield select(selectSiteState);
|
||||||
|
|
||||||
|
let show, asset;
|
||||||
|
if (isUpdate) {
|
||||||
|
show = yield select(selectShowState);
|
||||||
|
asset = selectAsset(show);
|
||||||
|
}
|
||||||
// validate the channel selection
|
// validate the channel selection
|
||||||
try {
|
try {
|
||||||
validateChannelSelection(publishInChannel, selectedChannel, loggedInChannel);
|
validateChannelSelection(publishInChannel, selectedChannel, loggedInChannel);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return yield put(updateError('channel', error.message));
|
return yield put(updateError('channel', error.message));
|
||||||
};
|
}
|
||||||
// validate publish parameters
|
// validate publish parameters
|
||||||
try {
|
try {
|
||||||
validateNoPublishErrors(publishToolErrors);
|
validateNoPublishErrors(publishToolErrors);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return console.log('publish error:', error.message);
|
return console.log('publish error:', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let publishMetadata, publishFormData, publishChannel;
|
||||||
// create metadata
|
// 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) {
|
if (thumbnail) {
|
||||||
// add thumbnail to publish metadata
|
// add thumbnail to publish metadata
|
||||||
publishMetadata['thumbnail'] = createThumbnailUrl(thumbnailChannel, thumbnailChannelId, claim, host);
|
publishMetadata['thumbnail'] = createThumbnailUrl(thumbnailChannel, thumbnailChannelId, claim, host);
|
||||||
}
|
}
|
||||||
// create form data for main publish
|
// create form data for main publish
|
||||||
const publishFormData = createPublishFormData(file, thumbnail, publishMetadata);
|
publishFormData = createPublishFormData(file, thumbnail, publishMetadata);
|
||||||
// make the publish request
|
// make the publish request
|
||||||
const publishChannel = yield call(makePublishRequestChannel, publishFormData);
|
publishChannel = yield call(makePublishRequestChannel, publishFormData, isUpdate);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const {loadStart, progress, load, success, error: publishError} = yield take(publishChannel);
|
const {loadStart, progress, load, success, error: publishError} = yield take(publishChannel);
|
||||||
if (publishError) {
|
if (publishError) {
|
||||||
|
@ -43,7 +63,21 @@ function * publishFile (action) {
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
yield put(clearFile());
|
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) {
|
if (loadStart) {
|
||||||
yield put(updatePublishStatus(publishStates.LOAD_START, null));
|
yield put(updatePublishStatus(publishStates.LOAD_START, null));
|
||||||
|
@ -55,7 +89,7 @@ function * publishFile (action) {
|
||||||
yield put(updatePublishStatus(publishStates.PUBLISHING, null));
|
yield put(updatePublishStatus(publishStates.PUBLISHING, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export function * watchPublishStart () {
|
export function * watchPublishStart () {
|
||||||
yield takeLatest(actions.PUBLISH_START, publishFile);
|
yield takeLatest(actions.PUBLISH_START, publishFile);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { watchUpdateChannelAvailability } from './updateChannelAvailability';
|
||||||
import { watchChannelCreate } from './createChannel';
|
import { watchChannelCreate } from './createChannel';
|
||||||
import { watchChannelLoginCheck } from './checkForLoggedInChannel';
|
import { watchChannelLoginCheck } from './checkForLoggedInChannel';
|
||||||
import { watchChannelLogout } from './logoutChannel';
|
import { watchChannelLogout } from './logoutChannel';
|
||||||
|
import { watchAbandonClaim } from './abandon';
|
||||||
|
|
||||||
export function * rootSaga () {
|
export function * rootSaga () {
|
||||||
yield all([
|
yield all([
|
||||||
|
@ -27,5 +28,6 @@ export function * rootSaga () {
|
||||||
watchChannelLoginCheck(),
|
watchChannelLoginCheck(),
|
||||||
watchChannelLogout(),
|
watchChannelLogout(),
|
||||||
watchUpdateAssetViews(),
|
watchUpdateAssetViews(),
|
||||||
|
watchAbandonClaim(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
export const selectAsset = (show) => {
|
export const selectAsset = show => {
|
||||||
const request = show.requestList[show.request.id];
|
const requestId = show.request.id;
|
||||||
const assetKey = request.key;
|
let asset;
|
||||||
return show.assetList[assetKey];
|
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) => {
|
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) => {
|
export const createPublishFormData = (file, thumbnail, metadata) => {
|
||||||
let fd = new FormData();
|
let fd = new FormData();
|
||||||
// append file
|
// append file
|
||||||
fd.append('file', file);
|
if (file) {
|
||||||
|
fd.append('file', file);
|
||||||
|
}
|
||||||
// append thumbnail
|
// append thumbnail
|
||||||
if (thumbnail) {
|
if (thumbnail) {
|
||||||
fd.append('thumbnail', thumbnail);
|
fd.append('thumbnail', thumbnail);
|
||||||
|
@ -31,5 +33,5 @@ export const createPublishFormData = (file, thumbnail, metadata) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createThumbnailUrl = (channel, channelId, claim, host) => {
|
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_
|
_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
|
## 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.
|
### 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
|
$ mkdir Logo
|
||||||
$ cd Logo
|
$ cd Logo
|
||||||
$ touch index.jsx
|
$ touch index.jsx
|
||||||
|
|
177
package-lock.json
generated
177
package-lock.json
generated
|
@ -1003,6 +1003,7 @@
|
||||||
"version": "5.5.2",
|
"version": "5.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||||
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"co": "4.6.0",
|
"co": "4.6.0",
|
||||||
"fast-deep-equal": "1.1.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": {
|
"boxen": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
|
||||||
|
@ -2711,7 +2704,8 @@
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"coa": {
|
"coa": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
|
@ -3035,24 +3029,6 @@
|
||||||
"which": "1.3.1"
|
"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": {
|
"crypto-browserify": {
|
||||||
"version": "3.12.0",
|
"version": "3.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||||
|
@ -4556,7 +4532,8 @@
|
||||||
"fast-deep-equal": {
|
"fast-deep-equal": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
|
"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": {
|
"fast-json-stable-stringify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
@ -5639,6 +5616,7 @@
|
||||||
"version": "5.0.3",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
||||||
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "5.5.2",
|
"ajv": "5.5.2",
|
||||||
"har-schema": "2.0.0"
|
"har-schema": "2.0.0"
|
||||||
|
@ -5733,17 +5711,6 @@
|
||||||
"minimalistic-assert": "1.0.1"
|
"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": {
|
"he": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
|
||||||
|
@ -5823,11 +5790,6 @@
|
||||||
"minimalistic-crypto-utils": "1.0.1"
|
"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": {
|
"hoist-non-react-statics": {
|
||||||
"version": "2.5.5",
|
"version": "2.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||||
|
@ -6523,7 +6485,8 @@
|
||||||
"json-schema-traverse": {
|
"json-schema-traverse": {
|
||||||
"version": "0.3.1",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
|
"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": {
|
"json-stable-stringify-without-jsonify": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -10402,7 +10365,8 @@
|
||||||
"oauth-sign": {
|
"oauth-sign": {
|
||||||
"version": "0.8.2",
|
"version": "0.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
"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": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -11411,8 +11375,7 @@
|
||||||
"psl": {
|
"psl": {
|
||||||
"version": "1.1.29",
|
"version": "1.1.29",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
|
||||||
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==",
|
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"pstree.remy": {
|
"pstree.remy": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
@ -11958,34 +11921,6 @@
|
||||||
"is-finite": "1.0.2"
|
"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": {
|
"require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
@ -12837,14 +12772,6 @@
|
||||||
"kind-of": "3.2.2"
|
"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": {
|
"sort-keys": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||||
|
@ -13454,6 +13381,7 @@
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
|
||||||
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
|
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"punycode": "1.4.1"
|
"punycode": "1.4.1"
|
||||||
}
|
}
|
||||||
|
@ -13747,13 +13675,86 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"universal-analytics": {
|
"universal-analytics": {
|
||||||
"version": "0.4.17",
|
"version": "0.4.20",
|
||||||
"resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.17.tgz",
|
"resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz",
|
||||||
"integrity": "sha512-N2JFymxv4q2N5Wmftc5JCcM5t1tp+sc1kqeDRhDL4XLY5X6PBZ0kav2wvVUZJJMvmSq3WXrmzDu062p+cSFYfQ==",
|
"integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "3.1.0",
|
"debug": "3.1.0",
|
||||||
"request": "2.86.0",
|
"request": "2.88.0",
|
||||||
"uuid": "3.3.2"
|
"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": {
|
"universalify": {
|
||||||
|
|
10
package.json
10
package.json
|
@ -22,14 +22,14 @@
|
||||||
"test": "mocha --recursive",
|
"test": "mocha --recursive",
|
||||||
"test:no-lbc": "npm test -- --grep @usesLbc --invert",
|
"test:no-lbc": "npm test -- --grep @usesLbc --invert",
|
||||||
"test:server": "mocha --recursive './server/**/*.test.js'",
|
"test:server": "mocha --recursive './server/**/*.test.js'",
|
||||||
"transpile": "builder concurrent transpile:server transpile:client transpile:client_custom",
|
"transpile": "builder concurrent transpile:server transpile:client transpile:custom",
|
||||||
"transpile:dev": "builder concurrent transpile:server:dev transpile:client:dev transpile:client_custom:dev",
|
"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": "babel server/render/src -d server/render/build",
|
||||||
"transpile:server:dev": "babel server/render/src -w -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": "babel client/src -d client/build",
|
||||||
"transpile:client:dev": "babel client/src -w -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:custom": "babel site/custom/src -d site/custom/build",
|
||||||
"transpile:client_custom:dev": "babel site/client_custom/src -w -d site/client_custom/build"
|
"transpile:custom:dev": "babel site/custom/src -w -d site/custom/build"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
"redux-saga": "^0.16.0",
|
"redux-saga": "^0.16.0",
|
||||||
"sequelize": "^4.38.0",
|
"sequelize": "^4.38.0",
|
||||||
"sequelize-cli": "^4.0.0",
|
"sequelize-cli": "^4.0.0",
|
||||||
"universal-analytics": "^0.4.17",
|
"universal-analytics": "^0.4.20",
|
||||||
"webpack": "^3.10.0",
|
"webpack": "^3.10.0",
|
||||||
"webpack-merge": "^4.1.4",
|
"webpack-merge": "^4.1.4",
|
||||||
"webpack-node-externals": "^1.7.2",
|
"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}`);
|
logger$1.debug(`claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
|
||||||
return await table.findAll({
|
return await table.findAll({
|
||||||
where: { name: claimName },
|
where: { name: claimName },
|
||||||
|
@ -860,7 +860,12 @@ var claimQueries = (db, table, sequelize) => ({
|
||||||
throw new Error('No claim(s) found with that claim name');
|
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) => {
|
getOutpoint: async (name, claimId) => {
|
||||||
logger$1.debug(`finding outpoint for ${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}`);
|
logger.debug(`claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
|
||||||
return await table.findAll({
|
return await table.findAll({
|
||||||
where: { name: claimName },
|
where: { name: claimName },
|
||||||
|
@ -59,7 +59,12 @@ export default (db, table, sequelize) => ({
|
||||||
throw new Error('No claim(s) found with that claim name');
|
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) => {
|
getOutpoint: async (name, claimId) => {
|
||||||
logger.debug(`finding outpoint for ${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 getChannelClaims = async (channelName, channelShortId, page) => {
|
||||||
const channelId = await chainquery.claim.queries.getLongClaimId(channelName, channelShortId);
|
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 processingChannelClaims = channelClaims ? channelClaims.map((claim) => getClaimData(claim)) : [];
|
||||||
const processedChannelClaims = await Promise.all(processingChannelClaims);
|
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 { handleErrorResponse } = require('../../../utils/errorHandlers.js');
|
||||||
const getClaimData = require('server/utils/getClaimData');
|
const getClaimData = require('server/utils/getClaimData');
|
||||||
|
const fetchClaimData = require('server/utils/fetchClaimData');
|
||||||
const chainquery = require('chainquery');
|
const chainquery = require('chainquery');
|
||||||
const db = require('server/models');
|
const db = require('server/models');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
route to return data for a claim
|
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 claimData = async ({ ip, originalUrl, body, params }, res) => {
|
||||||
const claimName = params.claimName;
|
|
||||||
let claimId = params.claimId;
|
|
||||||
if (claimId === 'none') claimId = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let resolvedClaim = await chainquery.claim.queries.resolveClaim(claimName, claimId).catch(() => {});
|
const resolvedClaim = await fetchClaimData(params);
|
||||||
|
|
||||||
if(!resolvedClaim) {
|
|
||||||
resolvedClaim = await db.Claim.resolveClaim(claimName, claimId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resolvedClaim) {
|
if (!resolvedClaim) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
|
|
|
@ -11,7 +11,7 @@ const createPublishParams = (filePath, name, title, description, license, nsfw,
|
||||||
}
|
}
|
||||||
// provide default for license
|
// provide default for license
|
||||||
if (license === null || license.trim() === '') {
|
if (license === null || license.trim() === '') {
|
||||||
license = ' '; // default to empty string
|
license = ''; // default to empty string
|
||||||
}
|
}
|
||||||
// create the basic publish params
|
// create the basic publish params
|
||||||
const publishParams = {
|
const publishParams = {
|
||||||
|
|
|
@ -17,6 +17,9 @@ const parsePublishApiRequestBody = require('./parsePublishApiRequestBody.js');
|
||||||
const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
|
const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
|
||||||
const authenticateUser = require('./authentication.js');
|
const authenticateUser = require('./authentication.js');
|
||||||
|
|
||||||
|
const chainquery = require('chainquery');
|
||||||
|
const createCanonicalLink = require('../../../../../utils/createCanonicalLink');
|
||||||
|
|
||||||
const CLAIM_TAKEN = 'CLAIM_TAKEN';
|
const CLAIM_TAKEN = 'CLAIM_TAKEN';
|
||||||
const UNAPPROVED_CHANNEL = 'UNAPPROVED_CHANNEL';
|
const UNAPPROVED_CHANNEL = 'UNAPPROVED_CHANNEL';
|
||||||
|
|
||||||
|
@ -42,7 +45,25 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// define variables
|
// 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
|
// record the start time of the request
|
||||||
gaStartTime = Date.now();
|
gaStartTime = Date.now();
|
||||||
// validate the body and files of the request
|
// validate the body and files of the request
|
||||||
|
@ -64,6 +85,7 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
|
||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
checkClaimAvailability(name),
|
checkClaimAvailability(name),
|
||||||
createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId),
|
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(thumbnailPublishParams, thumbnailFileName, thumbnailFileType);
|
||||||
}
|
}
|
||||||
// publish the asset
|
// 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({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'publish completed successfully',
|
message: 'publish completed successfully',
|
||||||
data : {
|
data : {
|
||||||
name,
|
name,
|
||||||
claimId : result.claim_id,
|
claimId,
|
||||||
url : `${host}/${result.claim_id}/${name}`, // for backwards compatability with app
|
url : `${host}${canonicalUrl}`, // for backwards compatability with app
|
||||||
showUrl : `${host}/${result.claim_id}/${name}`,
|
showUrl : `${host}${canonicalUrl}`,
|
||||||
serveUrl: `${host}/${result.claim_id}/${name}${fileExtension}`,
|
serveUrl: `${host}${canonicalUrl}${fileExtension}`,
|
||||||
lbryTx : result,
|
pushTo : canonicalUrl,
|
||||||
|
claimData,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// record the publish end time and send to google analytics
|
// record the publish end time and send to google analytics
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const validateFileTypeAndSize = require('./validateFileTypeAndSize.js');
|
const validateFileTypeAndSize = require('./validateFileTypeAndSize.js');
|
||||||
|
|
||||||
const parsePublishApiRequestFiles = ({file, thumbnail}) => {
|
const parsePublishApiRequestFiles = ({file, thumbnail}, isUpdate) => {
|
||||||
// make sure a file was provided
|
// make sure a file was provided
|
||||||
if (!file) {
|
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');
|
throw new Error('no file with key of [file] found in request');
|
||||||
}
|
}
|
||||||
if (!file.path) {
|
if (!file.path) {
|
||||||
|
@ -28,18 +38,24 @@ const parsePublishApiRequestFiles = ({file, thumbnail}) => {
|
||||||
if (/'/.test(file.name)) {
|
if (/'/.test(file.name)) {
|
||||||
throw new Error('apostrophes are not allowed in the file name');
|
throw new Error('apostrophes are not allowed in the file name');
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the file
|
// validate the file
|
||||||
validateFileTypeAndSize(file);
|
if (file) validateFileTypeAndSize(file);
|
||||||
// return results
|
// return results
|
||||||
return {
|
const obj = {
|
||||||
fileName : file.name,
|
fileName : file.name,
|
||||||
filePath : file.path,
|
filePath : file.path,
|
||||||
fileExtension : path.extname(file.path),
|
fileExtension: path.extname(file.path),
|
||||||
fileType : file.type,
|
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;
|
module.exports = parsePublishApiRequestFiles;
|
||||||
|
|
|
@ -1,81 +1,72 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const { publishClaim } = require('../../../../lbrynet');
|
|
||||||
const db = require('../../../../models');
|
const db = require('../../../../models');
|
||||||
|
const { publishClaim } = require('../../../../lbrynet');
|
||||||
const { createFileRecordDataAfterPublish } = require('../../../../models/utils/createFileRecordData.js');
|
const { createFileRecordDataAfterPublish } = require('../../../../models/utils/createFileRecordData.js');
|
||||||
const { createClaimRecordDataAfterPublish } = require('../../../../models/utils/createClaimRecordData.js');
|
const { createClaimRecordDataAfterPublish } = require('../../../../models/utils/createClaimRecordData.js');
|
||||||
const deleteFile = require('./deleteFile.js');
|
const deleteFile = require('./deleteFile.js');
|
||||||
|
|
||||||
const publish = (publishParams, fileName, fileType) => {
|
const publish = async (publishParams, fileName, fileType) => {
|
||||||
return new Promise((resolve, reject) => {
|
let publishResults;
|
||||||
let publishResults, certificateId, channelName;
|
let channel;
|
||||||
// publish the file
|
let fileRecord;
|
||||||
return publishClaim(publishParams)
|
let newFile = Boolean(publishParams.file_path);
|
||||||
.then(result => {
|
|
||||||
logger.info(`Successfully published ${publishParams.name} ${fileName}`, result);
|
|
||||||
|
|
||||||
// Support new daemon, TODO: remove
|
try {
|
||||||
publishResults = result.output && result.output.claim_id ? result.output : result;
|
publishResults = await publishClaim(publishParams);
|
||||||
|
logger.info(`Successfully published ${publishParams.name} ${fileName}`, publishResults);
|
||||||
// get the channel information
|
const outpoint = `${publishResults.output.txid}:${publishResults.output.nout}`;
|
||||||
if (publishParams.channel_name) {
|
// get the channel information
|
||||||
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
|
if (publishParams.channel_name) {
|
||||||
return db.Channel.findOne({
|
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
|
||||||
where: {
|
channel = await db.Channel.findOne({
|
||||||
channelName: publishParams.channel_name,
|
where: {
|
||||||
},
|
channelName: publishParams.channel_name,
|
||||||
});
|
},
|
||||||
} else {
|
|
||||||
logger.debug('this claim was not published in a channel');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(channel => {
|
|
||||||
// set channel information
|
|
||||||
certificateId = null;
|
|
||||||
channelName = null;
|
|
||||||
if (channel) {
|
|
||||||
certificateId = channel.channelClaimId;
|
|
||||||
channelName = channel.channelName;
|
|
||||||
}
|
|
||||||
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([
|
|
||||||
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([
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
|
channel = null;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [file, claim] = await Promise.all([
|
||||||
|
db.upsert(db.File, fileRecord, upsertCriteria, 'File'),
|
||||||
|
db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim'),
|
||||||
|
]);
|
||||||
|
logger.info(`File and Claim records successfully created (${publishParams.name})`);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
file.setClaim(claim),
|
||||||
|
claim.setFile(file),
|
||||||
|
]);
|
||||||
|
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;
|
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 logger = require('winston');
|
||||||
const { apiHost, apiPort, getTimeout } = require('@config/lbryConfig');
|
const { apiHost, apiPort, getTimeout } = require('@config/lbryConfig');
|
||||||
const lbrynetUri = 'http://' + apiHost + ':' + apiPort;
|
const lbrynetUri = 'http://' + apiHost + ':' + apiPort;
|
||||||
|
const db = require('../models');
|
||||||
const { chooseGaLbrynetPublishLabel, sendGATimingEvent } = require('../utils/googleAnalytics.js');
|
const { chooseGaLbrynetPublishLabel, sendGATimingEvent } = require('../utils/googleAnalytics.js');
|
||||||
const handleLbrynetResponse = require('./utils/handleLbrynetResponse.js');
|
const handleLbrynetResponse = require('./utils/handleLbrynetResponse.js');
|
||||||
const { publishing } = require('@config/siteConfig');
|
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) {
|
getClaimList (claimName) {
|
||||||
logger.debug(`lbryApi >> Getting claim_list for "${claimName}"`);
|
logger.debug(`lbryApi >> Getting claim_list for "${claimName}"`);
|
||||||
const gaStartTime = Date.now();
|
const gaStartTime = Date.now();
|
||||||
|
@ -75,7 +91,13 @@ module.exports = {
|
||||||
})
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
sendGATimingEvent('lbrynet', 'resolveUri', 'RESOLVE', gaStartTime, Date.now());
|
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);
|
reject(data.result[uri].error);
|
||||||
} else { // if no errors, resolve
|
} else { // if no errors, resolve
|
||||||
resolve(data.result[uri]);
|
resolve(data.result[uri]);
|
||||||
|
|
|
@ -10,8 +10,14 @@ function logMetricsMiddleware(req, res, next) {
|
||||||
let referrer = req.get('referrer');
|
let referrer = req.get('referrer');
|
||||||
|
|
||||||
if(referrer && referrer.length > 255) {
|
if(referrer && referrer.length > 255) {
|
||||||
// Attempt to "safely" clamp long URLs
|
try {
|
||||||
referrer = /(.*?)#.*/.exec(referrer)[1];
|
// 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) {
|
if(referrer.length > 255) {
|
||||||
logger.warn('Request refferer exceeds 255 characters:', referrer);
|
logger.warn('Request refferer exceeds 255 characters:', referrer);
|
||||||
|
|
|
@ -28,7 +28,7 @@ async function createFileRecordDataAfterGet (resolveResult, getResult) {
|
||||||
filePath,
|
filePath,
|
||||||
fileType,
|
fileType,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
async function createFileRecordDataAfterPublish (fileName, fileType, publishParams, publishResults) {
|
async function createFileRecordDataAfterPublish (fileName, fileType, publishParams, publishResults) {
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -13,6 +13,8 @@ const claimGet = require('../../controllers/api/claim/get');
|
||||||
const claimList = require('../../controllers/api/claim/list');
|
const claimList = require('../../controllers/api/claim/list');
|
||||||
const claimLongId = require('../../controllers/api/claim/longId');
|
const claimLongId = require('../../controllers/api/claim/longId');
|
||||||
const claimPublish = require('../../controllers/api/claim/publish');
|
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 claimResolve = require('../../controllers/api/claim/resolve');
|
||||||
const claimShortId = require('../../controllers/api/claim/shortId');
|
const claimShortId = require('../../controllers/api/claim/shortId');
|
||||||
const claimViews = require('../../controllers/api/claim/views');
|
const claimViews = require('../../controllers/api/claim/views');
|
||||||
|
@ -29,12 +31,10 @@ const getOEmbedData = require('../../controllers/api/oEmbed');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// homepage routes
|
// homepage routes
|
||||||
'/api/homepage/data/channels': { controller: [ torCheckMiddleware, channelData ] },
|
'/api/homepage/data/channels': { controller: [ torCheckMiddleware, channelData ] },
|
||||||
|
|
||||||
// channel routes
|
// channel routes
|
||||||
'/api/channel/availability/:name': { controller: [ torCheckMiddleware, channelAvailability ] },
|
'/api/channel/availability/:name': { controller: [ torCheckMiddleware, channelAvailability ] },
|
||||||
'/api/channel/short-id/:longId/:name': { controller: [ torCheckMiddleware, channelShortId ] },
|
'/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/data/:channelName/:channelClaimId': { controller: [ torCheckMiddleware, channelData ] },
|
|
||||||
'/api/channel/claims/:channelName/:channelClaimId/:page': { controller: [ torCheckMiddleware, channelClaims ] },
|
'/api/channel/claims/:channelName/:channelClaimId/:page': { controller: [ torCheckMiddleware, channelClaims ] },
|
||||||
|
|
||||||
// sepcial routes
|
// sepcial routes
|
||||||
|
@ -47,6 +47,8 @@ module.exports = {
|
||||||
'/api/claim/list/:name': { controller: [ torCheckMiddleware, claimList ] },
|
'/api/claim/list/:name': { controller: [ torCheckMiddleware, claimList ] },
|
||||||
'/api/claim/long-id': { method: 'post', controller: [ torCheckMiddleware, claimLongId ] }, // note: should be a 'get'
|
'/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/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/resolve/:name/:claimId': { controller: [ torCheckMiddleware, claimResolve ] },
|
||||||
'/api/claim/short-id/:longId/:name': { controller: [ torCheckMiddleware, claimShortId ] },
|
'/api/claim/short-id/:longId/:name': { controller: [ torCheckMiddleware, claimShortId ] },
|
||||||
'/api/claim/views/:claimId': { controller: [ torCheckMiddleware, claimViews ] },
|
'/api/claim/views/:claimId': { controller: [ torCheckMiddleware, claimViews ] },
|
||||||
|
|
|
@ -15,6 +15,7 @@ module.exports = {
|
||||||
'/trending': { controller: redirect('/popular') },
|
'/trending': { controller: redirect('/popular') },
|
||||||
'/popular': { controller: handlePageRequest },
|
'/popular': { controller: handlePageRequest },
|
||||||
'/new': { controller: handlePageRequest },
|
'/new': { controller: handlePageRequest },
|
||||||
|
'/edit/:claimId': { controller: handlePageRequest },
|
||||||
'/multisite': { controller: handlePageRequest },
|
'/multisite': { controller: handlePageRequest },
|
||||||
'/video-embed/:name/:claimId/:config?': { controller: handleVideoEmbedRequest }, // for twitter
|
'/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,
|
claimId: data.claim_id || data.claimId,
|
||||||
fileExt: data.generated_extension || data.fileExt,
|
fileExt: data.generated_extension || data.fileExt,
|
||||||
description: data.description,
|
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,
|
outpoint: data.transaction_hash_id || data.outpoint,
|
||||||
host,
|
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 { statSync, existsSync, readdirSync } = require('fs');
|
||||||
const { join, resolve } = require('path');
|
const { join, resolve } = require('path');
|
||||||
const DEFAULT_ROOT = 'client/build';
|
const DEFAULT_ROOT = 'client/build';
|
||||||
const CUSTOM_ROOT = 'site/client_custom/build';
|
const CUSTOM_ROOT = 'site/custom/build';
|
||||||
const CUSTOM_SCSS_ROOT = 'site/client_custom/scss';
|
const CUSTOM_SCSS_ROOT = 'site/custom/scss';
|
||||||
|
|
||||||
const getFolders = path => {
|
const getFolders = path => {
|
||||||
if (existsSync(path)) {
|
if (existsSync(path)) {
|
||||||
|
|
Loading…
Reference in a new issue