Skip to content

Commit

Permalink
Fix persist (#81)
Browse files Browse the repository at this point in the history
* fix persist problem
* bump cashay
  • Loading branch information
mattkrick authored and jordanh committed Jun 29, 2016
1 parent 36bae76 commit 29b10c4
Show file tree
Hide file tree
Showing 23 changed files with 314 additions and 337 deletions.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"start": "NODE_ENV=production node ./src/server/server.babel.js",
"dev": "NODE_ENV=development node ./src/server/server.babel.js",
"build": "rimraf build && concurrently \"npm run build:client\" \"npm run build:server\"",
"bs": "rimraf build && concurrently \"npm run build:client\" \"npm run build:server\" \"npm run start\"",
"bs": "npm run build && npm start",
"quickstart": "rimraf build && concurrently \"npm run db:migrate\" \"npm run build:client\" \"npm run build:server\" \"npm run start\"",
"build:client": "NODE_ENV=production webpack --config ./webpack/prod.babel.js",
"build:client-min": "NODE_ENV=production DEPLOY=true webpack --config ./webpack/prod.babel.js",
Expand Down Expand Up @@ -78,7 +78,7 @@
"babel-polyfill": "6.9.1",
"babel-runtime": "6.9.2",
"body-parser": "1.15.2",
"cashay": "^0.11.1",
"cashay": "^0.11.3",
"compression": "1.6.2",
"cors": "2.7.1",
"dotenv": "^2.0.0",
Expand Down Expand Up @@ -114,9 +114,10 @@
"redux": "3.5.2",
"redux-form": "^6.0.0-alpha.15",
"redux-logger": "2.6.1",
"redux-persist": "^3.2.2",
"redux-promise": "0.5.3",
"redux-socket-cluster": "0.5.1",
"redux-storage": "^4.0.1",
"redux-storage-engine-localstorage": "^1.1.1",
"redux-thunk": "2.1.0",
"rethink-migrate": "^1.3.1",
"rethinkdbdash": "2.3.10",
Expand Down
24 changes: 0 additions & 24 deletions src/client/cashayPersistTransform.js

This file was deleted.

103 changes: 45 additions & 58 deletions src/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,82 +4,69 @@ import {cashay} from 'cashay';
import ActionHTTPTransport from 'universal/utils/ActionHTTPTransport';
import makeStore from './makeStore';
import Root from './Root';
import {persistStore} from 'redux-persist';
import getAuth from 'universal/redux/getAuth';
import cashayPersistTransform from './cashayPersistTransform';

const {routing} = window.__INITIAL_STATE__; // eslint-disable-line no-underscore-dangle

const initialState = {
routing
};

const store = makeStore(initialState);

// Create the Cashay singleton:
let cashaySchema = null;
if (__PRODUCTION__) {
/*
* During the production client bundle build, the server will need to be
* stopped.
*/
// eslint-disable-next-line global-require
cashaySchema = require('cashay!../server/utils/getCashaySchema.js?stopRethink');
const createCashay = (store, cashaySchema) => {
const persistedToken = store.getState().authToken;
cashay.create({
store,
schema: cashaySchema,
transport: new ActionHTTPTransport(persistedToken)
});
};

persistStore(store, {blacklist: ['routing'], transforms: [cashayPersistTransform]}, () => {
// don't include a transport so getAuth doesn't send a request to the server
cashay.create({
store,
schema: cashaySchema
});
const auth = getAuth();
// authToken is undefined if this is a first-time visit or token expired
cashay.create({transport: new ActionHTTPTransport(auth.authToken)});
(async() => {
const store = await makeStore(initialState);
// Create the Cashay singleton:
let cashaySchema = null;
if (__PRODUCTION__) {
/*
* During the production client bundle build, the server will need to be
* stopped.
*/
// eslint-disable-next-line global-require
cashaySchema = require('cashay!../server/utils/getCashaySchema.js?stopRethink');
createCashay(store, cashaySchema);
render(
<Root store={store}/>,
document.getElementById('root')
);
});
} else {
/*
* Hey! We're the server. No need to stop rethink. The server will
* take care of that when it wants to exit.
*/
// eslint-disable-next-line global-require
cashaySchema = require('cashay!../server/utils/getCashaySchema.js');

// Hot Module Replacement API
// eslint-disable-next-line global-require
const {AppContainer} = require('react-hot-loader');
} else {
/*
* Hey! We're the server. No need to stop rethink. The server will
* take care of that when it wants to exit.
*/
// eslint-disable-next-line global-require
cashaySchema = require('cashay!../server/utils/getCashaySchema.js');

persistStore(store, {blacklist: ['routing'], transforms: [cashayPersistTransform]}, () => {
// don't include a transport so getAuth doesn't send a request to the server
cashay.create({
store,
schema: cashaySchema
});
const auth = getAuth();
// authToken is undefined if this is a first-time visit or token expired
cashay.create({transport: new ActionHTTPTransport(auth.authToken)});
// Hot Module Replacement API
// eslint-disable-next-line global-require
const {AppContainer} = require('react-hot-loader');
createCashay(store, cashaySchema);
render(
<AppContainer>
<Root store={store}/>
</AppContainer>,
document.getElementById('root')
);
});

if (module.hot) {
/* eslint-disable global-require, no-shadow */
module.hot.accept('./Root', () => {
const Root = require('./Root');
render(
<AppContainer>
<Root store={store}/>
</AppContainer>,
document.getElementById('root')
);
/* eslint-enable global-require */
});
if (module.hot) {
/* eslint-disable global-require, no-shadow */
module.hot.accept('./Root', () => {
const Root = require('./Root');
render(
<AppContainer>
<Root store={store}/>
</AppContainer>,
document.getElementById('root')
);
/* eslint-enable global-require */
});
}
}
}
})();
15 changes: 10 additions & 5 deletions src/client/makeStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@ import thunkMiddleware from 'redux-thunk';
import {routerMiddleware} from 'react-router-redux';
import {browserHistory} from 'react-router';
import makeReducer from '../universal/redux/makeReducer';
import {autoRehydrate} from 'redux-persist';
import createEngine from 'redux-storage-engine-localstorage';
import {APP_NAME} from 'universal/utils/clientOptions';
import {createMiddleware, createLoader} from 'redux-storage';


export default initialState => {
export default async initialState => {
let store;
const reducer = makeReducer();
const reduxRouterMiddleware = routerMiddleware(browserHistory);
const engine = createEngine(APP_NAME);
const storageMiddleware = createMiddleware(engine);
const middlewares = [
storageMiddleware,
reduxRouterMiddleware,
thunkMiddleware
];

if (__PRODUCTION__) {
store = createStore(reducer, initialState, compose(autoRehydrate(), applyMiddleware(...middlewares)));
store = createStore(reducer, initialState, compose(applyMiddleware(...middlewares)));
} else {
const devtoolsExt = global.devToolsExtension && global.devToolsExtension();
if (!devtoolsExt) {
Expand All @@ -30,9 +34,10 @@ export default initialState => {
}
store = createStore(reducer, initialState, compose(
applyMiddleware(...middlewares),
autoRehydrate(),
devtoolsExt || (f => f),
));
}
const load = createLoader(engine);
await load(store);
return store;
};
36 changes: 14 additions & 22 deletions src/server/graphql/models/CachedUser/cachedUserMutation.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import r from '../../../database/rethinkDriver';
import {GraphQLString} from 'graphql';
import {CachedUserAndToken} from './cachedUserSchema';
import {GraphQLString, GraphQLNonNull} from 'graphql';
import {CachedUser} from './cachedUserSchema';
import {AuthenticationClient} from 'auth0';
import {auth0} from '../../../../universal/utils/clientOptions';
import sendEmail from '../../../email/sendEmail';
import ms from 'ms';
import {errorObj} from '../utils';

const auth0Client = new AuthenticationClient({
domain: auth0.account,
Expand All @@ -14,24 +13,22 @@ const auth0Client = new AuthenticationClient({

export default {
updateUserWithAuthToken: {
type: CachedUserAndToken,
type: CachedUser,
description: 'Given a new auth token, grab all the information we can from auth0 about the user',
args: {
// even though the token comes with the bearer, we include it here we use it like an arg
// even though the token comes with the bearer, we include it here we use it like an arg since the gatekeeper
// decodes it into an object
authToken: {
type: GraphQLString,
type: new GraphQLNonNull(GraphQLString),
description: 'The ID Token from auth0, a base64 JWT'
}
},
async resolve(source, {authToken}) {
// This is the only resolve function where authToken refers to a base64 string and not an object
if (!authToken) {
throw errorObj({_error: 'No JWT was provided'});
}
const userInfo = await auth0Client.tokens.getInfo(authToken);
const now = new Date();
// TODO loginsCount and blockedFor are not a part of this API response
const newUserObj = {
const newUser = {
cachedAt: now,
// TODO set expiry here
cacheExpiresAt: new Date(now.valueOf() + ms('30d')),
Expand All @@ -48,23 +45,18 @@ export default {
loginsCount: userInfo.logins_count,
blockedFor: userInfo.blocked_for || []
};
const newUserAndToken = {
user: newUserObj,
authToken
};
const changes = await r.table('CachedUser').insert(newUserObj, {
const changes = await r.table('CachedUser').insert(newUser, {
conflict: 'update',
returnChanges: true
});
// Did we update an existing cached profile?
if (changes.replaced > 0) {
return newUserAndToken;
if (changes.replaced === 0) {
const emailWelcomed = await sendEmail('newUser', newUser);
const welcomeSentAt = emailWelcomed ? new Date() : null;
// must wait for write to UserProfile because a query could follow quickly after
await r.table('UserProfile').insert({id: newUser.id, welcomeSentAt, isNew: true});
}
const emailWelcomed = await sendEmail('newUser', newUserObj);
const welcomeSentAt = emailWelcomed ? new Date() : null;
await r.table('UserProfile').insert({id: newUserObj.id, welcomeSentAt, isNew: true});
// must wait for write to UserProfile because a query could follow quickly after
return newUserAndToken;
return newUser;
}
}
};
23 changes: 7 additions & 16 deletions src/server/graphql/models/CachedUser/cachedUserQuery.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {GraphQLNonNull, GraphQLID, GraphQLString} from 'graphql';
import {CachedUser, CachedUserAndToken} from './cachedUserSchema';
import {GraphQLNonNull, GraphQLID} from 'graphql';
import {CachedUser} from './cachedUserSchema';
import {errorObj} from '../utils';
import r from '../../../database/rethinkDriver';
import {requireAuth, requireSU} from '../authorization';
Expand All @@ -23,25 +23,16 @@ export default {
throw errorObj({_error: 'User ID not found'});
}
},
getUserWithAuthToken: {
type: CachedUserAndToken,
getCurrentUser: {
type: CachedUser,
description: 'Given an auth token, return the user and auth token',
args: {
authToken: {
type: new GraphQLNonNull(GraphQLString),
description: 'The ID Token from auth0, a base64 JWT'
}
},
async resolve(source, args, {authToken}) {
const userId = requireAuth(authToken);
const user = await r.table('CachedUser').get(userId);
if (user) {
return {
user,
authToken: args.authToken
};
if (!user) {
throw errorObj({_error: 'User ID not found'});
}
throw errorObj({_error: 'User ID not found'});
return user;
}
}
};
15 changes: 0 additions & 15 deletions src/server/graphql/models/CachedUser/cachedUserSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,3 @@ export const CachedUser = new GraphQLObjectType({
}
})
});

export const CachedUserAndToken = new GraphQLObjectType({
name: 'CachedUserAndToken',
description: 'The user account profile + JWT',
fields: () => ({
user: {
type: CachedUser,
description: 'The user account profile'
},
authToken: {
type: GraphQLString,
description: 'The JWT that comes from auth0'
}
})
});
1 change: 1 addition & 0 deletions src/server/graphql/models/Team/teamSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const Team = new GraphQLObjectType({
type: GraphQLISO8601Type,
description: 'The datetime the team was last updated (not including members)'
},
meetingSlug: {type: GraphQLString, description: 'The slug for the meeting uri'},
members: {
type: new GraphQLList(TeamMember),
description: 'All the team members associated with this team',
Expand Down
21 changes: 13 additions & 8 deletions src/universal/decorators/loginWithToken/loginWithToken.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import React, {Component} from 'react';
import React, {Component, PropTypes} from 'react';
import {push} from 'react-router-redux';
import getAuth from 'universal/redux/getAuth';
import {cashay} from 'cashay';
import getAuthedUser from 'universal/redux/getAuthedUser';

// eslint-disable-next-line arrow-body-style
export default ComposedComponent => {
return class TokenizedComp extends Component {
static propTypes = {
dispatch: PropTypes.func,
authToken: PropTypes.string
};

render() {
const auth = getAuth();
const {dispatch} = cashay.store;
const user = getAuthedUser();
const {dispatch, authToken} = this.props;

// remove expired tokens from state
if (auth.authToken) {
if (auth.user.profile.isNew) {
if (authToken && user) {
if (user.profile.isNew) {
dispatch(push('/welcome'));
} else {
} else if (user.profile.isNew === false) {
dispatch(push('/me'));
}
return null;
Expand Down
Loading

0 comments on commit 29b10c4

Please sign in to comment.