├── LICENSE ├── README.md ├── client ├── .babelrc ├── .eslintrc ├── .gitignore ├── package.json ├── src │ ├── actions │ │ ├── asteroid.js │ │ └── index.js │ ├── app.js │ ├── components │ │ ├── add-todo.js │ │ ├── fetch-error.js │ │ ├── filter-link.js │ │ ├── footer.js │ │ ├── root.js │ │ ├── todo-app.js │ │ ├── todo-list.js │ │ └── todo.js │ ├── configure-asteroid.js │ ├── configure-store.js │ ├── index.html │ └── reducers │ │ ├── byId.js │ │ ├── createList.js │ │ └── index.js └── webpack.config.js └── server ├── .meteor ├── .finished-upgraders ├── .gitignore ├── .id ├── packages ├── platforms ├── release └── versions ├── imports └── server │ ├── api │ └── todos │ │ ├── methods.js │ │ ├── publications.js │ │ └── todos.js │ └── startup │ └── index.js ├── package.json └── server └── main.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Philipp Sporrer 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-redux-meteor 2 | 3 | A demo for using meteor (only) as your backend and react-redux as your frontend. 4 | 5 | I wrote a [blog post](https://blog.backlogapp.io/building-real-time-react-applications-with-redux-and-meteor-de29042eb460?gi=e166269ad5bb#.z8j0khk59) about this setup, which explains the setps you need to take to make your react-redux app real-time by connecting it to a meteor backend. 6 | 7 | The starting point for this example was the Todos App from Dan Abramov's ["Building React Applications with Idiomatic Redux"](https://egghead.io/courses/building-react-applications-with-idiomatic-redux) course ([Source code here](https://github.com/gaearon/todos)). If you are not sure how Redux works, watch the egghead.io courses first 🤓 8 | 9 | ## Features 10 | 11 | - React & Redux Frontend without any restrictions (no Meteor in the frontend) 12 | - live data *"for free"* by using a Meteor backend 13 | - optimistic UI 14 | - webpack (even in version 2.0 with tree-shaking enabled 🎉) 15 | - [Asteroid](https://github.com/mondora/asteroid) as DDP library 16 | 17 | ## How to run 18 | 19 | This app consists of a client and a server, we need to start both separately. This might sound trivial to you if you don't come from meteor 😉. 20 | 21 | ### Starting the client 22 | 23 | This will start your a `webpack-dev-server` so you can access your frontend at `http://localhost:8080` 24 | ```sh 25 | cd client 26 | npm i 27 | npm start 28 | ``` 29 | 30 | ### Starting the backend 31 | 32 | To start the meteor backend you first need to install meteor if you haven't already: 33 | ```sh 34 | curl https://install.meteor.com/ | sh 35 | ``` 36 | Then you can start your backend by simply running 37 | ```sh 38 | cd server 39 | meteor 40 | ``` 41 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["es2015", {"modules": false}], "react", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /client/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "parserOptions": { 6 | "ecmaVersion": 6, 7 | "sourceType": "module", 8 | "ecmaFeatures": { 9 | "jsx": true, 10 | "experimentalObjectRestSpread": true 11 | } 12 | }, 13 | "extends": "airbnb", 14 | "plugins": ["react"], 15 | "rules": { 16 | semi: ["error", "never"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-playground", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint": "eslint src", 9 | "validate": "npm-run-all --parallel validate-webpack:* lint", 10 | "validate-webpack:dev": "webpack-validator webpack.config.js --env.dev", 11 | "validate-webpack:prod": "webpack-validator webpack.config.js --env.prod", 12 | "clean-dist": "rimraf dist", 13 | "copy-files": "cpy src/index.html dist", 14 | "clean-and-copy": "npm run clean-dist && npm run copy-files", 15 | "prebuild": "npm run clean-and-copy", 16 | "prebuild:prod": "npm run clean-and-copy", 17 | "build": "webpack --env.dev", 18 | "build:prod": "webpack --env.prod -p", 19 | "prestart": "npm run clean-and-copy", 20 | "start": "webpack-dev-server --env.dev --history-api-fallback --content-base dist" 21 | }, 22 | "keywords": [], 23 | "author": "Philipp Sporrer ", 24 | "license": "MIT", 25 | "devDependencies": { 26 | "babel-core": "6.13.2", 27 | "babel-loader": "6.2.4", 28 | "babel-preset-es2015": "6.13.2", 29 | "babel-preset-react": "6.11.1", 30 | "babel-preset-stage-2": "6.11.0", 31 | "cpy-cli": "1.0.1", 32 | "eslint": "2.13.1", 33 | "eslint-config-airbnb": "9.0.1", 34 | "eslint-plugin-import": "1.11.1", 35 | "eslint-plugin-jsx-a11y": "1.5.5", 36 | "eslint-plugin-react": "5.2.2", 37 | "json-loader": "0.5.4", 38 | "npm-run-all": "2.3.0", 39 | "rimraf": "2.5.4", 40 | "webpack": "2.1.0-beta.20", 41 | "webpack-dev-server": "2.1.0-beta.0", 42 | "webpack-validator": "2.2.3" 43 | }, 44 | "dependencies": { 45 | "asteroid": "2.0.2", 46 | "lodash": "4.14.1", 47 | "meteor-random": "0.0.3", 48 | "node-uuid": "1.4.7", 49 | "normalizr": "2.2.1", 50 | "react": "15.2.1", 51 | "react-dom": "15.2.1", 52 | "react-redux": "4.4.5", 53 | "react-router": "3.0.0-alpha.2", 54 | "redux": "3.5.2", 55 | "redux-logger": "2.6.1", 56 | "redux-promise": "0.5.3", 57 | "redux-thunk": "2.1.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /client/src/actions/asteroid.js: -------------------------------------------------------------------------------- 1 | export default (dispatch, asteroid) => { 2 | asteroid.ddp.on('added', ({ collection, fields, id }) => { 3 | dispatch({ 4 | type: 'DDP_ADDED', 5 | response: { collection, doc: { id, ...fields } }, 6 | }) 7 | }) 8 | 9 | asteroid.ddp.on('changed', ({ collection, fields, id }) => { 10 | dispatch({ 11 | type: 'DDP_CHANGED', 12 | response: { collection, doc: { id, ...fields } }, 13 | }) 14 | }) 15 | 16 | asteroid.ddp.on('removed', ({ collection, id }) => { 17 | dispatch({ 18 | type: 'DDP_REMOVED', 19 | response: { collection, id }, 20 | }) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /client/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import random from 'meteor-random' 2 | import { getIsFetching, getTodo } from '../reducers' 3 | 4 | export const fetchTodos = (filter) => (dispatch, getState, asteroid) => { 5 | if (getIsFetching(getState(), filter)) { 6 | return Promise.resolve() 7 | } 8 | 9 | dispatch({ 10 | type: 'FETCH_TODOS_REQUEST', 11 | filter, 12 | }) 13 | 14 | return new Promise((resolve, reject) => { 15 | asteroid.subscribe('Todos.list', filter) 16 | .on('ready', () => { 17 | dispatch({ 18 | type: 'FETCH_TODOS_SUCCESS', 19 | filter, 20 | }) 21 | }) 22 | .on('error', error => { 23 | reject(error) 24 | dispatch({ 25 | type: 'FETCH_TODOS_FAILURE', 26 | filter, 27 | message: error.message || 'Something went wrong', 28 | }) 29 | }) 30 | }) 31 | } 32 | 33 | export const addTodo = (text) => (dispatch, getState, asteroid) => { 34 | // for optimistic UI we immediately dispatch an DDP_ADDED action 35 | const id = random.id() 36 | dispatch({ 37 | type: 'DDP_ADDED', 38 | response: { collection: 'todos', doc: { id, text, completed: false } }, 39 | }) 40 | asteroid.call('addTodo', id, text).then(() => { 41 | // if this succeeds the todo has already been added 42 | // so there is nothing more todo 43 | }) 44 | .catch(() => { 45 | // something went wrong when creating the new todo 46 | // since we optimistically added the todo already we need to remove it now 47 | dispatch({ 48 | type: 'DDP_REMOVED', 49 | response: { collection: 'todos', id }, 50 | }) 51 | }) 52 | } 53 | 54 | export const toggleTodo = (id) => (dispatch, getState, asteroid) => { 55 | const doc = getTodo(getState(), id) 56 | dispatch({ 57 | type: 'DDP_CHANGED', 58 | response: { collection: 'todos', doc: { ...doc, completed: !doc.completed } }, 59 | }) 60 | asteroid.call('toggleTodo', id) 61 | .catch(() => { 62 | // something went wrong when creating the new todo 63 | // since we optimistically added the todo already we need to remove it now 64 | dispatch({ 65 | type: 'DDP_CHANGED', 66 | response: { collection: 'todos', doc }, 67 | }) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /client/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Root from './components/root' 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root') 8 | ) 9 | -------------------------------------------------------------------------------- /client/src/components/add-todo.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { connect } from 'react-redux' 3 | import { addTodo } from '../actions' 4 | 5 | const AddTodo = ({ dispatch }) => { 6 | const handleAddTodo = () => { 7 | dispatch(addTodo(this.input.value)) 8 | this.input.value = '' 9 | } 10 | return ( 11 |
12 | { 15 | this.input = node 16 | }} 17 | /> 18 | 19 |
20 | ) 21 | } 22 | 23 | AddTodo.propTypes = { 24 | dispatch: PropTypes.func.isRequired, 25 | } 26 | 27 | export default connect()(AddTodo) 28 | -------------------------------------------------------------------------------- /client/src/components/fetch-error.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const FetchError = ({ message, onRetry }) => ( 4 |
5 |

Could not fetch Todos, {message}

6 | 7 |
8 | ) 9 | 10 | export default FetchError 11 | -------------------------------------------------------------------------------- /client/src/components/filter-link.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { Link } from 'react-router' 3 | 4 | const FilterLink = ({ filter, children }) => ( 5 | 12 | {children} 13 | 14 | ) 15 | 16 | FilterLink.propTypes = { 17 | filter: PropTypes.string.isRequired, 18 | children: PropTypes.any, 19 | } 20 | 21 | export default FilterLink 22 | -------------------------------------------------------------------------------- /client/src/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FilterLink from './filter-link' 3 | 4 | const Footer = () => ( 5 |
6 | {'Show'} 7 | 10 | {'All'} 11 | 12 | {' '} 13 | 16 | {'Active'} 17 | 18 | {' '} 19 | 22 | {'Completed'} 23 | 24 |
25 | ) 26 | 27 | export default Footer 28 | -------------------------------------------------------------------------------- /client/src/components/root.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import { Router, Route, browserHistory } from 'react-router' 4 | import TodoApp from './todo-app' 5 | import configureStore from '../configure-store' 6 | 7 | const Root = () => ( 8 | 9 | 10 | 11 | 12 | 13 | ) 14 | 15 | export default Root 16 | -------------------------------------------------------------------------------- /client/src/components/todo-app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TodoList from './todo-list' 3 | import Footer from './footer' 4 | import AddTodo from './add-todo' 5 | 6 | const TodoApp = () => ( 7 |
8 | 9 | 10 |
11 |
12 | ) 13 | 14 | export default TodoApp 15 | -------------------------------------------------------------------------------- /client/src/components/todo-list.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { withRouter } from 'react-router' 4 | import Todo from './todo' 5 | import * as actions from '../actions' 6 | import { getVisibleTodos, getIsFetching, getErrorMessage } from '../reducers/' 7 | import FetchError from './fetch-error' 8 | 9 | class VisibileTodoList extends Component { 10 | componentDidMount() { 11 | this.fetchData() 12 | } 13 | componentDidUpdate(prevProps) { 14 | if (this.props.filter !== prevProps.filter) { 15 | this.fetchData() 16 | } 17 | } 18 | fetchData() { 19 | const { props: { filter, fetchTodos } } = this 20 | fetchTodos(filter) 21 | } 22 | render() { 23 | const { props: { isFetching, todos, toggleTodo, errorMessage } } = this 24 | if (isFetching && !todos.length) { 25 | return

Loading...

26 | } 27 | if (errorMessage && !todos.length) { 28 | return ( this.fetchData()} 31 | />) 32 | } 33 | return 34 | } 35 | } 36 | 37 | VisibileTodoList.propTypes = { 38 | filter: PropTypes.string.isRequired, 39 | errorMessage: PropTypes.string, 40 | fetchTodos: PropTypes.func.isRequired, 41 | toggleTodo: PropTypes.func.isRequired, 42 | isFetching: PropTypes.bool.isRequired, 43 | todos: PropTypes.array.isRequired, 44 | } 45 | 46 | const TodoList = (props) => { 47 | const { todos, onToggleTodo } = props 48 | return ( 49 |
    50 | {todos.map(todo => 51 | 52 | )} 53 |
54 | ) 55 | } 56 | 57 | TodoList.propTypes = { 58 | todos: PropTypes.array.isRequired, 59 | onToggleTodo: PropTypes.func.isRequired, 60 | } 61 | 62 | const mapStateToProps = (state, { params }) => { 63 | const filter = params.filter || 'all' 64 | return { 65 | todos: getVisibleTodos( 66 | state, 67 | filter 68 | ), 69 | filter, 70 | isFetching: getIsFetching(state, filter), 71 | errorMessage: getErrorMessage(state, filter), 72 | } 73 | } 74 | 75 | export default withRouter(connect( 76 | mapStateToProps, 77 | actions 78 | )(VisibileTodoList)) 79 | -------------------------------------------------------------------------------- /client/src/components/todo.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Todo = ({ todo, onToggleTodo }) => { 4 | const handleToggleTodo = () => { 5 | onToggleTodo(todo.id) 6 | } 7 | return ( 8 |
  • 15 | {todo.text} 16 |
  • 17 | ) 18 | } 19 | 20 | Todo.propTypes = { 21 | todo: PropTypes.object.isRequired, 22 | onToggleTodo: PropTypes.func.isRequired, 23 | } 24 | 25 | export default Todo 26 | -------------------------------------------------------------------------------- /client/src/configure-asteroid.js: -------------------------------------------------------------------------------- 1 | import { createClass } from 'asteroid' 2 | 3 | const Asteroid = createClass() 4 | // Connect to a Meteor backend 5 | const asteroid = new Asteroid({ 6 | endpoint: 'ws://localhost:3000/websocket', 7 | }) 8 | 9 | export default asteroid 10 | -------------------------------------------------------------------------------- /client/src/configure-store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import createLogger from 'redux-logger' 3 | import reducers from './reducers/' 4 | import thunk from 'redux-thunk' 5 | import asteroid from './configure-asteroid' 6 | import initializeListeners from './actions/asteroid' 7 | 8 | const configureStore = () => { 9 | const middlewares = [thunk.withExtraArgument(asteroid)] 10 | 11 | if (process.env.NODE_ENV !== 'production') { 12 | middlewares.push(createLogger()) 13 | } 14 | 15 | const store = createStore( 16 | reducers, 17 | applyMiddleware(...middlewares) 18 | ) 19 | 20 | initializeListeners(store.dispatch, asteroid) 21 | 22 | return store 23 | } 24 | 25 | export default configureStore 26 | -------------------------------------------------------------------------------- /client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Redux Playground 6 | 7 | 8 |
    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/src/reducers/byId.js: -------------------------------------------------------------------------------- 1 | import omit from 'lodash/omit' 2 | 3 | const byId = (state = {}, action) => { 4 | if (action.type === 'DDP_CHANGED') { 5 | return { 6 | ...state, 7 | [action.response.doc.id]: { 8 | // merge the old doc with the changed fields 9 | ...state[action.response.doc.id], 10 | ...action.response.doc, 11 | }, 12 | } 13 | } 14 | if (action.type === 'DDP_ADDED') { 15 | return { 16 | ...state, 17 | [action.response.doc.id]: action.response.doc, 18 | } 19 | } 20 | if (action.type === 'DDP_REMOVED') { 21 | return omit(state, action.response.id) 22 | } 23 | return state 24 | } 25 | 26 | export default byId 27 | 28 | export const getTodo = (state, id) => state[id] 29 | -------------------------------------------------------------------------------- /client/src/reducers/createList.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import unique from 'lodash/uniq' 3 | 4 | const createList = (filter) => { 5 | const handleToggle = (state, action) => { 6 | const { response: { doc } } = action 7 | const { completed, id: toggleId } = doc 8 | const shouldRemove = ( 9 | (completed && filter === 'active') || 10 | (!completed && filter === 'completed') 11 | ) 12 | return shouldRemove 13 | ? state.filter(id => id !== toggleId) 14 | // otherwise add if not added yet 15 | : unique([...state, toggleId]) 16 | } 17 | 18 | const ids = (state = [], action) => { 19 | switch (action.type) { 20 | case 'DDP_ADDED': 21 | return ( 22 | filter === 'all' || 23 | (filter === 'active' && action.response.doc.completed === false) || 24 | (filter === 'completed' && action.response.doc.completed === true) 25 | ) ? unique([...state, action.response.doc.id]) : state 26 | case 'DDP_REMOVED': 27 | return state.filter(id => id !== action.response.id) 28 | case 'DDP_CHANGED': 29 | return handleToggle(state, action) 30 | default: 31 | return state 32 | } 33 | } 34 | 35 | const isFetching = (state = false, action) => { 36 | if (action.filter !== filter) { 37 | return state 38 | } 39 | switch (action.type) { 40 | case 'FETCH_TODOS_REQUEST': 41 | return true 42 | case 'FETCH_TODOS_SUCCESS': 43 | case 'FETCH_TODOS_FAILURE': 44 | return false 45 | default: 46 | return state 47 | } 48 | } 49 | 50 | const errorMessage = (state = null, action) => { 51 | if (action.filter !== filter) { 52 | return state 53 | } 54 | switch (action.type) { 55 | case 'FETCH_TODOS_FAILURE': 56 | return action.message 57 | case 'FETCH_TODOS_REQUEST': 58 | case 'FETCH_TODOS_SUCCESS': 59 | return null 60 | default: 61 | return state 62 | } 63 | } 64 | 65 | return combineReducers({ 66 | ids, 67 | isFetching, 68 | errorMessage, 69 | }) 70 | } 71 | 72 | export default createList 73 | 74 | export const getIds = (state) => state.ids 75 | export const getIsFetching = (state) => state.isFetching 76 | export const getErrorMessage = (state) => state.errorMessage 77 | -------------------------------------------------------------------------------- /client/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import byId, * as fromById from './byId' 3 | import createList, * as fromList from './createList' 4 | 5 | const listByFilter = combineReducers({ 6 | all: createList('all'), 7 | active: createList('active'), 8 | completed: createList('completed'), 9 | }) 10 | 11 | const todos = combineReducers({ 12 | byId, 13 | listByFilter, 14 | }) 15 | 16 | export default todos 17 | 18 | 19 | export const getVisibleTodos = (state, filter) => { 20 | const ids = fromList.getIds(state.listByFilter[filter]) 21 | return ids.map(id => fromById.getTodo(state.byId, id)) 22 | } 23 | 24 | export const getTodo = (state, id) => 25 | fromById.getTodo(state.byId, id) 26 | 27 | export const getIsFetching = (state, filter) => 28 | fromList.getIsFetching(state.listByFilter[filter]) 29 | 30 | export const getErrorMessage = (state, filter) => 31 | fromList.getErrorMessage(state.listByFilter[filter]) 32 | -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | module.exports = env => { 3 | return { 4 | entry: './app.js', 5 | output: { 6 | filename: 'bundle.js', 7 | path: path.resolve(__dirname, 'dist'), 8 | pathinfo: !env.prod, 9 | }, 10 | context: path.resolve(__dirname, 'src'), 11 | devtool: env.prod ? 'source-map' : 'eval', 12 | bail: env.prod, 13 | module: { 14 | loaders: [ 15 | { test: /\.js$/, loader: 'babel', exclude: /node_modules/ }, 16 | { test: /\.json$/, loader: 'json' }, 17 | ], 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.3.5-remove-old-dev-bundle-link 15 | 1.4.0-remove-old-dev-bundle-link 16 | -------------------------------------------------------------------------------- /server/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | dev_bundle 3 | -------------------------------------------------------------------------------- /server/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | g6430u1f326z56cd0vy 8 | -------------------------------------------------------------------------------- /server/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base@1.0.4 # Packages every Meteor app needs to have 8 | mobile-experience@1.0.4 # Packages for a great mobile UX 9 | mongo@1.1.10 # The database Meteor supports right now 10 | blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views 11 | reactive-var@1.0.10 # Reactive variable for tracker 12 | jquery@1.11.9 # Helpful client-side library 13 | tracker@1.1.0 # Meteor's client-side reactive programming library 14 | 15 | standard-minifier-css@1.1.8 # CSS minifier run for production mode 16 | standard-minifier-js@1.1.8 # JS minifier run for production mode 17 | es5-shim@4.6.13 # ECMAScript 5 compatibility for older browsers. 18 | ecmascript@0.5.7 # Enable ECMAScript2015+ syntax in app code 19 | 20 | insecure@1.0.7 # Allow all DB writes from clients (for prototyping) 21 | aldeed:collection2 22 | aldeed:simple-schema 23 | -------------------------------------------------------------------------------- /server/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /server/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.4.0.1 2 | -------------------------------------------------------------------------------- /server/.meteor/versions: -------------------------------------------------------------------------------- 1 | aldeed:collection2@2.9.1 2 | aldeed:collection2-core@1.1.1 3 | aldeed:schema-deny@1.0.1 4 | aldeed:schema-index@1.0.1 5 | aldeed:simple-schema@1.5.3 6 | allow-deny@1.0.5 7 | autoupdate@1.2.11 8 | babel-compiler@6.9.0 9 | babel-runtime@0.1.10 10 | base64@1.0.9 11 | binary-heap@1.0.9 12 | blaze@2.1.8 13 | blaze-html-templates@1.0.4 14 | blaze-tools@1.0.9 15 | boilerplate-generator@1.0.9 16 | caching-compiler@1.0.6 17 | caching-html-compiler@1.0.6 18 | callback-hook@1.0.9 19 | check@1.2.3 20 | ddp@1.2.5 21 | ddp-client@1.2.9 22 | ddp-common@1.2.6 23 | ddp-server@1.2.10 24 | deps@1.0.12 25 | diff-sequence@1.0.6 26 | ecmascript@0.5.7 27 | ecmascript-runtime@0.3.12 28 | ejson@1.0.12 29 | es5-shim@4.6.13 30 | fastclick@1.0.12 31 | geojson-utils@1.0.9 32 | hot-code-push@1.0.4 33 | html-tools@1.0.10 34 | htmljs@1.0.10 35 | http@1.1.8 36 | id-map@1.0.8 37 | insecure@1.0.7 38 | jquery@1.11.9 39 | launch-screen@1.0.12 40 | livedata@1.0.18 41 | logging@1.1.14 42 | mdg:validation-error@0.2.0 43 | meteor@1.2.16 44 | meteor-base@1.0.4 45 | minifier-css@1.2.13 46 | minifier-js@1.2.13 47 | minimongo@1.0.17 48 | mobile-experience@1.0.4 49 | mobile-status-bar@1.0.12 50 | modules@0.7.5 51 | modules-runtime@0.7.5 52 | mongo@1.1.10 53 | mongo-id@1.0.5 54 | npm-mongo@1.5.45 55 | observe-sequence@1.0.12 56 | ordered-dict@1.0.8 57 | promise@0.8.3 58 | raix:eventemitter@0.1.3 59 | random@1.0.10 60 | reactive-var@1.0.10 61 | reload@1.1.10 62 | retry@1.0.8 63 | routepolicy@1.0.11 64 | spacebars@1.0.12 65 | spacebars-compiler@1.0.12 66 | standard-minifier-css@1.1.8 67 | standard-minifier-js@1.1.8 68 | templating@1.1.14 69 | templating-tools@1.0.4 70 | tracker@1.1.0 71 | ui@1.0.11 72 | underscore@1.0.9 73 | url@1.0.10 74 | webapp@1.3.10 75 | webapp-hashing@1.0.9 76 | -------------------------------------------------------------------------------- /server/imports/server/api/todos/methods.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import Todos from './todos' 3 | 4 | Meteor.methods({ 5 | addTodo(_id, text) { 6 | // simulate slow server 7 | Meteor._sleepForMs(1000) 8 | 9 | // simulate add todo error 10 | // throw Error('NOT ALLOWED!!!') 11 | const todoId = Todos.insert({ _id, text }) 12 | return Todos.findOne(todoId) 13 | }, 14 | toggleTodo(_id) { 15 | // simulate slow server 16 | Meteor._sleepForMs(1000) 17 | 18 | // simulate toggle todo error 19 | // throw Error('NOT ALLOWED!!!') 20 | const todo = Todos.findOne(_id) 21 | Todos.update(_id, { $set: { completed: !todo.completed } }) 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /server/imports/server/api/todos/publications.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import Todos from './todos' 3 | 4 | Meteor.publish('Todos.list', (filter = 'all') => { 5 | const query = {} 6 | // simulate slow server 7 | Meteor._sleepForMs(1000) 8 | // simulate error when publishing 9 | // throw new Meteor.Error('Something went wrong!') 10 | if (filter === 'active') { 11 | query.completed = false 12 | } 13 | else if (filter === 'completed') { 14 | query.completed = true 15 | } 16 | return Todos.find(query) 17 | }) 18 | -------------------------------------------------------------------------------- /server/imports/server/api/todos/todos.js: -------------------------------------------------------------------------------- 1 | import { Mongo } from 'meteor/mongo' 2 | import { SimpleSchema } from 'meteor/aldeed:simple-schema' 3 | 4 | const Todos = new Mongo.Collection('todos') 5 | 6 | Todos.schema = new SimpleSchema({ 7 | _id: { 8 | type: String, 9 | }, 10 | text: { 11 | type: String, 12 | }, 13 | completed: { 14 | type: Boolean, 15 | defaultValue: false, 16 | }, 17 | }) 18 | 19 | Todos.attachSchema(Todos.schema) 20 | 21 | export default Todos 22 | -------------------------------------------------------------------------------- /server/imports/server/startup/index.js: -------------------------------------------------------------------------------- 1 | import '../api/todos/methods.js' 2 | import '../api/todos/publications.js' 3 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "private": true, 4 | "scripts": { 5 | "start": "meteor run" 6 | }, 7 | "dependencies": { 8 | "meteor-node-stubs": "~0.2.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /server/server/main.js: -------------------------------------------------------------------------------- 1 | import '/imports/server/startup' 2 | --------------------------------------------------------------------------------