diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..06721631 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@babel/env", "@babel/react"], + "plugins": ["@babel/plugin-proposal-object-rest-spread"] +} diff --git a/.eslintignore b/.eslintignore index 8254ff89..9791ef95 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ node_modules/ -public/bundle +exports/ index.js -test +test/ +server/render/build diff --git a/.gitignore b/.gitignore index 0612b374..6f577b24 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,4 @@ -node_modules/ -.idea/ - -config/mysqlConfig.js -config/siteConfig.js - -public/bundle/ -index.js +node_modules +.idea +/devConfig/sequelizeCliConfig.js +/devConfig/testingConfig.js diff --git a/FAQ/Troubleshooting.md b/FAQ/Troubleshooting.md deleted file mode 100644 index 09b16bae..00000000 --- a/FAQ/Troubleshooting.md +++ /dev/null @@ -1,12 +0,0 @@ -## Common problems and errors - - - -## How to get help? - -For live help, you can join [our chat](https://chat.lbry.io) and post in the #speech channel. Please share a detailed message with the issue you are experiencing. -You can also [email LBRY](mailto:help@lbry.io) with questions or issues. - -## Report a bug - -To report an issue, open an issue directly on GitHub [here](https://github.com/lbryio/spee.ch). We would appreciate a quick search to see if a similar issues already exist, as well. The penalty for not doing so is a mild shaming. diff --git a/FAQ/speech-for-advanced-users b/FAQ/speech-for-advanced-users deleted file mode 100644 index 988113c9..00000000 --- a/FAQ/speech-for-advanced-users +++ /dev/null @@ -1,12 +0,0 @@ -This FAQ section is for the more advanced users or devlopers who would like to learn how spee.ch works under the hood. - - -## Spee.ch stack - - - - -## Spee.ch and LBRY network - - - diff --git a/FAQ/what-is-speech.md b/FAQ/what-is-speech.md deleted file mode 100644 index 8b137891..00000000 --- a/FAQ/what-is-speech.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/README.md b/README.md index 81850bea..a5f5c4ff 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,19 @@ # Spee.ch -Spee.ch is a web app that reads and publishes images and videos to and from the [LBRY](https://lbry.io/) blockchain. +This repo packages the spee.ch server for use with spee.ch implementations. ## Installation -* start mysql - * install mysql - * create a database called `lbry` - * save your connection `username` and `password` someplace handy -* start lbrynet daemon - * install the [`lbry`](https://github.com/lbryio/lbry) daemon - * start the `lbry` daemon -* start spee.ch - * clone this repo - * run `npm install` - * create your own config files in `/config` - * copy `mysqlConfig.js.example`, name it `mysqlConfig.js`, and update its contents. - * copy `siteConfig.js.example`, name it `siteConfig.js`, and update its contents. - * build the app by running `npm run build` - * for development, `npm run build-dev` will build the app and continue to listen for changes, building again when a change is made. - * to start the server, run `npm run start` - * for development, `npm run start-dev` will start the server and continue to listen for changes, restarting the server again whenever a change is made. - * for production, [pm2](http://pm2.keymetrics.io/docs/usage/quick-start/) is a great tool for starting and managing node processes -* visit [localhost:3000](http://localhost:3000) and check out your spee.ch app! -* start spee.ch-sync (optional, recommended) - * Note: this tool will decode blocks from the `lbry` blockchain and update the Claim and Certificate tables in mysql with all the claims from the blockchain. This is not necessary if you only want to host and resolve content published through your version of spee.ch, but it is required if you want to retrieve and host other content from the lbry network. - * install and run this [`speech-sync`](https://github.com/billbitt/spee.ch-sync) tool +visit [lbryio/www.spee.ch](https://github.com/lbryio/www.spee.ch) to get started -## Development & App Structure -* the `client/` folder houses all of the `react` and `redux` code - * `client.js` is the entry point for the react app - * [react components](https://reactjs.org/docs/react-component.html) are located in `client/components`, `client/containers`, and `client/pages` - * `/components` contains the 'dumb' components that receive props (if any) from their parents - * `/containers` contains the 'smart' redux-connected components that receive props from the `redux-store` - * `/pages` contains the components which act as the main pages of the app - * actions are located in the `client/actions` folder - * reducers are located in the `client/reducers` folder - * sagas are located in the `client/sagas` folder -* the `server/` folder contains all of server code - * `server.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 file. This file is the entry point for webpack to build the server bundle. - * the `/routes` folder contains all of the routes for the express app - * the `/models` folder contains all of the models which the app uses to interact with the `mysql` database. Note: this app uses the [sequelize](http://docs.sequelizejs.com/) orm. +## Development / Structure +* the `server/` folder 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 file. This file is the entry point for webpack to build the server bundle. + * the `server/routes` folder contains all of the routes for the express app + * the `server/models` folder contains all of the models which the app uses to interact with the `mysql` database. Note: this app uses the [sequelize](http://docs.sequelizejs.com/) ORM. * webpack - * During the build process, webpack creates two bundles for this project: - * (1) a client-side app bundle which will be located at `public/bundle/bundle.js` - * (2) a server bundle which will be located at `index.js` -* configuration - * the `config/` folder contains all of the required config files. The project contains `.example` files which can be copied to create the necessary `.js` files - * the `devConfig/` folder contains optional config files. Updating these files is not necessary. If you update these files, make sure to add them to your `.gitignore` file so they are not included in source control. + * During the build process, webpack creates a bundle for this project at `index.js`: ## Tests -* Spee.ch uses `mocha` with `chai` for testing. +* This package uses `mocha` with `chai` for testing. * To run all tests that do not require LBC, run `npm test -- --grep @usesLbc --invert` * To run all tests, including those that require LBC (like publishing), simply run `npm test` @@ -82,3 +47,23 @@ Spee.ch is a web app that reads and publishes images and videos to and from the ## Bugs If you find a bug or experience a problem, please report your issue here on github and find us in the lbry discord! + +## Contribute + +### Below is a guide to the issue tags in this repo +#### level 1 +Issues with spee.ch that anyone with basic web development can handle, little-to-no experience with the spee.ch codebase is required. + +#### level 2 +Issues with spee.ch familiarity with the spee.ch codebase is required, but little-to-no familiarity with the lbry daemon is necessary + +#### level 3 +Issues with spee.ch strong familiarity with the spee.ch code base and how the lbry daemon functions is required + +#### level 4 +Issues with lbry (e.g. the spee.ch wallet, lbrynet configuration, etc.) that require strong familiarity with the lbry daemon and/or network to fix. Generally these issues are best suited for the lbry protocol team but are placed in this repo because of they are part of the spee.ch implementation + +### Stack +The spee.ch stack is MySQL, Express.js, Node.js, React.js. Spee.ch runs lbrynet on its server, and spee.ch uses the lbrynet api to make requests such as `publish`, `create_channel`, and `get`. + +spee.ch also runs a sync tool, which decodes the `LBRY` blocks as they are mined and stores the claims in mysql. It stores all claims in the `Claims` table, and all channel claims in the `Certificates` table. diff --git a/client/actions/channel.js b/client/actions/channel.js deleted file mode 100644 index 7c17ed94..00000000 --- a/client/actions/channel.js +++ /dev/null @@ -1,14 +0,0 @@ -import * as actions from 'constants/channel_action_types'; - -// export action creators - -export function updateLoggedInChannel (name, shortId, longId) { - return { - type: actions.CHANNEL_UPDATE, - data: { - name, - shortId, - longId, - }, - }; -}; diff --git a/client/actions/publish.js b/client/actions/publish.js deleted file mode 100644 index 6666e9dd..00000000 --- a/client/actions/publish.js +++ /dev/null @@ -1,87 +0,0 @@ -import * as actions from 'constants/publish_action_types'; - -// export action creators -export function selectFile (file) { - return { - type: actions.FILE_SELECTED, - data: file, - }; -}; - -export function clearFile () { - return { - type: actions.FILE_CLEAR, - }; -}; - -export function updateMetadata (name, value) { - return { - type: actions.METADATA_UPDATE, - data: { - name, - value, - }, - }; -}; - -export function updateClaim (value) { - return { - type: actions.CLAIM_UPDATE, - data: value, - }; -}; - -export function setPublishInChannel (channel) { - return { - type: actions.SET_PUBLISH_IN_CHANNEL, - channel, - }; -}; - -export function updatePublishStatus (status, message) { - return { - type: actions.PUBLISH_STATUS_UPDATE, - data: { - status, - message, - }, - }; -}; - -export function updateError (name, value) { - return { - type: actions.ERROR_UPDATE, - data: { - name, - value, - }, - }; -}; - -export function updateSelectedChannel (channelName) { - return { - type: actions.SELECTED_CHANNEL_UPDATE, - data: channelName, - }; -}; - -export function toggleMetadataInputs (showMetadataInputs) { - return { - type: actions.TOGGLE_METADATA_INPUTS, - data: showMetadataInputs, - }; -}; - -export function onNewThumbnail (file) { - return { - type: actions.THUMBNAIL_NEW, - data: file, - }; -}; - -export function startPublish (history) { - return { - type: actions.PUBLISH_START, - data: { history }, - }; -} diff --git a/client/actions/show.js b/client/actions/show.js deleted file mode 100644 index 03a31c3e..00000000 --- a/client/actions/show.js +++ /dev/null @@ -1,119 +0,0 @@ -import * as actions from 'constants/show_action_types'; - -import { CHANNEL, ASSET_LITE, ASSET_DETAILS } from 'constants/show_request_types'; - -// basic request parsing -export function onHandleShowPageUri (params) { - return { - type: actions.HANDLE_SHOW_URI, - data: params, - }; -}; - -export function onRequestError (error) { - return { - type: actions.REQUEST_ERROR, - data: error, - }; -}; - -export function onNewChannelRequest (channelName, channelId) { - const requestType = CHANNEL; - const requestId = `cr#${channelName}#${channelId}`; - return { - type: actions.CHANNEL_REQUEST_NEW, - data: { requestType, requestId, channelName, channelId }, - }; -}; - -export function onNewAssetRequest (name, id, channelName, channelId, extension) { - const requestType = extension ? ASSET_LITE : ASSET_DETAILS; - const requestId = `ar#${name}#${id}#${channelName}#${channelId}`; - return { - type: actions.ASSET_REQUEST_NEW, - data: { - requestType, - requestId, - name, - modifier: { - id, - channel: { - name: channelName, - id : channelId, - }, - }, - }, - }; -}; - -export function onRequestUpdate (requestType, requestId) { - return { - type: actions.REQUEST_UPDATE, - data: { - requestType, - requestId, - }, - }; -}; - -export function addRequestToRequestList (id, error, key) { - return { - type: actions.REQUEST_LIST_ADD, - data: { id, error, key }, - }; -}; - -// asset actions - -export function addAssetToAssetList (id, error, name, claimId, shortId, claimData) { - return { - type: actions.ASSET_ADD, - data: { id, error, name, claimId, shortId, claimData }, - }; -} - -// channel actions - -export function addNewChannelToChannelList (id, name, shortId, longId, claimsData) { - return { - type: actions.CHANNEL_ADD, - data: { id, name, shortId, longId, claimsData }, - }; -}; - -export function onUpdateChannelClaims (channelKey, name, longId, page) { - return { - type: actions.CHANNEL_CLAIMS_UPDATE_ASYNC, - data: {channelKey, name, longId, page}, - }; -}; - -export function updateChannelClaims (channelListId, claimsData) { - return { - type: actions.CHANNEL_CLAIMS_UPDATE_SUCCESS, - data: {channelListId, claimsData}, - }; -}; - -// display a file - -export function fileRequested (name, claimId) { - return { - type: actions.FILE_REQUESTED, - data: { name, claimId }, - }; -}; - -export function updateFileAvailability (status) { - return { - type: actions.FILE_AVAILABILITY_UPDATE, - data: status, - }; -}; - -export function updateDisplayAssetError (error) { - return { - type: actions.DISPLAY_ASSET_ERROR, - data: error, - }; -}; diff --git a/client/api/assetApi.js b/client/api/assetApi.js deleted file mode 100644 index b6389612..00000000 --- a/client/api/assetApi.js +++ /dev/null @@ -1,34 +0,0 @@ -import Request from 'utils/request'; - -export function getLongClaimId (host, name, modifier) { - let body = {}; - // create request params - if (modifier) { - if (modifier.id) { - body['claimId'] = modifier.id; - } else { - body['channelName'] = modifier.channel.name; - body['channelClaimId'] = modifier.channel.id; - } - } - body['claimName'] = name; - const params = { - method : 'POST', - headers: { 'Content-Type': 'application/json' }, - body : JSON.stringify(body), - }; - // create url - const url = `${host}/api/claim/long-id`; - // return the request promise - return Request(url, params); -}; - -export function getShortId (host, name, claimId) { - const url = `${host}/api/claim/short-id/${claimId}/${name}`; - return Request(url); -}; - -export function getClaimData (host, name, claimId) { - const url = `${host}/api/claim/data/${name}/${claimId}`; - return Request(url); -}; diff --git a/client/api/channelApi.js b/client/api/channelApi.js deleted file mode 100644 index cf5738fc..00000000 --- a/client/api/channelApi.js +++ /dev/null @@ -1,13 +0,0 @@ -import Request from 'utils/request'; - -export function getChannelData (host, id, name) { - if (!id) id = 'none'; - const url = `${host}/api/channel/data/${name}/${id}`; - return Request(url); -}; - -export function getChannelClaims (host, longId, name, page) { - if (!page) page = 1; - const url = `${host}/api/channel/claims/${name}/${longId}/${page}`; - return Request(url); -}; diff --git a/client/api/fileApi.js b/client/api/fileApi.js deleted file mode 100644 index 62c71b78..00000000 --- a/client/api/fileApi.js +++ /dev/null @@ -1,11 +0,0 @@ -import Request from 'utils/request'; - -export function checkFileAvailability (claimId, host, name) { - const url = `${host}/api/file/availability/${name}/${claimId}`; - return Request(url); -} - -export function triggerClaimGet (claimId, host, name) { - const url = `${host}/api/claim/get/${name}/${claimId}`; - return Request(url); -} diff --git a/client/app.jsx b/client/app.jsx deleted file mode 100644 index 8d78b17a..00000000 --- a/client/app.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Route, Switch } from 'react-router-dom'; -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 ( - - - - - - - - - ); -}; - -export default App; diff --git a/client/channels/publish.js b/client/channels/publish.js deleted file mode 100644 index bdbfbf19..00000000 --- a/client/channels/publish.js +++ /dev/null @@ -1,48 +0,0 @@ -import {buffers, END, eventChannel} from 'redux-saga'; - -export const makePublishRequestChannel = (fd) => { - return eventChannel(emitter => { - const uri = '/api/claim/publish'; - const xhr = new XMLHttpRequest(); - // add event listeners - const onLoadStart = () => { - emitter({loadStart: true}); - }; - const onProgress = (event) => { - if (event.lengthComputable) { - const percentage = Math.round((event.loaded * 100) / event.total); - emitter({progress: percentage}); - } - }; - const onLoad = () => { - emitter({load: true}); - }; - xhr.upload.addEventListener('loadstart', onLoadStart); - xhr.upload.addEventListener('progress', onProgress); - xhr.upload.addEventListener('load', onLoad); - // set state change handler - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - const response = JSON.parse(xhr.response); - if ((xhr.status === 200) && response.success) { - emitter({success: response}); - emitter(END); - } else { - emitter({error: new Error(response.message)}); - emitter(END); - } - } - }; - // open and send - xhr.open('POST', uri, true); - xhr.send(fd); - // clean up - return () => { - xhr.upload.removeEventListener('loadstart', onLoadStart); - xhr.upload.removeEventListener('progress', onProgress); - xhr.upload.removeEventListener('load', onLoad); - xhr.onreadystatechange = null; - xhr.abort(); - }; - }, buffers.sliding(2)); -}; diff --git a/client/client.js b/client/client.js deleted file mode 100644 index bcd817fc..00000000 --- a/client/client.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { hydrate } from 'react-dom'; -import { Provider } from 'react-redux'; -import { createStore, applyMiddleware, compose } from 'redux'; -import { BrowserRouter } from 'react-router-dom'; -import Reducer from 'reducers'; -import createSagaMiddleware from 'redux-saga'; -import rootSaga from 'sagas'; - -import GAListener from 'components/GAListener'; -import App from './app'; - -// get the state from a global variable injected into the server-generated HTML -const preloadedState = window.__PRELOADED_STATE__ || null; - -// Allow the passed state to be garbage-collected -delete window.__PRELOADED_STATE__; - -// create and apply middleware -const sagaMiddleware = createSagaMiddleware(); -const middleware = applyMiddleware(sagaMiddleware); -const reduxMiddleware = window.__REDUX_DEVTOOLS_EXTENSION__ ? compose(middleware, window.__REDUX_DEVTOOLS_EXTENSION__()) : middleware; - -// create teh store -let store; -if (preloadedState) { - store = createStore(Reducer, preloadedState, reduxMiddleware); -} else { - store = createStore(Reducer, reduxMiddleware); -} - -// run the saga middlweare -sagaMiddleware.run(rootSaga); - -// render the app -hydrate( - - - - - - - , - document.getElementById('react-app') -); diff --git a/client/components/ActiveStatusBar/index.jsx b/client/components/ActiveStatusBar/index.jsx deleted file mode 100644 index 69a98440..00000000 --- a/client/components/ActiveStatusBar/index.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -const ActiveStatusBar = () => { - return | ; -}; - -export default ActiveStatusBar; diff --git a/client/components/AssetPreview/index.js b/client/components/AssetPreview/index.js deleted file mode 100644 index e431b9bd..00000000 --- a/client/components/AssetPreview/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import { connect } from 'react-redux'; -import View from './view'; - -const mapStateToProps = ({site: {defaults: { defaultThumbnail }}}) => { - return { - defaultThumbnail, - }; -}; - -export default connect(mapStateToProps, null)(View); diff --git a/client/components/AssetPreview/view.jsx b/client/components/AssetPreview/view.jsx deleted file mode 100644 index d98a1f4c..00000000 --- a/client/components/AssetPreview/view.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; - -const AssetPreview = ({ defaultThumbnail, claimData: { name, claimId, fileExt, contentType, thumbnail } }) => { - const directSourceLink = `${claimId}/${name}.${fileExt}`; - const showUrlLink = `/${claimId}/${name}`; - return ( -
- - {(() => { - switch (contentType) { - case 'image/jpeg': - case 'image/jpg': - case 'image/png': - case 'image/gif': - return ( - {name} - ); - case 'video/mp4': - return ( - {name} - ); - default: - return ( -

unsupported file type

- ); - } - })()} - -
- ); -}; - -export default AssetPreview; diff --git a/client/components/ExpandingTextArea/index.jsx b/client/components/ExpandingTextArea/index.jsx deleted file mode 100644 index db83c5d8..00000000 --- a/client/components/ExpandingTextArea/index.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -class ExpandingTextarea extends Component { - constructor (props) { - super(props); - this._handleChange = this._handleChange.bind(this); - } - componentDidMount () { - this.adjustTextarea({}); - } - _handleChange (event) { - const { onChange } = this.props; - if (onChange) onChange(event); - this.adjustTextarea(event); - } - adjustTextarea ({ target = this.el }) { - target.style.height = 0; - target.style.height = `${target.scrollHeight}px`; - } - render () { - const { ...rest } = this.props; - return ( -