diff --git a/client/index.js b/client/index.js
index e95bd0f908..c39b55c644 100644
--- a/client/index.js
+++ b/client/index.js
@@ -1,8 +1,9 @@
import Rx from 'rx';
import React from 'react';
+import Fetchr from 'fetchr';
+import debugFactory from 'debug';
import { Router } from 'react-router';
import { history } from 'react-router/lib/BrowserHistory';
-import debugFactory from 'debug';
import { hydrate } from 'thundercats';
import { Render } from 'thundercats-react';
@@ -11,6 +12,9 @@ import { app$ } from '../common/app';
const debug = debugFactory('fcc:client');
const DOMContianer = document.getElementById('fcc');
const catState = window.__fcc__.data || {};
+const services = new Fetchr({
+ xhrPath: '/services'
+});
Rx.longStackSupport = !!debug.enabled;
@@ -18,7 +22,7 @@ Rx.longStackSupport = !!debug.enabled;
app$(history)
.flatMap(
({ AppCat }) => {
- const appCat = AppCat();
+ const appCat = AppCat(null, services);
return hydrate(appCat, catState)
.map(() => appCat);
},
diff --git a/common/app/App.jsx b/common/app/App.jsx
index e97ac4d83f..6ebfa221f0 100644
--- a/common/app/App.jsx
+++ b/common/app/App.jsx
@@ -1,28 +1,37 @@
import React, { PropTypes } from 'react';
+import { contain } from 'thundercats-react';
import { Row } from 'react-bootstrap';
import { Nav } from './components/Nav';
import { Footer } from './components/Footer';
-export default class extends React.Component {
- constructor(props) {
- super(props);
- }
+export default contain(
+ {
+ store: 'appStore',
+ fetchAction: 'appActions.getUser',
+ getPayload(props) {
+ return {
+ isPrimed: !!props.username
+ };
+ }
+ },
+ React.createClass({
+ displayName: 'FreeCodeCamp',
- static displayName = 'FreeCodeCamp'
- static propTypes = {
- children: PropTypes.node
- }
+ propTypes: {
+ children: PropTypes.node
+ },
- render() {
- return (
-
-
-
- { this.props.children }
-
-
-
- );
- }
-}
+ render() {
+ return (
+
+
+
+ { this.props.children }
+
+
+
+ );
+ }
+ })
+);
diff --git a/common/app/Cat.js b/common/app/Cat.js
index d1686b78fc..3a7c3246e6 100644
--- a/common/app/Cat.js
+++ b/common/app/Cat.js
@@ -1,9 +1,11 @@
import { Cat } from 'thundercats';
import { HikesActions, HikesStore } from './routes/Hikes/flux';
-
+import { AppActions, AppStore } from './flux';
export default Cat()
- .init(({ instance }) => {
- instance.register(HikesActions);
- instance.register(HikesStore, null, instance);
+ .init(({ instance: cat, args: [services] }) => {
+ cat.register(AppActions, null, services);
+ cat.register(AppStore, null, cat);
+ cat.register(HikesActions, null, services);
+ cat.register(HikesStore, null, cat);
});
diff --git a/common/app/flux/Actions.js b/common/app/flux/Actions.js
new file mode 100644
index 0000000000..f915d34f9d
--- /dev/null
+++ b/common/app/flux/Actions.js
@@ -0,0 +1,31 @@
+import { Actions } from 'thundercats';
+import debugFactory from 'debug';
+
+const debug = debugFactory('freecc:app:actions');
+
+export default Actions({
+ setUser({ username, picture, progressTimestamps = [] }) {
+ return {
+ username,
+ picture,
+ points: progressTimestamps.length
+ };
+ },
+ getUser: null
+})
+ .refs({ displayName: 'AppActions' })
+ .init(({ instance: appActions, args: [services] }) => {
+ appActions.getUser.subscribe(({ isPrimed }) => {
+ if (isPrimed) {
+ debug('isPrimed');
+ return;
+ }
+ services.read('user', null, null, (err, user) => {
+ if (err) {
+ return debug('user service error');
+ }
+ debug('user service returned successful');
+ return appActions.setUser(user);
+ });
+ });
+ });
diff --git a/common/app/flux/Store.js b/common/app/flux/Store.js
new file mode 100644
index 0000000000..09d50e69c0
--- /dev/null
+++ b/common/app/flux/Store.js
@@ -0,0 +1,18 @@
+import { Store } from 'thundercats';
+
+const { createRegistrar, setter } = Store;
+const initValue = {
+ username: null,
+ picture: null,
+ points: 0
+};
+
+export default Store(initValue)
+ .refs({ displayName: 'AppStore' })
+ .init(({ instance: appStore, args: [cat] }) => {
+ const { setUser } = cat.getActions('appActions');
+ const register = createRegistrar(appStore);
+ register(setter(setUser));
+
+ return appStore;
+ });
diff --git a/common/app/flux/index.js b/common/app/flux/index.js
new file mode 100644
index 0000000000..a07e138ae6
--- /dev/null
+++ b/common/app/flux/index.js
@@ -0,0 +1,2 @@
+export AppActions from './Actions';
+export AppStore from './Store';
diff --git a/common/app/routes/Hikes/flux/Actions.js b/common/app/routes/Hikes/flux/Actions.js
index b3532f15f0..ddc4100564 100644
--- a/common/app/routes/Hikes/flux/Actions.js
+++ b/common/app/routes/Hikes/flux/Actions.js
@@ -1,12 +1,8 @@
import { Actions } from 'thundercats';
import assign from 'object.assign';
import debugFactory from 'debug';
-import Fetchr from 'fetchr';
const debug = debugFactory('freecc:hikes:actions');
-const service = new Fetchr({
- xhrPath: '/services'
-});
function getCurrentHike(hikes =[{}], dashedName, currentHike) {
if (!dashedName) {
@@ -36,7 +32,7 @@ export default Actions({
setHikes: null
})
.refs({ displayName: 'HikesActions' })
- .init(({ instance }) => {
+ .init(({ instance, args: [services] }) => {
// set up hikes fetching
instance.fetchHikes.subscribe(
({ isPrimed, dashedName }) => {
@@ -53,7 +49,7 @@ export default Actions({
}
});
}
- service.read('hikes', null, null, (err, hikes) => {
+ services.read('hikes', null, null, (err, hikes) => {
if (err) {
debug('an error occurred fetching hikes', err);
}
diff --git a/server/boot/a-react.js b/server/boot/a-react.js
index cf4bb86d43..9cf7d06f69 100644
--- a/server/boot/a-react.js
+++ b/server/boot/a-react.js
@@ -1,11 +1,12 @@
import React from 'react';
import Router from 'react-router';
+import Fetchr from 'fetchr';
import Location from 'react-router/lib/Location';
import debugFactory from 'debug';
import { app$ } from '../../common/app';
import { RenderToString } from 'thundercats-react';
-const debug = debugFactory('freecc:servereact');
+const debug = debugFactory('freecc:react-server');
// add routes here as they slowly get reactified
// remove their individual controllers
@@ -25,6 +26,7 @@ export default function reactSubRouter(app) {
app.use(router);
function serveReactApp(req, res, next) {
+ const services = new Fetchr({ req });
const location = new Location(req.path, req.query);
// returns a router wrapped app
@@ -42,7 +44,7 @@ export default function reactSubRouter(app) {
// prefetches data and sets up it up for current state
debug('rendering to string');
return RenderToString(
- AppCat(),
+ AppCat(null, services),
React.createElement(Router, initialState)
);
})
diff --git a/server/boot/a-services.js b/server/boot/a-services.js
index 3cb288a99d..a93cc217c9 100644
--- a/server/boot/a-services.js
+++ b/server/boot/a-services.js
@@ -1,8 +1,11 @@
import Fetchr from 'fetchr';
import getHikesService from '../services/hikes';
+import getUserServices from '../services/user';
export default function bootServices(app) {
const hikesService = getHikesService(app);
+ const userServices = getUserServices(app);
Fetchr.registerFetcher(hikesService);
+ Fetchr.registerFetcher(userServices);
app.use('/services', Fetchr.middleware());
}
diff --git a/server/services/user.js b/server/services/user.js
new file mode 100644
index 0000000000..5f293bfee0
--- /dev/null
+++ b/server/services/user.js
@@ -0,0 +1,30 @@
+import debugFactory from 'debug';
+import assign from 'object.assign';
+
+const censor = '**********************:P********';
+const debug = debugFactory('freecc:services:user');
+const protectedUserFields = {
+ id: censor,
+ password: censor,
+ profiles: censor
+};
+
+export default function userServices(/* app */) {
+ return {
+ name: 'user',
+ read: (req, resource, params, config, cb) => {
+ let { user } = req;
+ if (user) {
+ debug('user is signed in');
+ // Zalgo!!!
+ return process.nextTick(() => {
+ cb(null, assign({}, user.toJSON(), protectedUserFields));
+ });
+ }
+ debug('user is not signed in');
+ return process.nextTick(() => {
+ cb(null, {});
+ });
+ }
+ };
+}