allow claims to be updated and abandoned
This commit is contained in:
parent
462528ef03
commit
34a66f1968
39 changed files with 779 additions and 161 deletions
|
@ -14,6 +14,12 @@ export function clearFile () {
|
|||
};
|
||||
}
|
||||
|
||||
export function setUpdateTrue () {
|
||||
return {
|
||||
type: actions.SET_UPDATE_TRUE,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateMetadata (name, value) {
|
||||
return {
|
||||
type: actions.METADATA_UPDATE,
|
||||
|
@ -31,6 +37,13 @@ export function updateClaim (value) {
|
|||
};
|
||||
};
|
||||
|
||||
export function abandonClaim (data) {
|
||||
return {
|
||||
type: actions.ABANDON_CLAIM,
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
export function setPublishInChannel (channel) {
|
||||
return {
|
||||
type: actions.SET_PUBLISH_IN_CHANNEL,
|
||||
|
|
|
@ -105,6 +105,13 @@ export function updateAssetViewsInList (id, claimId, claimViews) {
|
|||
};
|
||||
}
|
||||
|
||||
export function removeAsset (data) {
|
||||
return {
|
||||
type: actions.ASSET_REMOVE,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
// channel actions
|
||||
|
||||
export function addNewChannelToChannelList (id, name, shortId, longId, claimsData) {
|
||||
|
|
|
@ -42,3 +42,15 @@ export function getClaimViews (claimId) {
|
|||
const url = `/api/claim/views/${claimId}`;
|
||||
return Request(url);
|
||||
}
|
||||
|
||||
export function doAbandonClaim (claimId) {
|
||||
const params = {
|
||||
method : 'POST',
|
||||
body : JSON.stringify({claimId}),
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
credentials: 'include',
|
||||
};
|
||||
return Request('/api/claim/abandon', params);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import ContentPageWrapper from '@pages/ContentPageWrapper';
|
|||
import FourOhFourPage from '@pages/FourOhFourPage';
|
||||
import MultisitePage from '@pages/MultisitePage';
|
||||
import PopularPage from '@pages/PopularPage';
|
||||
import EditPage from '@pages/EditPage';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
|
@ -21,6 +22,7 @@ const App = () => {
|
|||
<Route exact path='/login' component={LoginPage} />
|
||||
<Route exact path='/multisite' component={MultisitePage} />
|
||||
<Route exact path='/popular' component={PopularPage} />
|
||||
<Route exact path='/edit/:identifier/:claim' component={EditPage} />
|
||||
<Route exact path='/:identifier/:claim' component={ContentPageWrapper} />
|
||||
<Route exact path='/:claim' component={ContentPageWrapper} />
|
||||
<Route component={FourOhFourPage} />
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {buffers, END, eventChannel} from 'redux-saga';
|
||||
|
||||
export const makePublishRequestChannel = (fd) => {
|
||||
export const makePublishRequestChannel = (fd, isUpdate) => {
|
||||
return eventChannel(emitter => {
|
||||
const uri = '/api/claim/publish';
|
||||
const uri = `/api/claim/${isUpdate ? 'update' : 'publish'}`;
|
||||
const xhr = new XMLHttpRequest();
|
||||
// add event listeners
|
||||
const onLoadStart = () => {
|
||||
|
|
|
@ -10,3 +10,5 @@ export const TOGGLE_METADATA_INPUTS = 'TOGGLE_METADATA_INPUTS';
|
|||
export const THUMBNAIL_NEW = 'THUMBNAIL_NEW';
|
||||
export const PUBLISH_START = 'PUBLISH_START';
|
||||
export const CLAIM_AVAILABILITY = 'CLAIM_AVAILABILITY';
|
||||
export const SET_UPDATE_TRUE = 'SET_UPDATE_TRUE';
|
||||
export const ABANDON_CLAIM = 'ABANDON_CLAIM';
|
||||
|
|
|
@ -3,3 +3,4 @@ export const LOADING = 'LOADING';
|
|||
export const PUBLISHING = 'PUBLISHING';
|
||||
export const SUCCESS = 'SUCCESS';
|
||||
export const FAILED = 'FAILED';
|
||||
export const ABANDONING = 'ABANDONING';
|
||||
|
|
|
@ -11,6 +11,8 @@ export const REQUEST_LIST_ADD = 'REQUEST_LIST_ADD';
|
|||
// asset actions
|
||||
export const ASSET_ADD = 'ASSET_ADD';
|
||||
export const ASSET_VIEWS_UPDATE = 'ASSET_VIEWS_UPDATE';
|
||||
export const ASSET_UPDATE_CLAIMDATA = 'ASSET_UPDATE_CLAIMDATA';
|
||||
export const ASSET_REMOVE = 'ASSET_REMOVE';
|
||||
|
||||
// channel actions
|
||||
export const CHANNEL_ADD = 'CHANNEL_ADD';
|
||||
|
|
|
@ -3,7 +3,8 @@ import View from './view';
|
|||
import { fileRequested } from '../../actions/show';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
|
||||
const mapStateToProps = ({ show }) => {
|
||||
const mapStateToProps = (props) => {
|
||||
const {show} = props;
|
||||
// select error and status
|
||||
const error = show.displayAsset.error;
|
||||
const status = show.displayAsset.status;
|
||||
|
|
|
@ -3,14 +3,49 @@ import Row from '@components/Row';
|
|||
import ProgressBar from '@components/ProgressBar';
|
||||
import { LOCAL_CHECK, UNAVAILABLE, ERROR, AVAILABLE } from '../../constants/asset_display_states';
|
||||
|
||||
class AvailableContent extends React.Component {
|
||||
render () {
|
||||
const {contentType, sourceUrl, name, thumbnail} = this.props;
|
||||
switch (contentType) {
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
case 'image/png':
|
||||
case 'image/gif':
|
||||
return (
|
||||
<img
|
||||
className='asset-image'
|
||||
src={sourceUrl}
|
||||
alt={name}
|
||||
/>
|
||||
);
|
||||
case 'video/mp4':
|
||||
return (
|
||||
<video
|
||||
className='asset-video'
|
||||
controls poster={thumbnail}
|
||||
>
|
||||
<source
|
||||
src={sourceUrl}
|
||||
/>
|
||||
<p>Your browser does not support the <code>video</code> element.</p>
|
||||
</video>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<p>Unsupported content type</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AssetDisplay extends React.Component {
|
||||
componentDidMount () {
|
||||
const { asset: { claimData: { name, claimId } } } = this.props;
|
||||
this.props.onFileRequest(name, claimId);
|
||||
}
|
||||
render () {
|
||||
const { status, error, asset: { claimData: { name, claimId, contentType, fileExt, thumbnail } } } = this.props;
|
||||
const sourceUrl = `/${claimId}/${name}.${fileExt}`;
|
||||
const { status, error, asset: { name, claimData: { claimId, contentType, fileExt, thumbnail, outpoint } } } = this.props;
|
||||
const sourceUrl = `/${claimId}/${name}.${fileExt}?${outpoint}`;
|
||||
return (
|
||||
<div className={'asset-display'}>
|
||||
{(status === LOCAL_CHECK) &&
|
||||
|
@ -36,37 +71,12 @@ class AssetDisplay extends React.Component {
|
|||
</div>
|
||||
}
|
||||
{(status === AVAILABLE) &&
|
||||
(() => {
|
||||
switch (contentType) {
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
case 'image/png':
|
||||
case 'image/gif':
|
||||
return (
|
||||
<img
|
||||
className='asset-image'
|
||||
src={sourceUrl}
|
||||
alt={name}
|
||||
/>
|
||||
);
|
||||
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>
|
||||
);
|
||||
}
|
||||
})()
|
||||
<AvailableContent
|
||||
contentType={contentType}
|
||||
sourceUrl={sourceUrl}
|
||||
name={name}
|
||||
thumbnail={thumbnail}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,8 @@ import { connect } from 'react-redux';
|
|||
import View from './view';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
|
||||
const mapStateToProps = ({ show }) => {
|
||||
const mapStateToProps = (props) => {
|
||||
const {show} = props;
|
||||
// select asset
|
||||
const asset = selectAsset(show);
|
||||
// return props
|
||||
|
|
|
@ -2,10 +2,14 @@ import { connect } from 'react-redux';
|
|||
import View from './view';
|
||||
import { selectAsset } from '../../selectors/show';
|
||||
|
||||
const mapStateToProps = ({ show }) => {
|
||||
const { claimData: { title } } = selectAsset(show);
|
||||
const mapStateToProps = (props) => {
|
||||
const { claimData: { title, claimId, name, channelName } } = selectAsset(props.show);
|
||||
const editable = Boolean(props.channel.loggedInChannel.name === channelName);
|
||||
return {
|
||||
title,
|
||||
claimId,
|
||||
name,
|
||||
editable,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Row from '@components/Row';
|
||||
|
||||
const AssetTitle = ({ title }) => {
|
||||
const AssetTitle = ({ title, editable, claimId, name }) => {
|
||||
return (
|
||||
<Row>
|
||||
<h3>{title}</h3>
|
||||
<h3>
|
||||
{title}
|
||||
{editable && (<span> (<Link to={`/edit/${claimId}/${name}`}>edit</Link>)</span>)}
|
||||
</h3>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { clearFile, startPublish } from '../../actions/publish';
|
||||
import { clearFile, startPublish, abandonClaim } from '../../actions/publish';
|
||||
import View from './view';
|
||||
|
||||
const mapStateToProps = ({ channel, publish }) => {
|
||||
const mapStateToProps = ({ show, publish }) => {
|
||||
// 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 {
|
||||
file: publish.file,
|
||||
file : publish.file,
|
||||
isUpdate: publish.isUpdate,
|
||||
asset,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
clearFile,
|
||||
startPublish,
|
||||
abandonClaim,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import {Link, withRouter} from 'react-router-dom';
|
||||
import PublishUrlInput from '@containers/PublishUrlInput';
|
||||
import PublishThumbnailInput from '@containers/PublishThumbnailInput';
|
||||
import PublishMetadataInputs from '@containers/PublishMetadataInputs';
|
||||
import ChannelSelect from '@containers/ChannelSelect';
|
||||
import Row from '@components/Row';
|
||||
import Label from '@components/Label';
|
||||
import RowLabeled from '@components/RowLabeled';
|
||||
import ButtonPrimaryJumbo from '@components/ButtonPrimaryJumbo';
|
||||
import ButtonTertiary from '@components/ButtonTertiary';
|
||||
import ButtonSecondary from '@components/ButtonSecondary';
|
||||
import SpaceAround from '@components/SpaceAround';
|
||||
import PublishFinePrint from '@components/PublishFinePrint';
|
||||
|
||||
|
@ -14,22 +17,62 @@ class PublishDetails extends React.Component {
|
|||
constructor (props) {
|
||||
super(props);
|
||||
this.onPublishSubmit = this.onPublishSubmit.bind(this);
|
||||
this.abandonClaim = this.abandonClaim.bind(this);
|
||||
}
|
||||
onPublishSubmit () {
|
||||
this.props.startPublish(this.props.history);
|
||||
}
|
||||
abandonClaim () {
|
||||
const {asset, history} = this.props;
|
||||
if (asset) {
|
||||
const {claimData} = asset;
|
||||
this.props.abandonClaim({claimData, history});
|
||||
}
|
||||
}
|
||||
render () {
|
||||
const {file, isUpdate, asset} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<PublishUrlInput />
|
||||
</Row>
|
||||
{isUpdate ? (asset && (
|
||||
<React.Fragment>
|
||||
<Row>
|
||||
<RowLabeled
|
||||
label={
|
||||
<Label value={'Channel:'} />
|
||||
}
|
||||
content={
|
||||
<span className='text'>
|
||||
{asset.claimData.channelName}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<RowLabeled
|
||||
label={
|
||||
<Label value={'Asset:'} />
|
||||
}
|
||||
content={
|
||||
<span className='text'>
|
||||
{asset.name}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)) : (
|
||||
<React.Fragment>
|
||||
<Row>
|
||||
<PublishUrlInput />
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<ChannelSelect />
|
||||
</Row>
|
||||
<Row>
|
||||
<ChannelSelect />
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{ this.props.file.type === 'video/mp4' && (
|
||||
{ file && file.type === 'video/mp4' && (
|
||||
<Row>
|
||||
<PublishThumbnailInput />
|
||||
</Row>
|
||||
|
@ -46,6 +89,17 @@ class PublishDetails extends React.Component {
|
|||
/>
|
||||
</Row>
|
||||
|
||||
{isUpdate && (
|
||||
<Row>
|
||||
<SpaceAround>
|
||||
<ButtonSecondary
|
||||
value={'Abandon Claim'}
|
||||
onClickHandler={this.abandonClaim}
|
||||
/>
|
||||
</SpaceAround>
|
||||
</Row>
|
||||
)}
|
||||
|
||||
<Row>
|
||||
<SpaceAround>
|
||||
<ButtonTertiary
|
||||
|
|
|
@ -71,6 +71,13 @@ class PublishStatus extends React.Component {
|
|||
</Row>
|
||||
</div>
|
||||
}
|
||||
{status === publishStates.ABANDONING &&
|
||||
<div className={'status'}>
|
||||
<Row>
|
||||
<p>Your claim is being abandoned.</p>
|
||||
</Row>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ const mapStateToProps = ({ publish }) => {
|
|||
disabled: publish.disabled,
|
||||
file : publish.file,
|
||||
status : publish.status.status,
|
||||
isUpdate: publish.isUpdate,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -6,18 +6,19 @@ import PublishDisabledMessage from '@containers/PublishDisabledMessage';
|
|||
|
||||
class PublishTool extends React.Component {
|
||||
render () {
|
||||
if (this.props.disabled) {
|
||||
const {disabled, file, isUpdate, status} = this.props;
|
||||
if (disabled) {
|
||||
return (
|
||||
<PublishDisabledMessage />
|
||||
);
|
||||
} else {
|
||||
if (this.props.file) {
|
||||
if (this.props.status) {
|
||||
if (file || isUpdate) {
|
||||
if (status) {
|
||||
return (
|
||||
<PublishStatus />
|
||||
);
|
||||
} else {
|
||||
return <PublishPreview />;
|
||||
return <PublishPreview isUpdate={isUpdate} />;
|
||||
}
|
||||
}
|
||||
return <Dropzone />;
|
||||
|
|
31
client/src/pages/EditPage/index.js
Normal file
31
client/src/pages/EditPage/index.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { setUpdateTrue, updateMetadata, clearFile } from '../../actions/publish';
|
||||
import { onHandleShowPageUri } from '../../actions/show';
|
||||
import View from './view';
|
||||
|
||||
const mapStateToProps = (props) => {
|
||||
const { show } = props;
|
||||
// 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 {
|
||||
asset,
|
||||
myChannel: props.channel.loggedInChannel.name,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
updateMetadata,
|
||||
onHandleShowPageUri,
|
||||
setUpdateTrue,
|
||||
clearFile,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
42
client/src/pages/EditPage/view.jsx
Normal file
42
client/src/pages/EditPage/view.jsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
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, updateMetadata} = this.props;
|
||||
onHandleShowPageUri(match.params);
|
||||
setUpdateTrue();
|
||||
if (asset) {
|
||||
['title', 'description', 'license', 'nsfw'].forEach(meta => updateMetadata(meta, asset.claimData[meta]));
|
||||
}
|
||||
}
|
||||
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;
|
|
@ -4,16 +4,19 @@ import PublishTool from '@containers/PublishTool';
|
|||
import ContentPageWrapper from '@pages/ContentPageWrapper';
|
||||
|
||||
class HomePage extends React.Component {
|
||||
componentDidMount () {
|
||||
this.props.onHandleShowHomepage(this.props.match.params);
|
||||
}
|
||||
// componentDidMount () {
|
||||
// this.props.onHandleShowHomepage(this.props.match.params);
|
||||
// }
|
||||
//
|
||||
// componentWillReceiveProps (nextProps) {
|
||||
// if (nextProps.match.params !== this.props.match.params) {
|
||||
// this.props.onHandleShowHomepage(nextProps.match.params);
|
||||
// }
|
||||
// }
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.match.params !== this.props.match.params) {
|
||||
this.props.onHandleShowHomepage(nextProps.match.params);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.props.clearFile();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { homeChannel } = this.props;
|
||||
return homeChannel ? (
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import View from './view';
|
||||
|
||||
const mapStateToProps = ({ show }) => {
|
||||
const mapStateToProps = (props) => {
|
||||
const {show} = props;
|
||||
// select request info
|
||||
const requestId = show.request.id;
|
||||
// select asset info
|
||||
|
@ -11,7 +12,7 @@ const mapStateToProps = ({ show }) => {
|
|||
if (request && assetList) {
|
||||
const assetKey = request.key; // note: just store this in the request
|
||||
asset = assetList[assetKey] || null;
|
||||
};
|
||||
}
|
||||
// return props
|
||||
return {
|
||||
asset,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import PageLayout from '@components/PageLayout';
|
||||
|
||||
import HorizontalSplit from '@components/HorizontalSplit';
|
||||
import AssetTitle from '@containers/AssetTitle';
|
||||
import AssetDisplay from '@containers/AssetDisplay';
|
||||
|
|
|
@ -41,6 +41,7 @@ const initialState = {
|
|||
license : '',
|
||||
nsfw : false,
|
||||
},
|
||||
isUpdate : false,
|
||||
thumbnail: null,
|
||||
thumbnailChannel,
|
||||
thumbnailChannelId,
|
||||
|
@ -49,7 +50,7 @@ const initialState = {
|
|||
export default function (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actions.FILE_SELECTED:
|
||||
return Object.assign({}, initialState, { // note: clears to initial state
|
||||
return Object.assign({}, state.isUpdate ? state : initialState, { // note: clears to initial state
|
||||
file: action.data,
|
||||
});
|
||||
case actions.FILE_CLEAR:
|
||||
|
@ -90,6 +91,10 @@ export default function (state = initialState, action) {
|
|||
return Object.assign({}, state, {
|
||||
thumbnail: action.data,
|
||||
});
|
||||
case actions.SET_UPDATE_TRUE:
|
||||
return Object.assign({}, state, {
|
||||
isUpdate: true,
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -65,6 +65,33 @@ 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 Object.assign({}, state, {
|
||||
assetList : newAssetList,
|
||||
channelList: Object.assign({}, state.channelList, {
|
||||
[channelId]: Object.assign({}, state.channelList[channelId], {
|
||||
claimsData: Object.assign({}, state.channelList[channelId].claimsData, {
|
||||
claims: newClaimsData,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
case actions.ASSET_UPDATE_CLAIMDATA:
|
||||
return Object.assign({}, state, {
|
||||
assetList: Object.assign({}, state.assetList, {
|
||||
[action.data.id]: Object.assign({}, state.assetList[action.data.id], {
|
||||
claimData: Object.assign({}, state.assetList[action.data.id].claimData, action.data.claimData),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
// channel data
|
||||
case actions.CHANNEL_ADD:
|
||||
return Object.assign({}, state, {
|
||||
|
|
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,61 @@ import { updateError, updatePublishStatus, clearFile } from '../actions/publish'
|
|||
import { selectPublishState } from '../selectors/publish';
|
||||
import { selectChannelState } from '../selectors/channel';
|
||||
import { selectSiteState } from '../selectors/site';
|
||||
import { selectShowState, selectAsset } from '../selectors/show';
|
||||
import { validateChannelSelection, validateNoPublishErrors } from '../utils/validate';
|
||||
import { createPublishMetadata, createPublishFormData, createThumbnailUrl } from '../utils/publish';
|
||||
import { makePublishRequestChannel } from '../channels/publish';
|
||||
|
||||
function * publishFile (action) {
|
||||
const { history } = action.data;
|
||||
const { publishInChannel, selectedChannel, file, claim, metadata, thumbnailChannel, thumbnailChannelId, thumbnail, error: publishToolErrors } = yield select(selectPublishState);
|
||||
const publishState = yield select(selectPublishState);
|
||||
const { publishInChannel, selectedChannel, file, claim, metadata, thumbnailChannel, thumbnailChannelId, thumbnail, isUpdate, error: publishToolErrors } = publishState;
|
||||
const { loggedInChannel } = yield select(selectChannelState);
|
||||
const { host } = yield select(selectSiteState);
|
||||
|
||||
let show, asset;
|
||||
if (isUpdate) {
|
||||
show = yield select(selectShowState);
|
||||
asset = selectAsset(show);
|
||||
}
|
||||
// validate the channel selection
|
||||
try {
|
||||
validateChannelSelection(publishInChannel, selectedChannel, loggedInChannel);
|
||||
} catch (error) {
|
||||
return yield put(updateError('channel', error.message));
|
||||
};
|
||||
}
|
||||
// validate publish parameters
|
||||
try {
|
||||
validateNoPublishErrors(publishToolErrors);
|
||||
} catch (error) {
|
||||
return console.log('publish error:', error.message);
|
||||
}
|
||||
// create metadata
|
||||
let publishMetadata = createPublishMetadata(claim, file, metadata, publishInChannel, selectedChannel);
|
||||
if (thumbnail) {
|
||||
// add thumbnail to publish metadata
|
||||
publishMetadata['thumbnail'] = createThumbnailUrl(thumbnailChannel, thumbnailChannelId, claim, host);
|
||||
|
||||
let publishMetadata, publishFormData, publishChannel;
|
||||
if (isUpdate) {
|
||||
publishMetadata = createPublishMetadata(asset.name, {type: asset.claimData.contentType}, metadata, publishInChannel, selectedChannel);
|
||||
publishMetadata['channelName'] = asset.claimData.channelName;
|
||||
if (thumbnail) {
|
||||
// add thumbnail to publish metadata
|
||||
publishMetadata['thumbnail'] = createThumbnailUrl(thumbnailChannel, thumbnailChannelId, claim, host);
|
||||
}
|
||||
// create form data for main publish
|
||||
publishFormData = createPublishFormData(file, thumbnail, publishMetadata);
|
||||
// make the publish request
|
||||
publishChannel = yield call(makePublishRequestChannel, publishFormData, true);
|
||||
} else {
|
||||
// create metadata
|
||||
publishMetadata = createPublishMetadata(claim, file, metadata, publishInChannel, selectedChannel);
|
||||
if (thumbnail) {
|
||||
// add thumbnail to publish metadata
|
||||
publishMetadata['thumbnail'] = createThumbnailUrl(thumbnailChannel, thumbnailChannelId, claim, host);
|
||||
}
|
||||
// create form data for main publish
|
||||
publishFormData = createPublishFormData(file, thumbnail, publishMetadata);
|
||||
// make the publish request
|
||||
publishChannel = yield call(makePublishRequestChannel, publishFormData);
|
||||
}
|
||||
// create form data for main publish
|
||||
const publishFormData = createPublishFormData(file, thumbnail, publishMetadata);
|
||||
// make the publish request
|
||||
const publishChannel = yield call(makePublishRequestChannel, publishFormData);
|
||||
|
||||
while (true) {
|
||||
const {loadStart, progress, load, success, error: publishError} = yield take(publishChannel);
|
||||
if (publishError) {
|
||||
|
@ -43,6 +67,15 @@ function * publishFile (action) {
|
|||
}
|
||||
if (success) {
|
||||
yield put(clearFile());
|
||||
if (isUpdate) {
|
||||
yield put({
|
||||
type: 'ASSET_UPDATE_CLAIMDATA',
|
||||
data: {
|
||||
id : `a#${success.data.name}#${success.data.claimId}`,
|
||||
claimData: success.data.claimData,
|
||||
},
|
||||
});
|
||||
}
|
||||
return history.push(`/${success.data.claimId}/${success.data.name}`);
|
||||
}
|
||||
if (loadStart) {
|
||||
|
@ -55,7 +88,7 @@ function * publishFile (action) {
|
|||
yield put(updatePublishStatus(publishStates.PUBLISHING, null));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function * watchPublishStart () {
|
||||
yield takeLatest(actions.PUBLISH_START, publishFile);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { watchUpdateChannelAvailability } from './updateChannelAvailability';
|
|||
import { watchChannelCreate } from './createChannel';
|
||||
import { watchChannelLoginCheck } from './checkForLoggedInChannel';
|
||||
import { watchChannelLogout } from './logoutChannel';
|
||||
import { watchAbandonClaim } from './abandon';
|
||||
|
||||
export function * rootSaga () {
|
||||
yield all([
|
||||
|
@ -27,5 +28,6 @@ export function * rootSaga () {
|
|||
watchChannelLoginCheck(),
|
||||
watchChannelLogout(),
|
||||
watchUpdateAssetViews(),
|
||||
watchAbandonClaim(),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@ export const createPublishMetadata = (claim, { type }, { title, description, lic
|
|||
export const createPublishFormData = (file, thumbnail, metadata) => {
|
||||
let fd = new FormData();
|
||||
// append file
|
||||
fd.append('file', file);
|
||||
if (file) {
|
||||
fd.append('file', file);
|
||||
}
|
||||
// append thumbnail
|
||||
if (thumbnail) {
|
||||
fd.append('thumbnail', thumbnail);
|
||||
|
@ -31,5 +33,5 @@ export const createPublishFormData = (file, thumbnail, metadata) => {
|
|||
};
|
||||
|
||||
export const createThumbnailUrl = (channel, channelId, claim, host) => {
|
||||
return `${host}/${channel}:${channelId}/${claim}-thumb.png`;
|
||||
return `${host}/${channel}:${channelId}/${claim}-thumb.jpg`;
|
||||
};
|
||||
|
|
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('../../../../models');
|
||||
const { abandonClaim } = require('../../../../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;
|
|
@ -14,7 +14,7 @@ const publish = require('./publish.js');
|
|||
const createPublishParams = require('./createPublishParams.js');
|
||||
const createThumbnailPublishParams = require('./createThumbnailPublishParams.js');
|
||||
const parsePublishApiRequestBody = require('./parsePublishApiRequestBody.js');
|
||||
const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
|
||||
const {parsePublishApiRequestFiles} = require('./parsePublishApiRequestFiles.js');
|
||||
const authenticateUser = require('./authentication.js');
|
||||
|
||||
const CLAIM_TAKEN = 'CLAIM_TAKEN';
|
||||
|
@ -85,17 +85,18 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
|
|||
// publish the asset
|
||||
return publish(publishParams, fileName, fileType);
|
||||
})
|
||||
.then(result => {
|
||||
.then(claimData => {
|
||||
logger.debug('Publish success >', claimData);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'publish completed successfully',
|
||||
data : {
|
||||
name,
|
||||
claimId : result.claim_id,
|
||||
url : `${host}/${result.claim_id}/${name}`, // for backwards compatability with app
|
||||
showUrl : `${host}/${result.claim_id}/${name}`,
|
||||
serveUrl: `${host}/${result.claim_id}/${name}${fileExtension}`,
|
||||
lbryTx : result,
|
||||
claimId : claimData.claimId,
|
||||
url : `${host}/${claimData.claimId}/${name}`, // for backwards compatability with app
|
||||
showUrl : `${host}/${claimData.claimId}/${name}`,
|
||||
serveUrl: `${host}/${claimData.claimId}/${name}${fileExtension}`,
|
||||
claimData,
|
||||
},
|
||||
});
|
||||
// record the publish end time and send to google analytics
|
||||
|
|
|
@ -42,4 +42,61 @@ const parsePublishApiRequestFiles = ({file, thumbnail}) => {
|
|||
};
|
||||
};
|
||||
|
||||
module.exports = parsePublishApiRequestFiles;
|
||||
const parsePublishApiRequestFile = ({file, thumbnail}, isUpdate) => {
|
||||
// make sure a file was provided
|
||||
if (!file) {
|
||||
if (isUpdate) {
|
||||
return;
|
||||
}
|
||||
throw new Error('no file with key of [file] found in request');
|
||||
}
|
||||
if (!file.path) {
|
||||
throw new Error('no file path found');
|
||||
}
|
||||
if (!file.type) {
|
||||
throw new Error('no file type found');
|
||||
}
|
||||
if (!file.size) {
|
||||
throw new Error('no file size found');
|
||||
}
|
||||
// validate the file name
|
||||
if (!file.name) {
|
||||
throw new Error('no file name found');
|
||||
}
|
||||
if (file.name.indexOf('.') < 0) {
|
||||
throw new Error('no file extension found in file name');
|
||||
}
|
||||
if (file.name.indexOf('.') === 0) {
|
||||
throw new Error('file name cannot start with "."');
|
||||
}
|
||||
if (/'/.test(file.name)) {
|
||||
throw new Error('apostrophes are not allowed in the file name');
|
||||
}
|
||||
|
||||
// validate the file
|
||||
validateFileTypeAndSize(file);
|
||||
// return results
|
||||
return {
|
||||
fileName : file.name,
|
||||
filePath : file.path,
|
||||
fileExtension: path.extname(file.path),
|
||||
fileType : file.type,
|
||||
};
|
||||
};
|
||||
|
||||
const parsePublishApiRequestThumbnail = ({file, thumbnail}) => {
|
||||
if (!thumbnail) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
thumbnailFileName: thumbnail.name,
|
||||
thumbnailFilePath: thumbnail.path,
|
||||
thumbnailFileType: thumbnail.type,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
parsePublishApiRequestFiles,
|
||||
parsePublishApiRequestFile,
|
||||
parsePublishApiRequestThumbnail,
|
||||
};
|
||||
|
|
|
@ -1,81 +1,62 @@
|
|||
const logger = require('winston');
|
||||
const { publishClaim } = require('../../../../lbrynet');
|
||||
const db = require('../../../../models');
|
||||
const { publishClaim } = require('../../../../lbrynet');
|
||||
const { createFileRecordDataAfterPublish } = require('../../../../models/utils/createFileRecordData.js');
|
||||
const { createClaimRecordDataAfterPublish } = require('../../../../models/utils/createClaimRecordData.js');
|
||||
const deleteFile = require('./deleteFile.js');
|
||||
|
||||
const publish = (publishParams, fileName, fileType) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let publishResults, certificateId, channelName;
|
||||
// publish the file
|
||||
return publishClaim(publishParams)
|
||||
.then(result => {
|
||||
logger.info(`Successfully published ${publishParams.name} ${fileName}`, result);
|
||||
const publish = async (publishParams, fileName, fileType) => {
|
||||
let publishResults;
|
||||
let channel;
|
||||
let fileRecord;
|
||||
let filePath = publishParams.file_path;
|
||||
let newFile = Boolean(filePath);
|
||||
|
||||
// Support new daemon, TODO: remove
|
||||
publishResults = result.output && result.output.claim_id ? result.output : result;
|
||||
try {
|
||||
publishResults = await publishClaim(publishParams);
|
||||
logger.info(`Successfully published ${publishParams.name} ${fileName}`, publishResults);
|
||||
|
||||
// get the channel information
|
||||
if (publishParams.channel_name) {
|
||||
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
|
||||
return db.Channel.findOne({
|
||||
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);
|
||||
// get the channel information
|
||||
if (publishParams.channel_name) {
|
||||
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
|
||||
channel = await db.Channel.findOne({
|
||||
where: {
|
||||
channelName: publishParams.channel_name,
|
||||
},
|
||||
});
|
||||
});
|
||||
} 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) {
|
||||
fileRecord = await createFileRecordDataAfterPublish(fileName, fileType, publishParams, publishResults);
|
||||
} else {
|
||||
fileRecord = await db.File.findOne({where: {claimId}});
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
await Promise.all([
|
||||
file.setClaim(claim),
|
||||
claim.setFile(file),
|
||||
]);
|
||||
logger.info('File and Claim records successfully associated');
|
||||
|
||||
return claimRecord;
|
||||
} catch (err) {
|
||||
logger.error('PUBLISH ERROR', err);
|
||||
await deleteFile(filePath);
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = publish;
|
||||
|
|
151
server/controllers/api/claim/update/index.js
Normal file
151
server/controllers/api/claim/update/index.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
const logger = require('winston');
|
||||
const db = require('../../../../models');
|
||||
const { details, publishing: { disabled, disabledMessage, primaryClaimAddress } } = require('@config/siteConfig');
|
||||
const { resolveUri } = require('../../../../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 {parsePublishApiRequestFile, parsePublishApiRequestThumbnail} = require('../publish/parsePublishApiRequestFiles.js');
|
||||
const authenticateUser = require('../publish/authentication.js');
|
||||
const createThumbnailPublishParams = require('../publish/createThumbnailPublishParams.js');
|
||||
|
||||
/*
|
||||
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 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;
|
||||
let channelId;
|
||||
let channelPassword;
|
||||
let description;
|
||||
let fileName;
|
||||
let filePath;
|
||||
let fileType;
|
||||
let gaStartTime;
|
||||
let thumbnail;
|
||||
let fileExtension;
|
||||
let license;
|
||||
let name;
|
||||
let nsfw;
|
||||
let thumbnailFileName;
|
||||
let thumbnailFilePath;
|
||||
let thumbnailFileType;
|
||||
let title;
|
||||
let claimRecord;
|
||||
let metadata;
|
||||
// record the start time of the request
|
||||
gaStartTime = Date.now();
|
||||
|
||||
try {
|
||||
({name, nsfw, license, title, description, thumbnail} = parsePublishApiRequestBody(body));
|
||||
if (files.file) {
|
||||
({fileName, filePath, fileExtension, fileType} = parsePublishApiRequestFile(files));
|
||||
if (files.thumbnail) {
|
||||
({thumbnailFileName, thumbnailFilePath, thumbnailFileType} = parsePublishApiRequestThumbnail(files));
|
||||
}
|
||||
}
|
||||
({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 }) => {
|
||||
return db.Claim.findOne({
|
||||
where: {
|
||||
name,
|
||||
channelName,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(claim => {
|
||||
return resolveUri(`${claim.name}#${claim.claimId}`);
|
||||
})
|
||||
.then(fullClaim => {
|
||||
claimRecord = fullClaim;
|
||||
logger.info('fullClaim', fullClaim);
|
||||
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) {
|
||||
publishParams['file_path'] = filePath;
|
||||
} else {
|
||||
fileName = fullClaim.file_name;
|
||||
fileType = fullClaim.mime_type;
|
||||
publishParams['sources'] = fullClaim.claim.value.stream.source;
|
||||
}
|
||||
// publish the thumbnail, if one exists
|
||||
if (thumbnailFileName) {
|
||||
const thumbnailPublishParams = createThumbnailPublishParams(thumbnailFilePath, name, license, nsfw);
|
||||
publish(thumbnailPublishParams, thumbnailFileName, thumbnailFileType);
|
||||
publishParams['thumbnail'] = `${details.host}/${channelName}:${channelId}/${name}-thumb.jpg`;
|
||||
}
|
||||
|
||||
return publish(publishParams, fileName, fileType);
|
||||
})
|
||||
.then(claimData => {
|
||||
const {claimId} = claimData;
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'update successful',
|
||||
data : {
|
||||
name,
|
||||
channelName,
|
||||
channelId: claimData.certificateId,
|
||||
claimId,
|
||||
url : `${details.host}/${claimId}/${name}`, // for backwards compatability with app
|
||||
showUrl : `${details.host}/${claimId}/${name}`,
|
||||
claimData,
|
||||
},
|
||||
});
|
||||
// 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;
|
56
server/controllers/api/claim/update/updatePublishParams.js
Normal file
56
server/controllers/api/claim/update/updatePublishParams.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
const path = require('path');
|
||||
const validateFileTypeAndSize = require('../publish/validateFileTypeAndSize.js');
|
||||
|
||||
const parseUpdateFile = ({file}) => {
|
||||
// make sure a file was provided
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
if (!file.path) {
|
||||
throw new Error('no file path found');
|
||||
}
|
||||
if (!file.type) {
|
||||
throw new Error('no file type found');
|
||||
}
|
||||
if (!file.size) {
|
||||
throw new Error('no file size found');
|
||||
}
|
||||
// validate the file name
|
||||
if (!file.name) {
|
||||
throw new Error('no file name found');
|
||||
}
|
||||
if (file.name.indexOf('.') < 0) {
|
||||
throw new Error('no file extension found in file name');
|
||||
}
|
||||
if (file.name.indexOf('.') === 0) {
|
||||
throw new Error('file name cannot start with "."');
|
||||
}
|
||||
if (/'/.test(file.name)) {
|
||||
throw new Error('apostrophes are not allowed in the file name');
|
||||
}
|
||||
// validate the file
|
||||
validateFileTypeAndSize(file);
|
||||
// return results
|
||||
return {
|
||||
fileName : file.name,
|
||||
filePath : file.path,
|
||||
fileExtension: path.extname(file.path),
|
||||
fileType : file.type,
|
||||
};
|
||||
};
|
||||
|
||||
const parseUpdateThumbnail = ({thumbnail}) => {
|
||||
if (!thumbnail) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
thumbnailFileName: thumbnail.name,
|
||||
thumbnailFilePath: thumbnail.path,
|
||||
thumbnailFileType: thumbnail.type,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = [
|
||||
parseUpdateFile,
|
||||
parseUpdateThumbnail,
|
||||
];
|
|
@ -46,6 +46,21 @@ module.exports = {
|
|||
});
|
||||
});
|
||||
},
|
||||
async abandonClaim ({claimId}) {
|
||||
logger.debug(`lbryApi >> Abandon claim "${claimId}"`);
|
||||
const gaStartTime = Date.now();
|
||||
try {
|
||||
const abandon = await axios.post(lbrynetUri, {
|
||||
method: 'claim_abandon',
|
||||
params: { claim_id: claimId },
|
||||
});
|
||||
sendGATimingEvent('lbrynet', 'abandonClaim', 'ABANDON_CLAIM', gaStartTime, Date.now());
|
||||
return abandon.data;
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return error;
|
||||
}
|
||||
},
|
||||
getClaimList (claimName) {
|
||||
logger.debug(`lbryApi >> Getting claim_list for "${claimName}"`);
|
||||
const gaStartTime = Date.now();
|
||||
|
|
|
@ -13,6 +13,8 @@ const claimGet = require('../../controllers/api/claim/get');
|
|||
const claimList = require('../../controllers/api/claim/list');
|
||||
const claimLongId = require('../../controllers/api/claim/longId');
|
||||
const claimPublish = require('../../controllers/api/claim/publish');
|
||||
const claimAbandon = require('../../controllers/api/claim/abandon');
|
||||
const claimUpdate = require('../../controllers/api/claim/update');
|
||||
const claimResolve = require('../../controllers/api/claim/resolve');
|
||||
const claimShortId = require('../../controllers/api/claim/shortId');
|
||||
const claimViews = require('../../controllers/api/claim/views');
|
||||
|
@ -29,12 +31,10 @@ const getOEmbedData = require('../../controllers/api/oEmbed');
|
|||
module.exports = {
|
||||
// homepage routes
|
||||
'/api/homepage/data/channels': { controller: [ torCheckMiddleware, channelData ] },
|
||||
|
||||
// channel routes
|
||||
'/api/channel/availability/:name': { controller: [ torCheckMiddleware, channelAvailability ] },
|
||||
'/api/channel/short-id/:longId/:name': { controller: [ torCheckMiddleware, channelShortId ] },
|
||||
'/api/channel/data/:channelName/:channelClaimId': { controller: [ torCheckMiddleware, channelData ] },
|
||||
'/api/channel/data/:channelName/:channelClaimId': { controller: [ torCheckMiddleware, channelData ] },
|
||||
'/api/channel/claims/:channelName/:channelClaimId/:page': { controller: [ torCheckMiddleware, channelClaims ] },
|
||||
|
||||
// sepcial routes
|
||||
|
@ -47,6 +47,8 @@ module.exports = {
|
|||
'/api/claim/list/:name': { controller: [ torCheckMiddleware, claimList ] },
|
||||
'/api/claim/long-id': { method: 'post', controller: [ torCheckMiddleware, claimLongId ] }, // note: should be a 'get'
|
||||
'/api/claim/publish': { method: 'post', controller: [ torCheckMiddleware, autoblockPublishMiddleware, multipartMiddleware, autoblockPublishBodyMiddleware, claimPublish ] },
|
||||
'/api/claim/update': { method: 'post', controller: [ torCheckMiddleware, multipartMiddleware, claimUpdate ] },
|
||||
'/api/claim/abandon': { method: 'post', controller: [ torCheckMiddleware, multipartMiddleware, claimAbandon ] },
|
||||
'/api/claim/resolve/:name/:claimId': { controller: [ torCheckMiddleware, claimResolve ] },
|
||||
'/api/claim/short-id/:longId/:name': { controller: [ torCheckMiddleware, claimShortId ] },
|
||||
'/api/claim/views/:claimId': { controller: [ torCheckMiddleware, claimViews ] },
|
||||
|
|
|
@ -15,6 +15,7 @@ module.exports = {
|
|||
'/trending': { controller: redirect('/popular') },
|
||||
'/popular': { controller: handlePageRequest },
|
||||
'/new': { controller: handlePageRequest },
|
||||
'/edit/:claimId': { controller: handlePageRequest },
|
||||
'/multisite': { controller: handlePageRequest },
|
||||
'/video-embed/:name/:claimId/:config?': { controller: handleVideoEmbedRequest }, // for twitter
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue