Skip to content

redarrowlabs/tournavation-2015

Repository files navigation

Slack HuBoard Dependency Status devDependency Status

Tournavation-2015

MKE Tournavation project for 2015.

What is Tournavation

Tournavation is a social innovation project led by Red Arrow Labs and Dohmen Foundation. The goal is to address a social issue voted on by the community, and create a technical solution to help alleviate the problem. The project is developed by a volunteer team from the community, and gifted back as an open source project.

The Idea

Health disparities are inextricably linked with poverty (Milwaukee Health Report, 2013). At the core of the issue are three root causes that Health STEMheroes addresses: (1) lack of real-time information for both public servants and citizens, which hinders effective resource allocation to those most in need; (2) disempowerment of those in poverty, particularly adolescents, to understand and improve their own health; and (3) far too few students (especially females and students of color) pursuing studies and careers in science, technology, engineering, math (STEM), especially as such jobs boom in Milwaukee and would provide a path out of economic poverty that negatively affects health outcomes.

The Solution

The goal of this application is to allow Health Mentors to create experiments for Participants to track Health Behaviors and learn from the results.

About this project

This is an ES7 Node.js isomorphic/universal project with a RESTfull web API. Express is used for the HTTP server, as well as routing HTTP requests. React and Alt are leveraged for rendering and handling application flow.

Getting started

Contributing to this project

Anyone and everyone is welcome to contribute. Please take a moment to review the guidelines for contributing.

TL;DR

Project Breakdown

API (src/server/api)

The boilerplate code for the Express server should be pretty much done, so adding a new route should be pretty easy and straight forward.

  • Create a new route under src/server/api/routes (the file name should be descriptive of its purpose)
  • Export a function that will depend on an Express router, and add the desired routes to it
  • The routes should contain the business logic needed
  • If data access is needed, create a new Model under src/server/api/models
 export default function (router) {
  	router.route('/tests').get(function(req, res) {
  	    res.send({ message: 'Test get' });
  	});
  
  	router.route('/tests/:id').get(function(req, res) {
  	    res.send({ message: 'Test get: {_id}' });
  	});
  
  	router.route('/tests').post(function(req, res) {
  	    res.send({ message: 'Test post' });
  	});
  
  	router.route('/tests/:id').put(function(req,res){
  	    res.send({ message: 'Test put: {_id}' });
  	});
  
  	router.route('/tests/:id').delete(function(req, res) {
  	    res.send({ message: 'Test delete: {_id}' });
  	});
  }

Data access is done through Mongoose to MongoDB.

  • Create a new model under src/server/api/models (the file name should be descriptive of its purpose)
  • Define a schema for the model
  • Export the new model
  import mongoose from 'mongoose';
  var Schema = mongoose.Schema;
  
  var schema = new Schema({
  	name: String,
    key: String,
    data: Schema.Types.Mixed,
  });
  
  export default mongoose.model('Test', schema);

Web Server / Client

Code for client facing code can be found under src/shared. This is the client side running code and what is rendered server side. This is built with React and Flux (Alt implementation). This breaks the code down into actions, stores, and components.

Actions

Actions can be defined as the tasks that the application need to perform from user, or even application, events. Actions are used to dispatch events to stores, as well has make API calls. These are basic classes, wrapped by Alt. Actions should be wrapped in the Alt resolver for asynchronous work (and handled for server side rendering). The result of the work should be dispatched out for notifications. When a new action class is added, it needs to be registered in src/shared/actions/index.js.

class TestActions {

  constructor() {
    // Add dispatch only actions
    this.generateActions(
      'updateTests',
    );
  }

    fetchTests() {
      return (dispatch, alt) =>
        alt.resolve(async () => {
          var data = await alt.api.fetchAll('tests');      
          alt.getActions('tests').updateAllTests(data);
        });
    }
}

export default HealthBehaviorActions;

Stores

Stores are what hold (immutable) state for the application. When an action dispatches, it is the store that is listening to update the state. Like actions, stores are classes that get wrapped by (immutable) Alt. This makes it an immutable store, so state needs to be set a little differently then traditional Alt stores. When binding to a store, a convention is used: store methods need to be named on<ActionMethodName>. For example, if there was and action for fetchTests, then the store needs onFetchTests to bind for dispatch events. Similarly with Actions, Stores need to be registered in src/shared/stores/index.js.

import Immutable from 'immutable';
var immutableAlt = require('alt-utils/lib/ImmutableUtil');

@immutableAlt
class TestStore{
  displayName = 'TestStore'
  constructor() {
    this.bindActions(this.alt.getActions('tests'));
    
    this.state = Immutable.Map({
      tests: new Immutable.List([])
    });
  }

  onFetchTests() {
    this.setState(
      this.state.set('tests',
        this.state.get('tests').clear()));
  }

  onUpdateTests(payload) {
    this.setState(
      this.state.set('tests',
        Immutable.fromJS(payload)));
  }
};

export default TestStore;

Components

Components are React classes that render HTML. They use stores to get state, and actions to trigger events in the application. The application component hierarchy is defined by routes (src/shared/routes.js), and components can only comprise of one HTML node/element, which can contain child components. If a component requires any properties, they should be passed in from the parent component. The state should be set in getInitialState(), and updated by listening to the store(s) of interest when the component is mounted.

export default React.createClass ({

  // call initial fetch to load data, called on both client and server
  componentWillMount() {
    const { flux } = this.context;
    flux.getActions('tests').fetchAllHealthBehaviors();
  },

  // begin listening to store
  componentDidMount() {
    const { flux } = this.context;
    flux.getStore('tests').listen(this.stateChanged);
  },

  // remove listener when component is unmounted
  componentWillUnmount() {
    onst { flux } = this.context;
    flux.getStore('tests').unlisten(this.stateChanged);
  },

  stateChanged(state) {
    this.setState({
    	tests: state.get('tests')
    });
  },

  getInitialState() {
    const { flux } = this.context;
    return {
    	tests: flux.getStore('tests').getState().get('tests')
    };
  },

  renderListItem(test) {
    return (
      <li key={test.get('id')}>
      	{test.get('start')} - {test.get('end')}
      </li>
    );
  },

  render() {
    const { tests } = this.state;
    return (
      <div>
        <span>Logged data:</span>
    	<ul>{ tests.map(this.renderListItem) }</ul>
      </div>
    );
  }
});

Globalization

Globalize is used as the globalization engine. Resource files are one per locale, and are found in src/shared/globalization.

SSL/TLS

The production server runs under HTTPS while development is HTTP only. To configure SSL, set the environment variables SSL_CERT_PATH and SSL_KEY_PATH to the path of the SSL certificate and key. Let's Encrypt is one trusted Certificate Authority providing free certificates. A certificate that is signed by an untrusted Certificate Authority or a certificate that is self-signed will display a warning in the browser before the user can proceed. Production certificates should be signed by a trusted Certificate Authority, as forcing users to bypass this warning allows certain attacks. For testing purposes, a self-signed certificate is enough and any users will need to bypass the warning. Using the openssl command below, a self-signed certificate can be created.

openssl req -x509 -newkey rsa:2048 -keyout self-signed.key -out self-signed.crt -days XXX -nodes

Additionally when using self-signed certificates, set the environment variable NODE_TLS_REJECT_UNAUTHORIZED=0. This setting bypasses authentication of the SSL certificate on the Node server. Do not use this setting in production, as it allows many of the attacks that HTTPS was designed to prevent.

License

MIT License (MIT)

About

MKE Tournavation project for 2015.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published