├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── Procfile ├── README.md ├── client ├── .bowerrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app │ ├── actions │ │ ├── AuthActions.jsx │ │ ├── RouteActions.jsx │ │ ├── SubmitActions.jsx │ │ └── TopicsActions.jsx │ ├── app.jsx │ ├── app.tests.js │ ├── assets │ │ └── imgs │ │ │ ├── heart-active.svg │ │ │ └── heart.svg │ ├── components │ │ ├── Body │ │ │ ├── Body.jsx │ │ │ └── _Body.scss │ │ ├── Form │ │ │ ├── Form.jsx │ │ │ └── _Form.scss │ │ ├── Header │ │ │ ├── Header.jsx │ │ │ ├── _Header.scss │ │ │ └── __tests__ │ │ │ │ └── Header-test.jsx │ │ ├── Loader │ │ │ ├── Loader.jsx │ │ │ └── _Loader.scss │ │ └── Topic │ │ │ ├── Topic.jsx │ │ │ └── _Topic.scss │ ├── container │ │ ├── AuthContainer │ │ │ └── AuthContainer.jsx │ │ ├── HeaderContainer │ │ │ └── HeaderContainer.jsx │ │ ├── SubmitContainer │ │ │ └── SubmitContainer.jsx │ │ └── TopicContainer │ │ │ ├── TopicContainer.jsx │ │ │ └── _TopicContainer.scss │ ├── history.jsx │ ├── index.html │ ├── pages │ │ ├── Auth │ │ │ └── Auth.jsx │ │ ├── List │ │ │ └── List.jsx │ │ ├── NotFound │ │ │ └── NotFound.jsx │ │ └── Submit │ │ │ └── Submit.jsx │ ├── reducer │ │ ├── Auth.jsx │ │ ├── Submit.jsx │ │ └── Topics.jsx │ ├── router.jsx │ ├── scss │ │ ├── _base.scss │ │ ├── _btns.scss │ │ ├── _fonts.scss │ │ ├── _forms.scss │ │ ├── _functions.scss │ │ ├── _grid.scss │ │ ├── _mixins.scss │ │ ├── _toolkit.scss │ │ ├── _utils.scss │ │ ├── _variables.scss │ │ ├── app.scss │ │ └── vendor │ │ │ └── basscss-sass │ │ │ ├── .bower.json │ │ │ ├── README.md │ │ │ ├── _align.scss │ │ │ ├── _background-colors.scss │ │ │ ├── _background-images.scss │ │ │ ├── _base-forms.scss │ │ │ ├── _base-reset.scss │ │ │ ├── _base-tables.scss │ │ │ ├── _base-typography.scss │ │ │ ├── _border-colors.scss │ │ │ ├── _borders.scss │ │ │ ├── _btn-outline.scss │ │ │ ├── _btn-primary.scss │ │ │ ├── _btn-sizes.scss │ │ │ ├── _btn.scss │ │ │ ├── _color-base.scss │ │ │ ├── _color-forms-dark.scss │ │ │ ├── _color-forms.scss │ │ │ ├── _color-input-range.scss │ │ │ ├── _color-progress.scss │ │ │ ├── _color-tables.scss │ │ │ ├── _colors.scss │ │ │ ├── _defaults.scss │ │ │ ├── _flex-object.scss │ │ │ ├── _grid.scss │ │ │ ├── _highlight.scss │ │ │ ├── _input-range.scss │ │ │ ├── _positions.scss │ │ │ ├── _progress.scss │ │ │ ├── _responsive-states.scss │ │ │ ├── _responsive-white-space.scss │ │ │ ├── _table-object.scss │ │ │ ├── _type-scale.scss │ │ │ ├── _ui-utility-groups.scss │ │ │ ├── _utility-headings.scss │ │ │ ├── _utility-layout.scss │ │ │ ├── _utility-typography.scss │ │ │ ├── _white-space.scss │ │ │ ├── basscss.scss │ │ │ ├── bower.json │ │ │ ├── build.js │ │ │ └── package.json │ ├── store.jsx │ └── util │ │ ├── api.js │ │ ├── auth.js │ │ ├── mirror.js │ │ └── topic.js ├── bower.json ├── build │ ├── css │ │ └── app.0.0.16.css │ ├── index.html │ └── js │ │ └── app.0.0.16.js ├── dev-server.js ├── karma.conf.js ├── package.json └── webpack │ ├── config.js │ ├── config.test.js │ ├── loaders.js │ └── plugins.js ├── keystone.js ├── models ├── Like.js ├── Post.js └── User.js ├── package.json ├── public ├── favicon.ico ├── fonts │ └── bootstrap │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 ├── images │ ├── logo-email.gif │ └── logo.svg ├── js │ ├── bootstrap │ │ ├── affix.js │ │ ├── alert.js │ │ ├── bootstrap-3.3.5.js │ │ ├── bootstrap-3.3.5.min.js │ │ ├── button.js │ │ ├── carousel.js │ │ ├── collapse.js │ │ ├── dropdown.js │ │ ├── modal.js │ │ ├── npm.js │ │ ├── popover.js │ │ ├── scrollspy.js │ │ ├── tab.js │ │ ├── tooltip.js │ │ └── transition.js │ └── jquery │ │ ├── jquery-1.11.3.js │ │ ├── jquery-1.11.3.min.js │ │ ├── jquery-2.1.4.js │ │ └── jquery-2.1.4.min.js └── styles │ ├── bootstrap │ ├── _bootstrap-compass.scss │ ├── _bootstrap-mincer.scss │ ├── _bootstrap-sprockets.scss │ ├── _bootstrap.scss │ └── bootstrap │ │ ├── _alerts.scss │ │ ├── _badges.scss │ │ ├── _breadcrumbs.scss │ │ ├── _button-groups.scss │ │ ├── _buttons.scss │ │ ├── _carousel.scss │ │ ├── _close.scss │ │ ├── _code.scss │ │ ├── _component-animations.scss │ │ ├── _dropdowns.scss │ │ ├── _forms.scss │ │ ├── _glyphicons.scss │ │ ├── _grid.scss │ │ ├── _input-groups.scss │ │ ├── _jumbotron.scss │ │ ├── _labels.scss │ │ ├── _list-group.scss │ │ ├── _media.scss │ │ ├── _mixins.scss │ │ ├── _modals.scss │ │ ├── _navbar.scss │ │ ├── _navs.scss │ │ ├── _normalize.scss │ │ ├── _pager.scss │ │ ├── _pagination.scss │ │ ├── _panels.scss │ │ ├── _popovers.scss │ │ ├── _print.scss │ │ ├── _progress-bars.scss │ │ ├── _responsive-embed.scss │ │ ├── _responsive-utilities.scss │ │ ├── _scaffolding.scss │ │ ├── _tables.scss │ │ ├── _theme.scss │ │ ├── _thumbnails.scss │ │ ├── _tooltip.scss │ │ ├── _type.scss │ │ ├── _utilities.scss │ │ ├── _variables.scss │ │ ├── _wells.scss │ │ └── mixins │ │ ├── _alerts.scss │ │ ├── _background-variant.scss │ │ ├── _border-radius.scss │ │ ├── _buttons.scss │ │ ├── _center-block.scss │ │ ├── _clearfix.scss │ │ ├── _forms.scss │ │ ├── _gradients.scss │ │ ├── _grid-framework.scss │ │ ├── _grid.scss │ │ ├── _hide-text.scss │ │ ├── _image.scss │ │ ├── _labels.scss │ │ ├── _list-group.scss │ │ ├── _nav-divider.scss │ │ ├── _nav-vertical-align.scss │ │ ├── _opacity.scss │ │ ├── _pagination.scss │ │ ├── _panels.scss │ │ ├── _progress-bar.scss │ │ ├── _reset-filter.scss │ │ ├── _reset-text.scss │ │ ├── _resize.scss │ │ ├── _responsive-visibility.scss │ │ ├── _size.scss │ │ ├── _tab-focus.scss │ │ ├── _table-row.scss │ │ ├── _text-emphasis.scss │ │ ├── _text-overflow.scss │ │ └── _vendor-prefixes.scss │ ├── site.css │ ├── site.scss │ └── site │ ├── _layout.scss │ └── _variables.scss ├── routes ├── firebase.js ├── index.js ├── likes.js ├── middleware.js ├── posts.js ├── schema.js ├── utils.js └── views │ ├── .gitkeep │ └── test.js ├── templates └── views │ ├── helpers │ └── index.js │ ├── index.hbs │ ├── layouts │ └── default.hbs │ └── partials │ └── pagination.hbs ├── test ├── fixtures │ └── index.js ├── likes.test.js └── posts.test.js └── updates └── 0.0.1-admins.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = true 10 | indent_style = tab 11 | 12 | [{*.yml,*.json}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | public/js/bootstrap 2 | public/js/jquery 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "keystone", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | keystone_withenv.js 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | # Deployed apps should consider commenting this line out: 26 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Ignore .env configuration files 30 | .env 31 | 32 | # Ignore .DS_Store files on OS X 33 | .DS_Store 34 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node keystone.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | keystone-graphql 2 | ====== 3 | With a taste of apollo-stack and firebase and react and and and 4 | 5 | ## Setup local 6 | 7 | 1. create firebase account 8 | 2. create service account 9 | 3. create keystone_withenv.js in the root folder with following content 10 | 11 | ``` 12 | process.env.DB_URL = ''; 13 | process.env.C_EMAIL = ''; 14 | process.env.KEY = ''; 15 | process.env.P_ID = ''; 16 | 17 | require('./keystone'); 18 | 19 | ``` 20 | 21 | ## Setup heroku 22 | 23 | 1. create an mongodb here: https://www.mlab.com/ 24 | 2. create all those ENV vars below in the Heroku CLI or under https://dashboard.heroku.com/apps//settings 25 | 26 | ``` 27 | C_EMAIL='' 28 | DB_URL='' 29 | KEY='' 30 | P_ID='' 31 | MONGO_URI='' 32 | 33 | ``` 34 | 35 | ## Modifiy client code 36 | 37 | 1. check client/ folder 38 | 2. check package.json for available commands 39 | 40 | ## licence 41 | 42 | MIT 43 | -------------------------------------------------------------------------------- /client/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/scss/vendor/" 3 | } 4 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{js,json,jshintrc,html,jsx}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /client/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | script: 5 | - npm run test-travis 6 | -------------------------------------------------------------------------------- /client/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Richard Willis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /client/app/actions/AuthActions.jsx: -------------------------------------------------------------------------------- 1 | import { AuthConst } from '../reducer/Auth'; 2 | import * as utils from '../util/api'; 3 | import * as RouteActions from './RouteActions'; 4 | 5 | export function logout() { 6 | return { 7 | type: AuthConst.LOGOUT 8 | } 9 | } 10 | 11 | export function login(auth) { 12 | return (dispatch, getState) => { 13 | dispatch({ 14 | type: AuthConst.LOGIN_LOADING 15 | }); 16 | let userData; 17 | utils.firebaseInstance.auth() 18 | .signInWithEmailAndPassword(auth.username, auth.password).then(function(user){ 19 | userData = user; 20 | return utils.firebaseInstance.auth().currentUser.getToken(true); 21 | }).then(function(id) { 22 | RouteActions.go('/submit'); 23 | dispatch({ 24 | type: AuthConst.LOGIN_SUCCESS, 25 | payload: { 26 | id: userData.uid, 27 | authkey: id 28 | } 29 | }); 30 | }).catch(function(error) { 31 | dispatch({ 32 | type: AuthConst.LOGIN_ERROR 33 | }); 34 | }); 35 | }; 36 | } 37 | 38 | export function signup(auth) { 39 | return (dispatch, getState) => { 40 | dispatch({ 41 | type: AuthConst.SIGNUP_LOADING 42 | }); 43 | let userData; 44 | utils.firebaseInstance.auth() 45 | .createUserWithEmailAndPassword(auth.username, auth.password).then(function(user){ 46 | userData = user; 47 | return utils.firebaseInstance.auth().currentUser.getToken(true); 48 | }).then(function(id) { 49 | RouteActions.go('/submit'); 50 | dispatch({ 51 | type: AuthConst.SIGNUP_SUCCESS, 52 | payload: { 53 | id: userData.uid, 54 | authkey: id, 55 | username: '' 56 | } 57 | }); 58 | }).catch(function(error) { 59 | dispatch({ 60 | type: AuthConst.SIGNUP_ERROR 61 | }); 62 | }); 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /client/app/actions/RouteActions.jsx: -------------------------------------------------------------------------------- 1 | import history from '../history'; 2 | 3 | export function go(myPath) { 4 | history.push(myPath) 5 | } 6 | 7 | export function goOr(condition, r1, r2) { 8 | if(condition){ 9 | go(r1); 10 | }else{ 11 | go(r2); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /client/app/actions/SubmitActions.jsx: -------------------------------------------------------------------------------- 1 | import { SubmitConst } from '../reducer/Submit'; 2 | import * as utils from '../util/api'; 3 | import * as RouteActions from './RouteActions'; 4 | 5 | export function submitTopic(submitData) { 6 | return (dispatch, getState) => { 7 | dispatch({ 8 | type: SubmitConst.SUBMIT_LOADING 9 | }); 10 | utils.submit(submitData).then((data) => { 11 | RouteActions.go('/'); 12 | dispatch({ 13 | type: SubmitConst.SUBMIT_SUCCESS 14 | }); 15 | }).catch(() => { 16 | dispatch({ 17 | type: SubmitConst.SUBMIT_ERROR 18 | }); 19 | }) 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /client/app/actions/TopicsActions.jsx: -------------------------------------------------------------------------------- 1 | import { TopicConst } from '../reducer/Topics'; 2 | import * as utils from '../util/api'; 3 | import { isLiked, getLikeId } from '../util/topic'; 4 | import { isLoggedIn } from '../util/auth'; 5 | 6 | export function getList() { 7 | return (dispatch, getState) => { 8 | dispatch({ 9 | type: TopicConst.GETLIST_LOADING 10 | }); 11 | utils.list().then((data) => { 12 | dispatch({ 13 | type: TopicConst.GETLIST_SUCCESS, 14 | payload: data 15 | }); 16 | }).catch((err) => { 17 | console.log(err) 18 | dispatch({ 19 | type: TopicConst.GETLIST_ERROR 20 | }); 21 | }) 22 | } 23 | } 24 | 25 | export function toggleLikeTopic(topic, auth){ 26 | return (dispatch, getState) => { 27 | if(!isLoggedIn(auth)) return; 28 | if(isLiked(topic, auth.id)){ 29 | utils.unlike({ 30 | token: auth.authkey, 31 | likedId: getLikeId(topic, auth.id), 32 | topicId: topic.id 33 | }).then(() => { 34 | dispatch(getList()); 35 | }); 36 | }else{ 37 | utils.like({ 38 | token: auth.authkey, 39 | userId: auth.id, 40 | topicId: topic.id 41 | }).then(() => { 42 | dispatch(getList()); 43 | }); 44 | } 45 | } 46 | } 47 | 48 | export function toggleTopic(topic) { 49 | if(topic.open){ 50 | return { 51 | type: TopicConst.TOPIC_CLOSE, 52 | payload: topic.id 53 | }; 54 | }else{ 55 | return { 56 | type: TopicConst.TOPIC_OPEN, 57 | payload: topic.id 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /client/app/app.jsx: -------------------------------------------------------------------------------- 1 | import './index.html'; 2 | import 'babel-core/polyfill'; 3 | import 'normalize.css/normalize.css'; 4 | import './scss/app.scss'; 5 | 6 | import React, { Component, PropTypes } from 'react'; 7 | import { Provider, connect } from 'react-redux'; 8 | import ReactDOM from 'react-dom'; 9 | import { Store, Client } from './store.jsx'; 10 | import Router from './router.jsx'; 11 | import { ApolloProvider } from 'react-apollo'; 12 | 13 | class Root extends Component { 14 | render() { 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | } 22 | 23 | ReactDOM.render( 24 | , 25 | document.getElementById('app') 26 | ); 27 | -------------------------------------------------------------------------------- /client/app/app.tests.js: -------------------------------------------------------------------------------- 1 | import 'babel-core/polyfill'; 2 | 3 | let context = require.context('.', true, /-test\.jsx?$/); 4 | context.keys().forEach(context); 5 | -------------------------------------------------------------------------------- /client/app/assets/imgs/heart-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/app/assets/imgs/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/app/components/Body/Body.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import styles from './_Body.scss'; 3 | 4 | 5 | class Body extends Component { 6 | 7 | render() { 8 | return ( 9 |
10 |
11 | {this.props.children} 12 |
13 |
14 | ); 15 | } 16 | } 17 | 18 | export default Body; 19 | -------------------------------------------------------------------------------- /client/app/components/Body/_Body.scss: -------------------------------------------------------------------------------- 1 | @import "../../scss/_variables"; 2 | @import "../../scss/_fonts"; 3 | 4 | 5 | .body{ 6 | background: $bg-color; 7 | margin-top: 10px; 8 | &__wrap{ 9 | padding: $space $space * 2; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/app/components/Form/Form.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import styles from './_Form.scss'; 3 | import classNames from 'classnames'; 4 | import Loader from '../Loader/Loader'; 5 | 6 | class Form extends Component { 7 | 8 | _submit(e){ 9 | e.preventDefault(); 10 | 11 | let that = this; 12 | switch(this.props.mode){ 13 | case 'auth': 14 | this.props.submitAuth({ 15 | username: that.refs.username.value, 16 | password: that.refs.password.value, 17 | }); 18 | break; 19 | case 'topic': 20 | this.props.submitTopic({ 21 | title: that.refs.title.value, 22 | content: that.refs.content.value, 23 | }); 24 | break; 25 | } 26 | } 27 | 28 | _auth(){ 29 | 30 | return ( 31 |
32 | 33 | 34 | {(this.props.error) &&
Error
} 35 |
36 | 37 |
38 |
39 | ) 40 | } 41 | 42 | _topic(){ 43 | 44 | return ( 45 |
46 | 47 |