Add context & dynamic route sagas, req for Osprey features

This commit is contained in:
Shawn 2018-09-27 22:56:10 -05:00
parent ec2aea5972
commit 158f0b4863
12 changed files with 200 additions and 91 deletions

View file

@ -2,10 +2,13 @@ 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) {
export function onHandleShowPageUri (params, url) {
return {
type: actions.HANDLE_SHOW_URI,
data: params,
data: {
...params,
url,
}
};
}

39
package-lock.json generated
View file

@ -1259,6 +1259,14 @@
"integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
"dev": true
},
"async-hook-jl": {
"version": "1.7.6",
"resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz",
"integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==",
"requires": {
"stack-chain": "1.3.7"
}
},
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
@ -2690,6 +2698,16 @@
"shimmer": "1.2.0"
}
},
"cls-hooked": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz",
"integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==",
"requires": {
"async-hook-jl": "1.7.6",
"emitter-listener": "1.1.1",
"semver": "5.5.1"
}
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -3640,6 +3658,14 @@
"minimalistic-crypto-utils": "1.0.1"
}
},
"emitter-listener": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.1.tgz",
"integrity": "sha1-6Lu+gkS8jg0LTvcc0UKUx/JBx+w=",
"requires": {
"shimmer": "1.2.0"
}
},
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@ -4365,6 +4391,14 @@
"promise": "7.3.1"
}
},
"express-http-context": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/express-http-context/-/express-http-context-1.1.0.tgz",
"integrity": "sha512-LS47HseitRIxzBHDEQrlVwZkEkMaViM+nhRCrlWYxPNIu7W8KUZyNUOxiD93OghHesl7y+DhBYuz3yfaNHDvVA==",
"requires": {
"cls-hooked": "4.2.2"
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -12893,6 +12927,11 @@
"tweetnacl": "0.14.5"
}
},
"stack-chain": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz",
"integrity": "sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU="
},
"stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",

View file

@ -54,6 +54,7 @@
"cookie-session": "^2.0.0-beta.3",
"express": "^4.15.2",
"express-handlebars": "^3.0.0",
"express-http-context": "^1.1.0",
"get-video-dimensions": "^1.0.0",
"helmet": "^3.13.0",
"image-size": "^0.6.3",

View file

@ -7,6 +7,7 @@ const cookieSession = require('cookie-session');
const http = require('http');
const logger = require('winston');
const Path = require('path');
const httpContext = require('express-http-context');
// load local modules
const db = require('./models');
@ -26,6 +27,14 @@ const {
},
} = require('@config/siteConfig');
function setRouteDataInContextMiddleware(routePath, routeData) {
return function (req, res, next) {
httpContext.set('routePath', routePath);
httpContext.set('routeData', routeData);
next();
};
}
function Server () {
this.initialize = () => {
// configure logging
@ -43,6 +52,9 @@ function Server () {
// set HTTP headers to protect against well-known web vulnerabilties
app.use(helmet());
// Support per-request http-context
app.use(httpContext.middleware);
// 'express.static' to serve static files from public directory
const publicPath = Path.resolve(process.cwd(), 'public');
app.use(express.static(publicPath));
@ -78,11 +90,15 @@ function Server () {
app.set('view engine', 'handlebars');
// set the routes on the app
require('./routes/auth/index')(app);
require('./routes/api/index')(app);
require('./routes/pages/index')(app);
require('./routes/assets/index')(app);
require('./routes/fallback/index')(app);
const routes = require('./routes');
Object.keys(routes).map((routePath) => {
let routeData = routes[routePath];
let routeMethod = routeData.hasOwnProperty('method') ? routeData.method : 'get';
let controllers = Array.isArray(routeData.controller) ? routeData.controller : [routeData.controller];
app[routeMethod](routePath, setRouteDataInContextMiddleware(routePath, routeData), ...controllers);
});
this.app = app;
};

View file

@ -18,6 +18,8 @@ var _effects = require("redux-saga/effects");
var _reactHelmet = _interopRequireDefault(require("react-helmet"));
var httpContext = _interopRequireWildcard(require("express-http-context"));
var _reducers = _interopRequireDefault(require("@reducers"));
var _GAListener = _interopRequireDefault(require("@components/GAListener"));
@ -28,6 +30,8 @@ var _sagas = _interopRequireDefault(require("@sagas"));
var _actions = _interopRequireDefault(require("@actions"));
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var returnSagaWithParams = function returnSagaWithParams(saga, params) {
@ -52,19 +56,17 @@ var returnSagaWithParams = function returnSagaWithParams(saga, params) {
};
module.exports = function (req, res) {
var context = {}; // create and apply middleware
var context = {};
var sagaMiddleware = (0, _reduxSaga.default)();
var middleware = (0, _redux.applyMiddleware)(sagaMiddleware); // create a new Redux store instance
var _httpContext$get = httpContext.get('routeData'),
_httpContext$get$acti = _httpContext$get.action,
action = _httpContext$get$acti === void 0 ? false : _httpContext$get$acti,
_httpContext$get$saga = _httpContext$get.saga,
saga = _httpContext$get$saga === void 0 ? false : _httpContext$get$saga;
var store = (0, _redux.createStore)(_reducers.default, middleware); // create an action to handle the given url,
// and create a the saga needed to handle that action
var runSaga = action !== false && saga !== false;
var action = _actions.default.onHandleShowPageUri(req.params);
var saga = returnSagaWithParams(_sagas.default.handleShowPageUri, action); // run the saga middleware with the saga call
sagaMiddleware.run(saga).done.then(function () {
var renderPage = function renderPage(store) {
// render component to a string
var html = (0, _server.renderToString)(_react.default.createElement(_reactRedux.Provider, {
store: store
@ -84,5 +86,27 @@ module.exports = function (req, res) {
var preloadedState = store.getState(); // send the rendered page back to the client
res.send((0, _renderFullPage.default)(helmet, html, preloadedState));
});
};
console.log(httpContext.get('routePath'), runSaga, httpContext.get('routeData'), action, saga);
if (runSaga) {
// create and apply middleware
var sagaMiddleware = (0, _reduxSaga.default)();
var middleware = (0, _redux.applyMiddleware)(sagaMiddleware); // create a new Redux store instance
var store = (0, _redux.createStore)(_reducers.default, middleware); // create an action to handle the given url,
// and create a the saga needed to handle that action
var boundAction = action(req.params, req.url);
var boundSaga = returnSagaWithParams(saga, boundAction); // run the saga middleware with the saga call
sagaMiddleware.run(boundSaga).done.then(function () {
return renderPage(store);
});
} else {
var _store = (0, _redux.createStore)(_reducers.default);
renderPage(_store);
}
};

View file

@ -7,6 +7,7 @@ import renderFullPage from '../renderFullPage';
import createSagaMiddleware from 'redux-saga';
import { call } from 'redux-saga/effects';
import Helmet from 'react-helmet';
import * as httpContext from 'express-http-context';
import Reducers from '@reducers';
import GAListener from '@components/GAListener';
@ -23,46 +24,60 @@ const returnSagaWithParams = (saga, params) => {
module.exports = (req, res) => {
let context = {};
// create and apply middleware
const sagaMiddleware = createSagaMiddleware();
const middleware = applyMiddleware(sagaMiddleware);
const {
action = false,
saga = false,
} = httpContext.get('routeData');
// create a new Redux store instance
const store = createStore(Reducers, middleware);
const runSaga = (action !== false && saga !== false);
// create an action to handle the given url,
// and create a the saga needed to handle that action
const action = Actions.onHandleShowPageUri(req.params);
const saga = returnSagaWithParams(Sagas.handleShowPageUri, action);
const renderPage = (store) => {
// render component to a string
const html = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<GAListener>
<App />
</GAListener>
</StaticRouter>
</Provider>
);
// run the saga middleware with the saga call
sagaMiddleware
.run(saga)
.done
.then(() => {
// render component to a string
const html = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<GAListener>
<App />
</GAListener>
</StaticRouter>
</Provider>
);
// get head tags from helmet
const helmet = Helmet.renderStatic();
// get head tags from helmet
const helmet = Helmet.renderStatic();
// check for a redirect
if (context.url) {
return res.redirect(301, context.url);
}
// check for a redirect
if (context.url) {
return res.redirect(301, context.url);
}
// get the initial state from our Redux store
const preloadedState = store.getState();
// get the initial state from our Redux store
const preloadedState = store.getState();
// send the rendered page back to the client
res.send(renderFullPage(helmet, html, preloadedState));
};
// send the rendered page back to the client
res.send(renderFullPage(helmet, html, preloadedState));
});
if (runSaga) {
// create and apply middleware
const sagaMiddleware = createSagaMiddleware();
const middleware = applyMiddleware(sagaMiddleware);
// create a new Redux store instance
const store = createStore(Reducers, middleware);
// create an action to handle the given url,
// and create a the saga needed to handle that action
const boundAction = action(req.params, req.url);
const boundSaga = returnSagaWithParams(saga, boundAction);
// run the saga middleware with the saga call
sagaMiddleware
.run(boundSaga)
.done
.then(() => renderPage(store) );
} else {
const store = createStore(Reducers);
renderPage(store);
}
};

View file

@ -21,31 +21,31 @@ const getTorList = require('../../controllers/api/tor');
const getBlockedList = require('../../controllers/api/blocked');
const getOEmbedData = require('../../controllers/api/oEmbed');
module.exports = (app) => {
module.exports = {
// channel routes
app.get('/api/channel/availability/:name', torCheckMiddleware, channelAvailability);
app.get('/api/channel/short-id/:longId/:name', torCheckMiddleware, channelShortId);
app.get('/api/channel/data/:channelName/:channelClaimId', torCheckMiddleware, channelData);
app.get('/api/channel/claims/:channelName/:channelClaimId/:page', torCheckMiddleware, channelClaims);
'/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/claims/:channelName/:channelClaimId/:page': { controller: [ torCheckMiddleware, channelClaims ] },
// claim routes
app.get('/api/claim/availability/:name', torCheckMiddleware, claimAvailability);
app.get('/api/claim/data/:claimName/:claimId', torCheckMiddleware, claimData);
app.get('/api/claim/get/:name/:claimId', torCheckMiddleware, claimGet);
app.get('/api/claim/list/:name', torCheckMiddleware, claimList);
app.post('/api/claim/long-id', torCheckMiddleware, claimLongId); // note: should be a 'get'
app.post('/api/claim/publish', torCheckMiddleware, multipartMiddleware, claimPublish);
app.get('/api/claim/resolve/:name/:claimId', torCheckMiddleware, claimResolve);
app.get('/api/claim/short-id/:longId/:name', torCheckMiddleware, claimShortId);
'/api/claim/availability/:name': { controller: [ torCheckMiddleware, claimAvailability ] },
'/api/claim/data/:claimName/:claimId': { controller: [ torCheckMiddleware, claimData ] },
'/api/claim/get/:name/:claimId': { controller: [ torCheckMiddleware, claimGet ] },
'/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, multipartMiddleware, claimPublish ] },
'/api/claim/resolve/:name/:claimId': { controller: [ torCheckMiddleware, claimResolve ] },
'/api/claim/short-id/:longId/:name': { controller: [ torCheckMiddleware, claimShortId ] },
// file routes
app.get('/api/file/availability/:name/:claimId', torCheckMiddleware, fileAvailability);
'/api/file/availability/:name/:claimId': { controller: [ torCheckMiddleware, fileAvailability ] },
// user routes
app.put('/api/user/password/', torCheckMiddleware, userPassword);
'/api/user/password/': { method: 'put', controller: [ torCheckMiddleware, userPassword ] },
// configs
app.get('/api/config/site/publishing', torCheckMiddleware, publishingConfig);
'/api/config/site/publishing': { controller: [ torCheckMiddleware, publishingConfig ] },
// tor
app.get('/api/tor', torCheckMiddleware, getTorList);
'/api/tor': { controller: [ torCheckMiddleware, getTorList ] },
// blocked
app.get('/api/blocked', torCheckMiddleware, getBlockedList);
'/api/blocked': { controller: [ torCheckMiddleware, getBlockedList ] },
// open embed
app.get('/api/oembed', torCheckMiddleware, getOEmbedData);
'/api/oembed': { controller: [ torCheckMiddleware, getOEmbedData ] },
};

View file

@ -1,7 +1,11 @@
const serveByClaim = require('../../controllers/assets/serveByClaim');
const serveByIdentifierAndClaim = require('../../controllers/assets/serveByIdentifierAndClaim');
module.exports = (app) => {
app.get('/:identifier/:claim', serveByIdentifierAndClaim);
app.get('/:claim', serveByClaim);
// TODO: Adjust build & sources to use import/export everywhere
const Actions = require('@actions').default;
const Sagas = require('@sagas').default;
module.exports = {
'/:identifier/:claim': { controller: serveByIdentifierAndClaim, action: Actions.onHandleShowPageUri, saga: Sagas.handleShowPageUri },
'/:claim': { controller: serveByClaim, action: Actions.onHandleShowPageUri, saga: Sagas.handleShowPageUri },
};

View file

@ -4,9 +4,9 @@ const handleLoginRequest = require('../../controllers/auth/login');
const handleLogoutRequest = require('../../controllers/auth/logout');
const handleUserRequest = require('../../controllers/auth/user');
module.exports = (app) => {
app.post('/signup', speechPassport.authenticate('local-signup'), handleSignupRequest);
app.post('/login', handleLoginRequest);
app.get('/logout', handleLogoutRequest);
app.get('/user', handleUserRequest);
module.exports = {
'/signup': { method: 'post', controller: [ speechPassport.authenticate('local-signup'), handleSignupRequest ] },
'/login': { method: 'post', controller: handleLoginRequest },
'/logout': { controller: handleLogoutRequest },
'/user': { controller: handleUserRequest },
};

View file

@ -1,5 +1,5 @@
const handlePageRequest = require('../../controllers/pages/sendReactApp');
module.exports = (app) => {
app.get('*', handlePageRequest);
module.exports = {
'*': { controller: handlePageRequest, action: 'fallback' },
};

7
server/routes/index.js Normal file
View file

@ -0,0 +1,7 @@
module.exports = {
...require('./pages'),
...require('./api'),
...require('./assets'),
...require('./auth'),
...require('./fallback'),
};

View file

@ -2,13 +2,13 @@ const handlePageRequest = require('../../controllers/pages/sendReactApp');
const handleVideoEmbedRequest = require('../../controllers/pages/sendVideoEmbedPage');
const redirect = require('../../controllers/utils/redirect');
module.exports = (app) => {
app.get('/', handlePageRequest);
app.get('/login', handlePageRequest);
app.get('/about', handlePageRequest);
app.get('/trending', redirect('/popular'));
app.get('/popular', handlePageRequest);
app.get('/new', handlePageRequest);
app.get('/multisite', handlePageRequest);
app.get('/video-embed/:name/:claimId', handleVideoEmbedRequest); // for twitter
module.exports = {
'/': { controller: handlePageRequest },
'/login': { controller: handlePageRequest },
'/about': { controller: handlePageRequest },
'/trending': { controller: redirect('/popular') },
'/popular': { controller: handlePageRequest },
'/new': { controller: handlePageRequest },
'/multisite': { controller: handlePageRequest },
'/video-embed/:name/:claimId': { controller: handleVideoEmbedRequest }, // for twitter
};