Folder structure #398

Merged
bones7242 merged 76 commits from folder-structure into master 2018-03-20 00:01:08 +01:00
177 changed files with 11432 additions and 430 deletions

View file

@ -1,3 +1,4 @@
node_modules/
public/
public/bundle
index.js
test

19
.gitignore vendored
View file

@ -1,7 +1,12 @@
node_modules
.idea
config/sequelizeCliConfig.js
config/speechConfig.js
public/bundle
server.js
webpack.config.js
node_modules/
.idea/
config/lbryConfig.js
config/loggerConfig.js
config/mysqlConfig.js
config/siteConfig.js
devConfig/slackConfig.js
devConfig/sequelizeCliConfig.js
devConfig/testingConfig.js
public/bundle/
index.js

View file

@ -1,5 +1,5 @@
const path = require('path');
module.exports = {
'config': path.resolve('config', 'sequelizeCliConfig.js'),
'config': path.resolve('devConfig', 'sequelizeCliConfig.js'),
}

View file

@ -1,7 +1,7 @@
# Spee.ch
Spee.ch is a web app that reads and publishes images and videos to and from the [LBRY](https://lbry.io/) blockchain.
## How to run this repository locally
##Installation
* start mysql
* install mysql
* create a database called `lbry`
@ -12,10 +12,13 @@ Spee.ch is a web app that reads and publishes images and videos to and from the
* start spee.ch
* clone this repo
* run `npm install`
* create your `speechConfig.js` file
* copy `speechConfig.js.example` and name it `speechConfig.js`
* create your own config files in `/config`
* copy `example.js.example` and name it `example.js`
* replace the `null` values in the config file with the appropriate values for your environment
* build the app by running `npm run build-prod`
* create your own config files in `/devConfig`
* copy `example.js.example` and name it `example.js`
* note: you must create these files, but the default values are sufficient if you do not want to update them.
* build the app by running `npm run build`
* to start the server, run `npm run start`
* visit [localhost:3000](http://localhost:3000)
* start spee.ch-sync (optional, recommended)
@ -45,8 +48,8 @@ Spee.ch is a web app that reads and publishes images and videos to and from the
* /api/claim/publish
* example: `curl -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim/publish`
* Parameters:
* `name`
* `file` (must be type .mp4, .jpeg, .jpg, .gif, or .png)
* `name` (required)
* `file` (required) (must be type .mp4, .jpeg, .jpg, .gif, or .png)
* `nsfw` (optional)
* `license` (optional)
* `title` (optional)

View file

@ -1,7 +1,6 @@
import Request from 'utils/request';
const { site: { host } } = require('../../config/speechConfig.js');
export function getLongClaimId (name, modifier) {
export function getLongClaimId (host, name, modifier) {
let body = {};
// create request params
if (modifier) {
@ -24,12 +23,12 @@ export function getLongClaimId (name, modifier) {
return Request(url, params);
};
export function getShortId (name, claimId) {
export function getShortId (host, name, claimId) {
const url = `${host}/api/claim/short-id/${claimId}/${name}`;
return Request(url);
};
export function getClaimData (name, claimId) {
export function getClaimData (host, name, claimId) {
const url = `${host}/api/claim/data/${name}/${claimId}`;
return Request(url);
};

View file

@ -1,13 +1,12 @@
import Request from 'utils/request';
const { site: { host } } = require('../../config/speechConfig.js');
export function getChannelData (name, id) {
export function getChannelData (host, id, name) {
if (!id) id = 'none';
const url = `${host}/api/channel/data/${name}/${id}`;
return Request(url);
};
export function getChannelClaims (name, longId, page) {
export function getChannelClaims (host, longId, name, page) {
if (!page) page = 1;
const url = `${host}/api/channel/claims/${name}/${longId}/${page}`;
return Request(url);

View file

@ -1,12 +1,11 @@
import Request from 'utils/request';
const { site: { host } } = require('../../config/speechConfig.js');
export function checkFileAvailability (name, claimId) {
export function checkFileAvailability (claimId, host, name) {
const url = `${host}/api/file/availability/${name}/${claimId}`;
return Request(url);
}
export function triggerClaimGet (name, claimId) {
export function triggerClaimGet (claimId, host, name) {
const url = `${host}/api/claim/get/${name}/${claimId}`;
return Request(url);
}

View file

@ -1,10 +1,10 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import HomePage from 'components/HomePage';
import AboutPage from 'components/AboutPage';
import LoginPage from 'containers/LoginPage';
import ShowPage from 'containers/ShowPage';
import FourOhFourPage from 'components/FourOhFourPage';
import HomePage from 'pages/HomePage'; // or use the provided local homepage
import AboutPage from 'pages/AboutPage';
import LoginPage from 'pages/LoginPage';
import ShowPage from 'pages/ShowPage';
import FourOhFourPage from 'containers/FourOhFourPage';
const App = () => {
return (

View file

@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import View from './view';
const mapStateToProps = ({site: {defaults: { defaultThumbnail }}}) => {
return {
defaultThumbnail,
};
};
export default connect(mapStateToProps, null)(View);

View file

@ -1,8 +1,7 @@
import React from 'react';
import { Link } from 'react-router-dom';
const { claim: { defaultThumbnail } } = require('../../../config/speechConfig.js');
const AssetPreview = ({ claimData: { name, claimId, fileExt, contentType, thumbnail } }) => {
const AssetPreview = ({ defaultThumbnail, claimData: { name, claimId, fileExt, contentType, thumbnail } }) => {
const directSourceLink = `${claimId}/${name}.${fileExt}`;
const showUrlLink = `/${claimId}/${name}`;
return (

View file

@ -1,10 +1,9 @@
import React from 'react';
import GoogleAnalytics from 'react-ga';
import { withRouter } from 'react-router-dom';
const config = require('../../../config/speechConfig.js');
const googleApiKey = config.analytics.googleId;
const { analytics: { googleId } } = require('../../../config/siteConfig.js');
GoogleAnalytics.initialize(googleApiKey);
GoogleAnalytics.initialize(googleId);
class GAListener extends React.Component {
componentDidMount () {

View file

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
class Preview extends React.Component {
class PublishPreview extends React.Component {
constructor (props) {
super(props);
this.state = {
@ -53,10 +53,10 @@ class Preview extends React.Component {
}
};
Preview.propTypes = {
PublishPreview.propTypes = {
dimPreview: PropTypes.bool.isRequired,
file : PropTypes.object.isRequired,
thumbnail : PropTypes.object,
};
export default Preview;
export default PublishPreview;

View file

@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import View from './view';
const mapStateToProps = ({ site }) => {
const { defaultDescription, defaultThumbnail, description: siteDescription, host: siteHost, title: siteTitle, twitter: siteTwitter } = site;
return {
defaultDescription,
defaultThumbnail,
siteDescription,
siteHost,
siteTitle,
siteTwitter,
};
};
export default connect(mapStateToProps, null)(View);

View file

@ -8,10 +8,16 @@ import { createCanonicalLink } from 'utils/canonicalLink';
class SEO extends React.Component {
render () {
let { pageTitle, asset, channel, pageUri } = this.props;
pageTitle = createPageTitle(pageTitle);
const metaTags = createMetaTags(asset, channel);
const canonicalLink = createCanonicalLink(asset, channel, pageUri);
// props from state
const { defaultDescription, defaultThumbnail, siteDescription, siteHost, siteTitle, siteTwitter } = this.props;
// props from parent
const { asset, channel, pageUri } = this.props;
let { pageTitle } = this.props;
// create page title, tags, and canonical link
pageTitle = createPageTitle(siteTitle, pageTitle);
const metaTags = createMetaTags(siteDescription, siteHost, siteTitle, siteTwitter, asset, channel, defaultDescription, defaultThumbnail);
const canonicalLink = createCanonicalLink(asset, channel, pageUri, siteHost);
// render results
return (
<Helmet
title={pageTitle}

View file

@ -1,6 +1,6 @@
import React from 'react';
import { validateFile } from 'utils/file';
import Preview from 'components/Preview';
import PublishPreview from 'components/PublishPreview';
class Dropzone extends React.Component {
constructor (props) {
@ -87,7 +87,7 @@ class Dropzone extends React.Component {
<div id='preview-dropzone' className={'row row--padded row--tall dropzone' + (this.state.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}>
{this.props.file ? (
<div>
<Preview
<PublishPreview
dimPreview={this.state.dimPreview}
file={this.props.file}
thumbnail={this.props.thumbnail}

View file

@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import View from './view';
const mapStateToProps = ({ site: { host, title } }) => {
return {
host,
title,
};
};
export default connect(mapStateToProps, null)(View);

View file

@ -1,10 +1,10 @@
import React from 'react';
import NavBar from 'containers/NavBar';
import Helmet from 'react-helmet';
const { site: { title, host } } = require('../../../config/speechConfig.js');
class FourOhForPage extends React.Component {
render () {
const {title, host} = this.props;
return (
<div>
<Helmet>

View file

@ -1,13 +1,14 @@
import { connect } from 'react-redux';
import { updateLoggedInChannel } from 'actions/channel';
import {updateSelectedChannel} from 'actions/publish';
import View from './view';
import {updateSelectedChannel} from '../../actions/publish';
const mapStateToProps = ({ channel }) => {
const mapStateToProps = ({ channel, site }) => {
return {
channelName : channel.loggedInChannel.name,
channelShortId: channel.loggedInChannel.shortId,
channelLongId : channel.loggedInChannel.longId,
siteDescription: site.description,
};
};

View file

@ -53,12 +53,13 @@ class NavBar extends React.Component {
}
}
render () {
const { siteDescription } = this.props;
return (
<div className='row row--wide nav-bar'>
<div className='row row--padded row--short flex-container--row flex-container--space-between-center'>
<Logo />
<div className='nav-bar--center'>
<span className='nav-bar-tagline'>Open-source, decentralized image and video sharing.</span>
<span className='nav-bar-tagline'>{siteDescription}</span>
</div>
<div className='nav-bar--right'>
<NavLink className='nav-bar-link link--nav' activeClassName='link--nav-active' to='/' exact>Publish</NavLink>

View file

@ -0,0 +1,10 @@
import {connect} from 'react-redux';
import View from './view';
const mapStateToProps = ({ publish }) => {
return {
message: publish.disabledMessage,
};
};
export default connect(mapStateToProps, null)(View);

View file

@ -0,0 +1,16 @@
import React from 'react';
class PublishDisabledMessage extends React.Component {
render () {
const message = this.props.message;
console.log('this.props.message:', message);
return (
<div className='row dropzone--disabled row--tall flex-container--column flex-container--center-center'>
<p className='text--disabled'>Publishing is currently disabled.</p>
<p className='text--disabled'>{message}</p>
</div>
);
}
}
export default PublishDisabledMessage;

View file

@ -0,0 +1,30 @@
import React from 'react';
import Dropzone from 'containers/Dropzone';
import PublishDetails from 'containers/PublishDetails';
import PublishStatus from 'containers/PublishStatus';
import PublishDisabledMessage from 'containers/PublishDisabledMessage';
class PublishTool extends React.Component {
render () {
if (this.props.disabled) {
console.log('publish is disabled');
return (
<PublishDisabledMessage />
);
} else {
console.log('publish is not disabled');
if (this.props.file) {
if (this.props.status) {
return (
<PublishStatus />
);
} else {
return <PublishDetails />;
}
}
return <Dropzone />;
}
}
};
export default PublishTool;

View file

@ -1,7 +1,7 @@
import React from 'react';
import SEO from 'components/SEO';
import NavBar from 'containers/NavBar';
import ErrorPage from 'components/ErrorPage';
import ErrorPage from 'pages/ErrorPage';
import AssetTitle from 'containers/AssetTitle';
import AssetDisplay from 'containers/AssetDisplay';
import AssetInfo from 'containers/AssetInfo';

View file

@ -1,6 +1,6 @@
import React from 'react';
import SEO from 'components/SEO';
import ErrorPage from 'components/ErrorPage';
import ErrorPage from 'pages/ErrorPage';
import NavBar from 'containers/NavBar';
import ChannelClaimsDisplay from 'containers/ChannelClaimsDisplay';

View file

@ -1,5 +1,5 @@
import React from 'react';
import ErrorPage from 'components/ErrorPage';
import ErrorPage from 'pages/ErrorPage';
import ShowAssetLite from 'containers/ShowAssetLite';
import ShowAssetDetails from 'containers/ShowAssetDetails';
import ShowChannel from 'containers/ShowChannel';

View file

@ -1,9 +1,10 @@
import * as actions from 'constants/publish_action_types';
import { LOGIN } from 'constants/publish_channel_select_states';
const { publish } = require('../../config/speechConfig.js');
const { publishing } = require('../../config/siteConfig.js');
const initialState = {
disabled : publish.disabled,
disabled : publishing.disabled,
disabledMessage : publishing.disabledMessage,
publishInChannel : false,
selectedChannel : LOGIN,
showMetadataInputs: false,
@ -25,9 +26,7 @@ const initialState = {
license : '',
nsfw : false,
},
thumbnailChannel : publish.thumbnailChannel,
thumbnailChannelId: publish.thumbnailChannelId,
thumbnail : null,
thumbnail: null,
};
export default function (state = initialState, action) {

34
client/reducers/site.js Normal file
View file

@ -0,0 +1,34 @@
const siteConfig = require('../../config/siteConfig.js');
const {
analytics: {
googleId: googleAnalyticsId,
},
assetDefaults: {
thumbnail: defaultThumbnail,
description: defaultDescription,
},
details: {
description,
host,
title,
twitter,
},
} = siteConfig;
const initialState = {
description,
googleAnalyticsId,
host,
title,
twitter,
defaultDescription,
defaultThumbnail,
};
export default function (state = initialState, action) {
switch (action.type) {
default:
return state;
}
}

View file

@ -1,16 +1,18 @@
import { call, put, takeLatest } from 'redux-saga/effects';
import {call, put, select, takeLatest} from 'redux-saga/effects';
import * as actions from 'constants/show_action_types';
import { updateFileAvailability, updateDisplayAssetError } from 'actions/show';
import { UNAVAILABLE, AVAILABLE } from 'constants/asset_display_states';
import { checkFileAvailability, triggerClaimGet } from 'api/fileApi';
import { selectSiteHost } from 'selectors/site';
function * retrieveFile (action) {
const name = action.data.name;
const claimId = action.data.claimId;
const host = yield select(selectSiteHost);
// see if the file is available
let isAvailable;
try {
({ data: isAvailable } = yield call(checkFileAvailability, name, claimId));
({ data: isAvailable } = yield call(checkFileAvailability, claimId, host, name));
} catch (error) {
return yield put(updateDisplayAssetError(error.message));
};
@ -21,7 +23,7 @@ function * retrieveFile (action) {
yield put(updateFileAvailability(UNAVAILABLE));
// initiate get request for the file
try {
yield call(triggerClaimGet, name, claimId);
yield call(triggerClaimGet, claimId, host, name);
} catch (error) {
return yield put(updateDisplayAssetError(error.message));
};

View file

@ -3,6 +3,7 @@ import * as actions from 'constants/show_action_types';
import { addRequestToRequestList, onRequestError, onRequestUpdate, addAssetToAssetList } from 'actions/show';
import { getLongClaimId, getShortId, getClaimData } from 'api/assetApi';
import { selectShowState } from 'selectors/show';
import { selectSiteHost } from 'selectors/site';
export function * newAssetRequest (action) {
const { requestType, requestId, name, modifier } = action.data;
@ -11,13 +12,14 @@ export function * newAssetRequest (action) {
// is this an existing request?
// If this uri is in the request list, it's already been fetched
const state = yield select(selectShowState);
const host = yield select(selectSiteHost);
if (state.requestList[requestId]) {
return null;
}
// get long id && add request to request list
let longId;
try {
({data: longId} = yield call(getLongClaimId, name, modifier));
({data: longId} = yield call(getLongClaimId, host, name, modifier));
} catch (error) {
return yield put(onRequestError(error.message));
}
@ -31,14 +33,14 @@ export function * newAssetRequest (action) {
// get short Id
let shortId;
try {
({data: shortId} = yield call(getShortId, name, longId));
({data: shortId} = yield call(getShortId, host, name, longId));
} catch (error) {
return yield put(onRequestError(error.message));
}
// get asset claim data
let claimData;
try {
({data: claimData} = yield call(getClaimData, name, longId));
({data: claimData} = yield call(getClaimData, host, name, longId));
} catch (error) {
return yield put(onRequestError(error.message));
}

View file

@ -3,6 +3,7 @@ import * as actions from 'constants/show_action_types';
import { addNewChannelToChannelList, addRequestToRequestList, onRequestError, onRequestUpdate, updateChannelClaims } from 'actions/show';
import { getChannelClaims, getChannelData } from 'api/channelApi';
import { selectShowState } from 'selectors/show';
import { selectSiteHost } from 'selectors/site';
export function * newChannelRequest (action) {
const { requestType, requestId, channelName, channelId } = action.data;
@ -11,13 +12,14 @@ export function * newChannelRequest (action) {
// is this an existing request?
// If this uri is in the request list, it's already been fetched
const state = yield select(selectShowState);
const host = yield select(selectSiteHost);
if (state.requestList[requestId]) {
return null;
}
// get channel long id
let longId, shortId;
try {
({ data: {longChannelClaimId: longId, shortChannelClaimId: shortId} } = yield call(getChannelData, channelName, channelId));
({ data: {longChannelClaimId: longId, shortChannelClaimId: shortId} } = yield call(getChannelData, host, channelName, channelId));
} catch (error) {
return yield put(onRequestError(error.message));
}
@ -32,7 +34,7 @@ export function * newChannelRequest (action) {
// get channel claims data
let claimsData;
try {
({ data: claimsData } = yield call(getChannelClaims, channelName, longId, 1));
({ data: claimsData } = yield call(getChannelClaims, host, longId, channelName, 1));
} catch (error) {
return yield put(onRequestError(error.message));
}
@ -48,9 +50,10 @@ export function * watchNewChannelRequest () {
function * getNewClaimsAndUpdateChannel (action) {
const { channelKey, name, longId, page } = action.data;
const host = yield select(selectSiteHost);
let claimsData;
try {
({ data: claimsData } = yield call(getChannelClaims, name, longId, page));
({ data: claimsData } = yield call(getChannelClaims, host, longId, name, page));
} catch (error) {
return yield put(onRequestError(error.message));
}

7
client/selectors/site.js Normal file
View file

@ -0,0 +1,7 @@
export const selectSiteState = (state) => {
return state.site;
};
export const selectSiteHost = (state) => {
return state.site.host;
};

View file

@ -0,0 +1,29 @@
const createBasicCanonicalLink = (page, siteHost) => {
return `${siteHost}/${page}`;
};
const createAssetCanonicalLink = (asset, siteHost) => {
let channelName, certificateId, name, claimId;
if (asset.claimData) {
({ channelName, certificateId, name, claimId } = asset.claimData);
};
if (channelName) {
return `${siteHost}/${channelName}:${certificateId}/${name}`;
};
return `${siteHost}/${claimId}/${name}`;
};
const createChannelCanonicalLink = (channel, siteHost) => {
const { name, longId } = channel;
return `${siteHost}/${name}:${longId}`;
};
export const createCanonicalLink = (asset, channel, page, siteHost) => {
if (asset) {
return createAssetCanonicalLink(asset, siteHost);
}
if (channel) {
return createChannelCanonicalLink(channel, siteHost);
}
return createBasicCanonicalLink(page, siteHost);
};

Some files were not shown because too many files have changed in this diff Show more