Skip to content
This repository has been archived by the owner on Jun 12, 2024. It is now read-only.

Commit

Permalink
added: documentation (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicznyleszek authored and MartijnR committed Aug 16, 2019
1 parent 95abc75 commit 520824d
Show file tree
Hide file tree
Showing 110 changed files with 22,899 additions and 319 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
30 changes: 29 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,20 @@
"node": true
},
"extends": "eslint:recommended",
"plugins": [
"jsdoc"
],
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 6
},
"settings": {
"jsdoc": {
"tagNamePreference": {
"returns": "return"
}
}
},
"rules": {
"indent": [ "warn", 4, {
"SwitchCase": 1
Expand All @@ -17,6 +27,24 @@
"linebreak-style": [ "error", "unix" ],
"quotes": [ "error", "single" ],
"semi": [ "error", "always" ],
"no-console": 0
"no-console": 0,
"jsdoc/check-examples": 1,
"jsdoc/check-param-names": 1,
"jsdoc/check-tag-names": 1,
"jsdoc/check-types": 1,
"jsdoc/newline-after-description": [ "warn", "always"],
"jsdoc/no-undefined-types": 1,
"jsdoc/require-description": 0,
"jsdoc/require-description-complete-sentence": 0,
"jsdoc/require-example": 0,
"jsdoc/require-hyphen-before-param-description": 1,
"jsdoc/require-param": 1,
"jsdoc/require-param-description": 1,
"jsdoc/require-param-name": 1,
"jsdoc/require-param-type": 1,
"jsdoc/require-returns-check": 1,
"jsdoc/require-returns-description": 1,
"jsdoc/require-returns-type": 1,
"jsdoc/valid-types": 1
}
}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
.scratchpad
.nodemon-*
.DS_Store
Thumbs.db
.env
*.sublime-project
*.sublime-workspace
*.diff
node_modules
npm-debug.log
public/css/*.css
**/build/*
translation_old.json
Expand All @@ -17,4 +19,4 @@ enketo-main.rdb
setup/docker/secrets/*
setup/docker/redis_main_data/*
logs/submissions*
logs/logrotate
logs/logrotate
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = grunt => {
const JS_INCLUDE = [ '**/*.js', '!**/node_modules/**', '!test/**/*.spec.js', '!public/js/build/*', '!test/client/config/karma.conf.js' ];
const JS_INCLUDE = [ '**/*.js', '!**/node_modules/**', '!test/**/*.spec.js', '!public/js/build/*', '!test/client/config/karma.conf.js', '!docs/**' ];
const path = require( 'path' );
const nodeSass = require( 'node-sass' );
const bundles = require( './buildFiles' ).bundles;
Expand Down
125 changes: 8 additions & 117 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,122 +1,12 @@
Enketo Express [![Build Status](https://travis-ci.org/enketo/enketo-express.svg?branch=master)](https://travis-ci.org/enketo/enketo-express) [![Dependency Status](https://david-dm.org/enketo/enketo-express.svg)](https://david-dm.org/enketo/enketo-express) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/609aaf6fa764454f901f1c8a427264ff)](https://www.codacy.com/app/martijnr/enketo-express?utm_source=github.com&utm_medium=referral&utm_content=enketo/enketo-express&utm_campaign=Badge_Grade)
[![Build Status](https://travis-ci.org/enketo/enketo-express.svg?branch=master)](https://travis-ci.org/enketo/enketo-express) [![Dependency Status](https://david-dm.org/enketo/enketo-express.svg)](https://david-dm.org/enketo/enketo-express) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/609aaf6fa764454f901f1c8a427264ff)](https://www.codacy.com/app/martijnr/enketo-express?utm_source=github.com&utm_medium=referral&utm_content=enketo/enketo-express&utm_campaign=Badge_Grade)

Enketo Express
==============

_The modern [Enketo Smart Paper](https://enketo.org) web application._

### How to install a test/development server

#### Manually:

1. Install JS prerequisites: [Node.js](https://github.com/nodesource/distributions) (8.x LTS), [Grunt Client](http://gruntjs.com).
2. Install [Redis](http://redis.io/topics/quickstart)
3. Install build-essential, curl and git with `(sudo) apt-get install build-essential git curl`
4. Clone this repository
5. Create config/config.json to override values in the [default config](./config/default-config.json). Start with `cp config/default-config.json config/config.json`
6. Install dependencies and build with `npm install` from the project root

#### Using vagrant:

This takes several shortcuts. Do not use for production!

1. Install [Vagrant](http://docs.vagrantup.com/v2/installation/index.html) and [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
2. Run `vagrant up` from project folder and wait until it completes \*
3. Log in to the VM with `vagrant ssh` and run `cd /vagrant && npm start`

The app should now be running on [localhost:8006](http://localhost:8006). You can test the API in a different console window with:
```curl --user enketorules: -d "server_url=https://ona.io/enketo&form_id=widgets" http://localhost:8006/api/v2/survey```.

_\* sometimes `vagrant up` fails for reasons beyond our control - e.g. if external resources are temporarily unavailable. Try running `vagrant reload --provision` to resolve this._

#### Using Docker:
1. Install [Docker Compose](http://docs.docker.com/compose/install/).
2. Create a [config file](./config/) at `config/config.json` specifying at minimum an API key.
3. **(Optional)** For HTTPS, copy your SSL certificate and key files to `setup/docker/secrets/ssl.crt` and `setup/docker/secrets/ssl.key` respectively (take care not to commit these files back to any public git repository). Plain HTTP requests to Enketo Express will be automatically redirected to HTTPS.
4. Execute `docker-compose up -d` from the [`setup/docker`](./setup/docker) directory and wait to see e.g. `Worker 1 ready for duty...`.
5. To stop, execute `docker-compose stop` from the [`setup/docker`](./setup/docker) directory. Database dumps from the main Redis instance will be mapped into the directory `setup/docker/redis_main_data/`.

The app should now be running on [localhost](http://localhost).

### How to install a production server

See [this tutorial](http://blog.enketo.org/install-enketo-production-ubuntu/) for detailed instructions. Another option, for some people, is to deploy with [Heroku](./doc/heroku.md).

### How to configure

All configuration is normally done in config/config.json. This file only has to contain the [default properties](./config/default-config.json) that you'd like to override. For some it may be preferrable to include all properties, to avoid surprises when the default configuration changes. Others may want to reduce hassle and keep the config.json as small as possible to automatically deploy configuration changes (e.g. new widgets). After editing the configuration, the app will need to be restarted.

As an alternative, there is an option to use environment variables instead of a config/config.json file. If the config/config.json file is missing Enketo will assume configuration is done with environment variables. A combination of both options is not supported. See [config/sample.env](./config/sample.env) for more information on equivalent environment variable names.

The default production configuration includes 2 redis instances. You can **greatly simplify installation by using 1 redis instance** instead (for non-production usage). To do this set the redis.cache.port to 6379 (same as redis.main.port). To set up 2 instances properly for production, you might find the vagrant setup steps in [bootstrap.sh](./setup/bootstrap.sh) useful.

For detailed guidance on each configuration item, see [this document](./config/README.md).

To configure your own custom external authentication also see [this section](#authentication).


### API

**Always** use the API to obtain webform URLs. Never try to craft or manipulate Enketo webform URLs yourself. This will make your Enketo integration future proof in case the URL structure changes. The API is stable, but webform URLs definitely are not.

The API is accessible on **/api/v2** and **/api/v1**. See the [documentation](http://apidocs.enketo.org) on how to use it.


### How to run
Run with `npm start` from project root.

You can now check that the app is running by going to e.g. http://localhost:8005 (depending on your server and port set in config/config.json or the port forwarding set up in Vagrant (default is 8006))

For a production server, we recommend using [pm2](https://github.com/unitech/pm2) or [forever](https://github.com/foreverjs/forever) to manage the node app.


### How to update

* update git repository with `git pull` (Check out a specific release (git tag) for a production server)
* update dependencies with `npm install --production` (This will run the CSS/JS builds automatically as well. If not, use `grunt` manually afterwards). You may have to remove `package-lock.json`.
* restart app


### Developer tools
* Install [nodemon](https://github.com/remy/nodemon) to automatically restart the server when a file changes.
* Install [gulp](http://gulpjs.com/) to automatically update the translation keys.
* Install [mocha](https://github.com/mochajs/mocha) to run tests.

The easiest way to start the app in development and debugging mode with livereload is with `grunt develop`.


### Browser support

See [this faq](https://enketo.org/faq/#browsers).

**Enketo endeavors to show a helpful (multi-lingual) error message on unsupported browsers when the form is loaded to avoid serious issues.**


### Themes

The default theme can be set in config/config.json. The default theme can be overridden in [the form definition](http://xlsform.org/#grid).

The recommended way to customize themes is to either:

* Create an issue (and fund or send a pull request) for changes to the existing themes, or
* Create your own theme in your own enketo-express port and add your custom theme in its own folder [here](app/views/styles). No other changes are required. A succesful rebuild with `grunt`, and your theme will become active when the app starts. The advantage of using this method instead of editing the existing themes, is that you will not have merge conflicts when you update your port! Add a print-specific version of your theme and use the same filenaming convention as the built-in themes.

See also [this further guidance](https://github.com/enketo/enketo-core#notes-for-css-developers).


### Authentication

This app can manage [OpenRosa form authentication](https://bitbucket.org/javarosa/javarosa/wiki/AuthenticationAPI) for protected forms, i.e. it is possible to log in to forms with credentials set in your OpenRosa Server (e.g. Aggregate/KoBo), just like in ODK Collect.

Alternatively, you could make use various _external authentication_ methods, i.e. using the authentication management of your form and data server.

For more information see [this documentation page](https://enketo.org/develop/auth/) and the [configuration documentation](./config/README.md#linked-form-and-data-server).

### Security

There are two major security considerations to be aware of. Both of these result in the need to run this application on **https** with a valid SSL certificate.

_API security_ is mainly arranged by the secret API key set up in config/config.json. This API key is sent in **cleartext** to Enketo by the form/data server (such as ODK Aggregate) and can easily be intercepted and read _if the transport is not secure_. Somebody could start using your Enketo Express installation for their own form/data server, or obtain the URLs of your forms. Using secure (https) transport mitigates against this hazard. Security increases as well by populating the _server url_ in config/config.json. Also, don't forget to change your API key when you start running Enketo Express in production.

_Form authentication_ is only secure when Enketo is running on **https**. To avoid leaking form server credentials, authentication is automatically disabled when the app is accessed in a 'production' environment on 'http'. If you **have to** to run the app on http in a production environment, you can bypass this security by setting `"allow insecure transport": true` in config/config.json. The only use case this would be acceptable in is when running the app on a local protected network (e.g. in the KoBo VM).
**To get started visit our [technical documentation](https://enketo.github.io/enketo-express).**


### Translation
Expand All @@ -128,11 +18,12 @@ _Send a message if you'd like to contribute! We use an easy web interface provid

### Funding

The development of this application was funded by [KoBo Toolbox (Harvard Humanitarian Initiative)](http://www.kobotoolbox.org), [iMMAP](http://immap.org), [OpenClinica](https://openclinica.com), and [Enketo LLC](https://enketo.org). The [Enketo-core](https://github.com/enketo/enketo-core) library (the form engine + themes) used in this application obtained significant funding from [SEL (Columbia University)](http://modi.mech.columbia.edu/), the [Santa Fe Institute](http://www.santafe.edu/), [Ona](https://ona.io) and the [HRP project](http://www.who.int/reproductivehealth/topics/mhealth/en/).
The development of this application was funded by [KoBo Toolbox (Harvard Humanitarian Initiative)](http://www.kobotoolbox.org), [iMMAP](http://immap.org), [OpenClinica](https://openclinica.com), and [Enketo LLC](https://enketo.org). The [Enketo-core](https://github.com/enketo/enketo-core) library (the form engine + themes) used in this application obtained significant funding from [SEL (Columbia University)](http://modi.mech.columbia.edu/), the [Santa Fe Institute](http://www.santafe.edu/), [Ona](https://ona.io) and the [HRP project](http://www.who.int/reproductivehealth/topics/mhealth/en/).


### License

See [the license document](LICENSE) for this application's license.
See [the license document](https://github.com/enketo/enketo-express/blob/master/LICENSE) for this application's license.

Note that some of the libraries used in this app have a different license. In particular note [this one](https://github.com/enketo/enketo-xpathjs).

Expand All @@ -143,4 +34,4 @@ The Enketo logo and Icons are trademarked by [Enketo LLC](https://www.linkedin.c

### Change log

See [change log](./CHANGELOG.md)
See [change log](https://github.com/enketo/enketo-express/blob/master/CHANGELOG.md)
27 changes: 25 additions & 2 deletions app/controllers/authentication-controller.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @module authentication-controller
*/

const csrfProtection = require( 'csurf' )( {
cookie: true
} );
Expand All @@ -15,6 +19,13 @@ router
.get( '/logout', logout )
.post( '/login', csrfProtection, setToken );

/**
* login
*
* @param {*} req
* @param {*} res
* @param {Function} next - callback for handling errors.
*/
function login( req, res, next ) {
let error;
const authSettings = req.app.get( 'linked form and data server' ).authentication;
Expand All @@ -23,8 +34,8 @@ function login( req, res, next ) {
if ( authSettings.type.toLowerCase() !== 'basic' ) {
if ( authSettings.url ) {
// the url is expected to:
// - authenticate the user,
// - set a session cookie (cross-domain if necessary) or add a token as query parameter to the return URL,
// - authenticate the user,
// - set a session cookie (cross-domain if necessary) or add a token as query parameter to the return URL,
// - and return the user back to Enketo
// - enketo will then pass the cookie or token along when requesting resources, or submitting data
// Though returnUrl was encoded with encodeURIComponent, for some reason it appears to have been automatically decoded here.
Expand All @@ -46,6 +57,12 @@ function login( req, res, next ) {
}
}

/**
* logout
*
* @param {*} req
* @param {*} res
*/
function logout( req, res ) {
res
.clearCookie( req.app.get( 'authentication cookie name' ) )
Expand All @@ -54,6 +71,12 @@ function logout( req, res ) {
.render( 'surveys/logout' );
}

/**
* setToken
*
* @param {*} req
* @param {*} res
*/
function setToken( req, res ) {
const username = req.body.username.trim();
const maxAge = 30 * 24 * 60 * 60 * 1000;
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/error-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

function getErrorMessage( req, error ) {
if ( error.message ) {
// convert certain set of messages into a more readable
// convert certain set of messages into a more readable
// and translated message
if ( /ECONNREFUSED/.test( error.message ) ) {
return req.i18n.t( 'error.econnrefused' );
Expand Down
29 changes: 19 additions & 10 deletions app/controllers/submission-controller.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @module submissions-controller
*/

const communicator = require( '../lib/communicator' );
const surveyModel = require( '../models/survey-model' );
const userModel = require( '../models/user-model' );
Expand Down Expand Up @@ -35,14 +39,13 @@ router
next( error );
} );

/**
/**
* Simply pipes well-formed request to the OpenRosa server and
* copies the response received.
*
* @param {[type]} req [description]
* @param {[type]} res [description]
* @param {Function} next [description]
* @return {[type]} [description]
* @param {*} req
* @param {*} res
* @param {Function} next - callback for handling errors.
*/
function submit( req, res, next ) {
let submissionUrl;
Expand Down Expand Up @@ -73,7 +76,7 @@ function submit( req, res, next ) {
// The Date header is actually forbidden to set programmatically, but we do it anyway to comply with OpenRosa
options.headers[ 'Date' ] = new Date().toUTCString();

// pipe the request
// pipe the request
req.pipe( request( options ) )
.on( 'response', orResponse => {
if ( orResponse.statusCode === 201 ) {
Expand All @@ -100,6 +103,13 @@ function submit( req, res, next ) {
.catch( next );
}

/**
* Get max submission size.
*
* @param {*} req
* @param {*} res
* @param {Function} next - callback for handling errors.
*/
function maxSize( req, res, next ) {
if ( req.query.xformUrl ) {
// Non-standard way of attempting to obtain max submission size from XForm url directly
Expand Down Expand Up @@ -129,10 +139,9 @@ function maxSize( req, res, next ) {
/**
* Obtains cached instance (for editing)
*
* @param {[type]} req [description]
* @param {[type]} res [description]
* @param {Function} next [description]
* @return {[type]} [description]
* @param {*} req
* @param {*} res
* @param {Function} next - callback for handling errors.
*/
function getInstance( req, res, next ) {
surveyModel.get( req.enketoId )
Expand Down
12 changes: 8 additions & 4 deletions app/controllers/survey-controller.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @module survey-controller
*/

const utils = require( '../lib/utils' );
const TError = require( '../lib/custom-error' ).TranslatedError;
const communicator = require( '../lib/communicator' );
Expand Down Expand Up @@ -141,10 +145,10 @@ function _renderWebform( req, res, next, options ) {

/**
* Debugging view that shows underlying XForm
* @param {[type]} req [description]
* @param {[type]} res [description]
* @param {Function} next [description]
* @return {[type]} [description]
*
* @param {*} req
* @param {*} res
* @param {Function} next - callback for handling errors.
*/
function xform( req, res, next ) {
return surveyModel.get( req.enketoId )
Expand Down
Loading

0 comments on commit 520824d

Please sign in to comment.