<commit_before>/**
 * TraCINg-Server - Gathering and visualizing cyber incidents on the world
 *
 * Copyright 2013 Matthias Gazzari, Annemarie Mattmann, André Wolski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This module provides methods of receiving POST data and storing them in a
 * database. Additionally it is send via socket.io to every client connected to
 * the server.
 */
// load node modules
var City = require("geoip").City;	// requires 'npm install geoip' and on ubuntu 'aptitude install libgeoip-dev'
var city = new City("GeoLiteCity.dat");

// load internal modules
var db = require("./db/db.js");

var debugInstance = 0;
var Debug = function(){
	var output = "";
	var instance = debugInstance++;

	this.get = function(){
		return output;
	};

	this.add = function(text){
		text = "PostHandler["+instance+"]: " + text;
		console.log(text);
		output += text + "\n";
	};
};

// process incomding data, respond with a status code, broadcast the received
// data and store the data in a database
function process (response, postData, authorized, sensor, io) {
	var debug = new Debug();
	debug.add("Processing " + (authorized?"authorized":"unauthorized") + " incoming data"); // "from '" + response.connection.remoteAddress + "'"

	if(postData.length == 0) {
		debug.add("Received no POST data.");
		response.writeHead(400, {"Content-Type": "text/plain"});
		response.write(debug.get());
		response.end();
	}
	else {
		try {
			var items = [];

			var lines = postData.trim().split("\n");
			debug.add("got " + lines.length + " line(s)");

			for(var i = 0; i < lines.length; i++){
				try {
					var parsedData = JSON.parse(lines[i].trim());

					// get geodata from ip
					var ipa, ipd;
					ipa = city.lookupSync(parsedData.src.ip);
					if(parsedData.dst)
						ipd = city.lookupSync(parsedData.dst.ip);

					// generate data
					var data = {
						sensorname: sensor.name || parsedData.sensor && parsedData.sensor.name || "Unknown",
						sensortype: sensor.type || parsedData.sensor && parsedData.sensor.type || "Unknown",
						src: {
							ip: parsedData.src.ip,
							port: parsedData.src && parsedData.src.port || 0,
						},
						dst: {
							ip: parsedData.dst && parsedData.dst.ip || null,
							port: parsedData.dst && parsedData.dst.port || 0,
						},
						type: parsedData.type || 0,
						log: parsedData.log || null,
						md5sum: parsedData.md5sum || null,
						date: parsedData.date && new Date(parsedData.date*1000) || new Date(), // now
						authorized: authorized,
					};

					if(ipa){
						data.src["ll"] = [ipa.latitude, ipa.longitude];
						data.src["country"] = ipa.country_name;
						data.src["cc"] = ipa.country_code;
						data.src["city"] = ipa.city || "";
					}

					if(ipd){
						data.dst["ll"] = [ipd.latitude, ipd.longitude];
						data.dst["country"] = ipd.country_name;
						data.dst["cc"] = ipd.country_code;
						data.dst["city"] = ipd.city || "";
					}

					// mark if attack ip is extern
					// add to database
					//if (ipa != null && ipd != null) {
					if (ipa) {
						// insert calls insertCallback which calls socket.emit
						items.push(data);
					}
					else {
						debug.add("An invalid source IP occured in line " + i + ": " + parsedData.src.ip + " (need to be a valid IP that could be resolved to a location via GeoIP)");
					}

				}
				catch (e) {
					debug.add("Received invalid JSON data in line " + i + ": " + e);
					debug.add("JSON String: " + lines[i].trim());
				}
			}

			if(items.length > 0){
				debug.add("try to insert " + items.length + " item(s) to the database");
				db.insert(items, function(err, items) {
					//console.log("dbInsertCallback", arguments);

					if(err){
						debug.add("dbInsert error: " + err);
						response.writeHead(400, {"Content-Type": "text/plain"});
					}
					else if(!items || items.length == 0){
						debug.add("no items inserted to the database");
						response.writeHead(400, {"Content-Type": "text/plain"});
					}
					else{
						debug.add("successfully inserted " + items.length + " item(s) to the database");
						response.writeHead(200, {"Content-Type": "text/plain"});
					}

					response.write(debug.get());
					response.end();

					if(!err){
						for(var i = 0; i < items.length; i++){
							io.sockets.emit("markIncident", items[i]);	// TODO send as one packet / one array
						}
					}
				});
			}
			else{
				debug.add("no items left to insert to the database!");
				response.writeHead(400, {"Content-Type": "text/plain"});
				response.write(debug.get());
				response.end();
			}
		}
		catch (e) {
			debug.add("Received invalid POST data. " + e);
			debug.add(e.stack);
			response.writeHead(400, {"Content-Type": "text/plain"});
			response.write(debug.get());
			response.end();
		}
	}
}

exports.process = process;
<commit_msg>check if the given port is valid, closes #20<commit_after>/**
 * TraCINg-Server - Gathering and visualizing cyber incidents on the world
 *
 * Copyright 2013 Matthias Gazzari, Annemarie Mattmann, André Wolski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This module provides methods of receiving POST data and storing them in a
 * database. Additionally it is send via socket.io to every client connected to
 * the server.
 */
// load node modules
var City = require("geoip").City;	// requires 'npm install geoip' and on ubuntu 'aptitude install libgeoip-dev'
var city = new City("GeoLiteCity.dat");

// load internal modules
var db = require("./db/db.js");

var debugInstance = 0;
var Debug = function(){
	var output = "";
	var instance = debugInstance++;

	this.get = function(){
		return output;
	};

	this.add = function(text){
		text = "PostHandler["+instance+"]: " + text;
		console.log(text);
		output += text + "\n";
	};
};

// process incomding data, respond with a status code, broadcast the received
// data and store the data in a database
function process (response, postData, authorized, sensor, io) {
	var debug = new Debug();
	debug.add("Processing " + (authorized?"authorized":"unauthorized") + " incoming data"); // "from '" + response.connection.remoteAddress + "'"

	if(postData.length == 0) {
		debug.add("Received no POST data.");
		response.writeHead(400, {"Content-Type": "text/plain"});
		response.write(debug.get());
		response.end();
	}
	else {
		try {
			var items = [];

			var lines = postData.trim().split("\n");
			debug.add("got " + lines.length + " line(s)");

			for(var i = 0; i < lines.length; i++){
				try {
					var parsedData = JSON.parse(lines[i].trim());

					// get geodata from ip
					var ipa, ipd;
					ipa = city.lookupSync(parsedData.src.ip);
					if(parsedData.dst)
						ipd = city.lookupSync(parsedData.dst.ip);

					// generate data
					var data = {
						sensorname: sensor.name || parsedData.sensor && parsedData.sensor.name || "Unknown",
						sensortype: sensor.type || parsedData.sensor && parsedData.sensor.type || "Unknown",
						src: {
							ip: parsedData.src.ip,
							port: parsedData.src && parsedData.src.port && (parsedData.src.port > 0) && (parsedData.src.port < 65535) && parsedData.src.port || 0,
						},
						dst: {
							ip: parsedData.dst && parsedData.dst.ip || null,
							port: parsedData.dst && parsedData.dst.port && (parsedData.dst.port > 0) && (parsedData.dst.port < 65535) && parsedData.dst.port || 0,
						},
						type: parsedData.type || 0,
						log: parsedData.log || null,
						md5sum: parsedData.md5sum || null,
						date: parsedData.date && new Date(parsedData.date*1000) || new Date(), // now
						authorized: authorized,
					};

					if(ipa){
						data.src["ll"] = [ipa.latitude, ipa.longitude];
						data.src["country"] = ipa.country_name;
						data.src["cc"] = ipa.country_code;
						data.src["city"] = ipa.city || "";
					}

					if(ipd){
						data.dst["ll"] = [ipd.latitude, ipd.longitude];
						data.dst["country"] = ipd.country_name;
						data.dst["cc"] = ipd.country_code;
						data.dst["city"] = ipd.city || "";
					}

					// mark if attack ip is extern
					// add to database
					//if (ipa != null && ipd != null) {
					if (ipa) {
						// insert calls insertCallback which calls socket.emit
						items.push(data);
					}
					else {
						debug.add("An invalid source IP occured in line " + i + ": " + parsedData.src.ip + " (need to be a valid IP that could be resolved to a location via GeoIP)");
					}

				}
				catch (e) {
					debug.add("Received invalid JSON data in line " + i + ": " + e);
					debug.add("JSON String: " + lines[i].trim());
				}
			}

			if(items.length > 0){
				debug.add("try to insert " + items.length + " item(s) to the database");
				db.insert(items, function(err, items) {
					//console.log("dbInsertCallback", arguments);

					if(err){
						debug.add("dbInsert error: " + err);
						response.writeHead(400, {"Content-Type": "text/plain"});
					}
					else if(!items || items.length == 0){
						debug.add("no items inserted to the database");
						response.writeHead(400, {"Content-Type": "text/plain"});
					}
					else{
						debug.add("successfully inserted " + items.length + " item(s) to the database");
						response.writeHead(200, {"Content-Type": "text/plain"});
					}

					response.write(debug.get());
					response.end();

					if(!err){
						for(var i = 0; i < items.length; i++){
							io.sockets.emit("markIncident", items[i]);	// TODO send as one packet / one array
						}
					}
				});
			}
			else{
				debug.add("no items left to insert to the database!");
				response.writeHead(400, {"Content-Type": "text/plain"});
				response.write(debug.get());
				response.end();
			}
		}
		catch (e) {
			debug.add("Received invalid POST data. " + e);
			debug.add(e.stack);
			response.writeHead(400, {"Content-Type": "text/plain"});
			response.write(debug.get());
			response.end();
		}
	}
}

exports.process = process;
<|endoftext|>


<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

var gulp = require('gulp');
var filter = require('gulp-filter');
var es = require('event-stream');
var path = require('path');

var all = [
	'build/**/*',
	'extensions/**/*',
	'scripts/**/*',
	'src/**/*',
	'test/**/*'
];

var eolFilter = [
	'**',
	'!extensions/**/out/**',
	'!**/node_modules/**',
	'!**/fixtures/**',
	'!**/*.{svg,exe,png,scpt,bat,cur,ttf,woff,eot}',
];

var indentationFilter = [
	'**',
	'!**/*.d.ts',
	'!extensions/typescript/server/**',
	'!test/assert.js',
	'!**/package.json',
	'!**/npm-shrinkwrap.json',
	'!**/octicons/**',
	'!**/vs/languages/sass/test/common/example.scss',
	'!**/vs/languages/less/common/parser/less.grammar.txt',
	'!**/vs/languages/css/common/buildscripts/css-schema.xml',
	'!**/vs/languages/markdown/common/raw.marked.js',
	'!**/vs/base/common/winjs.base.raw.js',
	'!**/vs/base/node/terminateProcess.sh',
	'!**/vs/base/node/terminateProcess.sh',
	'!**/vs/text.js',
	'!**/vs/nls.js',
	'!**/vs/css.js',
	'!**/vs/loader.js',
	'!extensions/**/snippets/**',
	'!extensions/**/syntaxes/**',
	'!extensions/**/themes/**',
];

var copyrightFilterList = [
	'**',
	'!**/*.json',
	'!**/*.html',
	'!**/test/**',
	'!**/*.md',
	'!**/*.sh',
	'!**/*.txt',
	'!src/vs/editor/standalone-languages/swift.ts',
];

var copyrightHeader = [
	'/*---------------------------------------------------------------------------------------------',
	' *  Copyright (c) Microsoft Corporation. All rights reserved.',
	' *  Licensed under the MIT License. See License.txt in the project root for license information.',
	' *--------------------------------------------------------------------------------------------*/'
].join('\n');

var hygiene = exports.hygiene = function (some) {
	var errorCount = 0;

	var eol = es.through(function (file) {
		if (/\r\n?/g.test(file.contents.toString('utf8'))) {
			console.error(file.relative + ': Bad EOL found');
			errorCount++;
		}

		this.emit('data', file);
	});

	var indentation = es.through(function (file) {
		file.contents
			.toString('utf8')
			.split(/\r\n|\r|\n/)
			.forEach(function(line, i) {
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
					errorCount++;
				}
			});

		this.emit('data', file);
	});

	var copyrights = es.through(function (file) {
		if (file.contents.toString('utf8').indexOf(copyrightHeader) !== 0) {
			console.error(file.relative + ': Missing or bad copyright statement');
			errorCount++;
		}

		this.emit('data', file);
	});

	return gulp.src(some || all, { base: '.' })
		.pipe(filter(function (f) { return !f.stat.isDirectory(); }))
		.pipe(filter(eolFilter))
		.pipe(eol)
		.pipe(filter(indentationFilter))
		.pipe(indentation)
		.pipe(filter(copyrightFilterList))
		.pipe(copyrights)
		.pipe(es.through(null, function () {
			if (errorCount > 0) {
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
			} else {
				this.emit('end');
			}
		}));
};

gulp.task('hygiene', function () {
	return hygiene();
});

// this allows us to run this as a git pre-commit hook
if (require.main === module) {
	var cp = require('child_process');
	cp.exec('git diff --cached --name-only', function (err, out) {
		if (err) {
			console.error();
			console.error(err);
			process.exit(1);
		}

		var some = out
			.split(/\r?\n/)
			.filter(function (l) { return !!l; });

		hygiene(some).on('error', function (err) {
			console.error();
			console.error(err);
			process.exit(1);
		});
	});
}
<commit_msg>Fix hygiene script<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

var gulp = require('gulp');
var filter = require('gulp-filter');
var es = require('event-stream');
var path = require('path');

var all = [
	'build/**/*',
	'extensions/**/*',
	'scripts/**/*',
	'src/**/*',
	'test/**/*'
];

var eolFilter = [
	'**',
	'!extensions/**/out/**',
	'!**/node_modules/**',
	'!**/fixtures/**',
	'!**/*.{svg,exe,png,scpt,bat,cur,ttf,woff,eot}',
];

var indentationFilter = [
	'**',
	'!**/lib/**',
	'!**/*.d.ts',
	'!extensions/typescript/server/**',
	'!test/assert.js',
	'!**/package.json',
	'!**/npm-shrinkwrap.json',
	'!**/octicons/**',
	'!**/vs/languages/sass/test/common/example.scss',
	'!**/vs/languages/less/common/parser/less.grammar.txt',
	'!**/vs/languages/css/common/buildscripts/css-schema.xml',
	'!**/vs/languages/markdown/common/raw.marked.js',
	'!**/vs/base/common/winjs.base.raw.js',
	'!**/vs/base/node/terminateProcess.sh',
	'!**/vs/base/node/terminateProcess.sh',
	'!**/vs/text.js',
	'!**/vs/nls.js',
	'!**/vs/css.js',
	'!**/vs/loader.js',
	'!extensions/**/snippets/**',
	'!extensions/**/syntaxes/**',
	'!extensions/**/themes/**',
];

var copyrightFilterList = [
	'**',
	'!**/*.json',
	'!**/*.html',
	'!**/test/**',
	'!**/*.md',
	'!**/*.sh',
	'!**/*.txt',
	'!src/vs/editor/standalone-languages/swift.ts',
];

var copyrightHeader = [
	'/*---------------------------------------------------------------------------------------------',
	' *  Copyright (c) Microsoft Corporation. All rights reserved.',
	' *  Licensed under the MIT License. See License.txt in the project root for license information.',
	' *--------------------------------------------------------------------------------------------*/'
].join('\n');

var hygiene = exports.hygiene = function (some) {
	var errorCount = 0;

	var eol = es.through(function (file) {
		if (/\r\n?/g.test(file.contents.toString('utf8'))) {
			console.error(file.relative + ': Bad EOL found');
			errorCount++;
		}

		this.emit('data', file);
	});

	var indentation = es.through(function (file) {
		file.contents
			.toString('utf8')
			.split(/\r\n|\r|\n/)
			.forEach(function(line, i) {
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
					errorCount++;
				}
			});

		this.emit('data', file);
	});

	var copyrights = es.through(function (file) {
		if (file.contents.toString('utf8').indexOf(copyrightHeader) !== 0) {
			console.error(file.relative + ': Missing or bad copyright statement');
			errorCount++;
		}

		this.emit('data', file);
	});

	return gulp.src(some || all, { base: '.' })
		.pipe(filter(function (f) { return !f.stat.isDirectory(); }))
		.pipe(filter(eolFilter))
		.pipe(eol)
		.pipe(filter(indentationFilter))
		.pipe(indentation)
		.pipe(filter(copyrightFilterList))
		.pipe(copyrights)
		.pipe(es.through(null, function () {
			if (errorCount > 0) {
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
			} else {
				this.emit('end');
			}
		}));
};

gulp.task('hygiene', function () {
	return hygiene();
});

// this allows us to run this as a git pre-commit hook
if (require.main === module) {
	var cp = require('child_process');
	cp.exec('git diff --cached --name-only', function (err, out) {
		if (err) {
			console.error();
			console.error(err);
			process.exit(1);
		}

		var some = out
			.split(/\r?\n/)
			.filter(function (l) { return !!l; });

		hygiene(some).on('error', function (err) {
			console.error();
			console.error(err);
			process.exit(1);
		});
	});
}
<|endoftext|>


<commit_before>import fetch from 'isomorphic-fetch'
import checkStatus from './../utils/checkStatus'
import { browserHistory } from 'react-router'
import validator from 'validator'

export const FETCH_ACTIVE_SURVEYS_REQUEST = 'FETCH_ACTIVE_SURVEYS_REQUEST'
export const FETCH_ACTIVE_SURVEYS_SUCCESS = 'FETCH_ACTIVE_SURVEYS_SUCCESS'
export const FETCH_ACTIVE_SURVEYS_FAILURE = 'FETCH_ACTIVE_SURVEYS_FAILURE'

export function fetchActiveSurveysRequest() {
  return {
    type: FETCH_ACTIVE_SURVEYS_REQUEST
  }
}

export function fetchActiveSurveysSuccess(response) {
  return {
    type: FETCH_ACTIVE_SURVEYS_SUCCESS,
    response
  }
}

export function fetchActiveSurveysFailure(error) {
  return {
    type: FETCH_ACTIVE_SURVEYS_FAILURE,
    error
  }
}

export function fetchActiveSurveys() {
  return function(dispatch) {
    dispatch(fetchActiveSurveysRequest())
    return fetch('/api/survey')
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(fetchActiveSurveysSuccess(response))
      // If only one survey comes back, go ahead and make the follow up API
      // to set it as active.
      if (response.length === 1) {
        dispatch(selectActiveSurvey(response[0]))
      }
    })
    .catch(error => dispatch(fetchActiveSurveysFailure(error)))
  }
}

export const SELECT_ACTIVE_SURVEY = 'SELECT_ACTIVE_SURVEY'
export const SELECT_ACTIVE_SURVEY_REQUEST = 'SELECT_ACTIVE_SURVEY_REQUEST'
export const SELECT_ACTIVE_SURVEY_SUCCESS = 'SELECT_ACTIVE_SURVEY_SUCCESS'
export const SELECT_ACTIVE_SURVEY_FAILURE = 'SELECT_ACTIVE_SURVEY_FAILURE'

export function selectActiveSurvey(survey) {
  return function(dispatch) {
    dispatch({
      type: SELECT_ACTIVE_SURVEY_REQUEST,
      survey
    })
    return fetch(`/api/survey/${survey.id}`)
    .then(checkStatus)
    .then(response => response.json())
    .then(response => dispatch(selectActiveSurveySuccess(response)))
    .catch(error => dispatch(selectActiveSurveyFailure(error)))
  }

}

export function selectActiveSurveyRequest() {
  return {
    type: SELECT_ACTIVE_SURVEY_REQUEST
  }
}

export function selectActiveSurveySuccess(response) {
  return {
    type: SELECT_ACTIVE_SURVEY_SUCCESS,
    response
  }
}

export function selectActiveSurveyFailure(error) {
  return {
    type: SELECT_ACTIVE_SURVEY_FAILURE,
    error
  }
}

export const TOGGLE_SURVEY_BUILDER_QUESTION_EDITABLE =
  'TOGGLE_SURVEY_BUILDER_QUESTION_EDITABLE'

export function toggleSurveyBuilderQuestionEditable(id) {
  return {
    type: TOGGLE_SURVEY_BUILDER_QUESTION_EDITABLE,
    id
  }
}

export const UPDATE_SURVEY_BUILDER_QUESTION = 'UPDATE_SURVEY_BUILDER_QUESTION'

export function updateSurveyBuilderQuestion(question) {
  return {
    type: UPDATE_SURVEY_BUILDER_QUESTION,
    question
  }
}

export const SELECT_SURVEY_QUESTION_RESPONSE = 'SELECT_SURVEY_QUESTION_RESPONSE'
export const SELECT_SURVEY_QUESTION_RESPONSE_INTENSITY =
  'SELECT_SURVEY_QUESTION_RESPONSE_INTENSITY'

export function selectSurveyQuestionResponse(questionId, answer) {
  return {
    type: SELECT_SURVEY_QUESTION_RESPONSE,
    questionId,
    answer
  }
}

export function selectSurveyQuestionResponseIntensity(questionId, intensity) {
  return {
    type: SELECT_SURVEY_QUESTION_RESPONSE_INTENSITY,
    questionId,
    intensity
  }
}

export const SUBMIT_SURVEY_ANSWERS = 'SUBMIT_SURVEY_ANSWERS'
export const SUBMIT_SURVEY_ANSWERS_REQUEST = 'SUBMIT_SURVEY_ANSWERS_REQUEST'
export const SUBMIT_SURVEY_ANSWERS_SUCCESS = 'SUBMIT_SURVEY_ANSWERS_SUCCESS'
export const SUBMIT_SURVEY_ANSWERS_FAILURE = 'SUBMIT_SURVEY_ANSWERS_FAILURE'

export function submitSurveyAnswersRequest() {
  return {
    type: SUBMIT_SURVEY_ANSWERS_REQUEST
  }
}

export function submitSurveyAnswersSuccess(response) {
  return {
    type: SUBMIT_SURVEY_ANSWERS_SUCCESS,
    response
  }
}

export function submitSurveyAnswersFailure(error) {
  return {
    type: SUBMIT_SURVEY_ANSWERS_FAILURE,
    error
  }
}

export function submitSurveyAnswers(responses) {
  return function(dispatch) {
    dispatch(submitSurveyAnswersRequest())
    return fetch('/api/survey_answer', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        responses: responses
      })
    })
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(submitSurveyAnswersSuccess(response))
      browserHistory.push(`/results/${response[0].survey_response_id}`)
    })
    .catch(error => {
      dispatch(submitSurveyAnswersFailure(error))
    })
  }
}

export const TOGGLE_WARDFINDER_WARD_DROPDOWN = 'TOGGLE_WARDFINDER_WARD_DROPDOWN'

export function toggleWardfinderWardDropdown() {
  return {
    type: TOGGLE_WARDFINDER_WARD_DROPDOWN
  }
}

export const SET_STREET_ADDRESS = 'SET_STREET_ADDRESS'
export const SET_STREET_ADDRESS_ERROR = 'SET_STREET_ADDRESS_ERROR'

export function setStreetAddress(street) {
  return {
    type: SET_STREET_ADDRESS,
    street
  }
}

export const SUBMIT_STREET_ADDRESS_REQUEST = 'SUBMIT_STREET_ADDRESS_REQUEST'
export const SUBMIT_STREET_ADDRESS_SUCCESS = 'SUBMIT_STREET_ADDRESS_SUCCESS'
export const SUBMIT_STREET_ADDRESS_FAILURE = 'SUBMIT_STREET_ADDRESS_FAILURE'

export function submitStreetAddressRequest() {
  return {
    type: SUBMIT_STREET_ADDRESS_REQUEST
  }
}

export function submitStreetAddressSuccess(response) {
  return {
    type: SUBMIT_STREET_ADDRESS_SUCCESS,
    response
  }
}

export function submitStreetAddressFailure(error) {
  return {
    type: SUBMIT_STREET_ADDRESS_FAILURE,
    error
  }
}

export function submitStreetAddress(street) {
  return function(dispatch) {
    dispatch(submitStreetAddressRequest(street))

    if (!street) {
      dispatch(setStreetAddress({
        help: 'This field shouldn\'t be blank'
      }))
      return
    }

    if (street.length < 3) {
      dispatch(setStreetAddress({
        help: 'Please enter a longer address'
      }))
      return
    }

    return fetch('/api/geography/ward', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        street: street
      })
    })
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(submitStreetAddressSuccess(response))
      dispatch(selectGeography(response.id))
    })
    .catch(error => {
      dispatch(submitStreetAddressFailure(error))
    })
  }
}

export const FETCH_GEOGRAPHY_REQUEST = 'FETCH_GEOGRAPHY_REQUEST'
export const FETCH_GEOGRAPHY_SUCCESS = 'FETCH_GEOGRAPHY_SUCCESS'
export const FETCH_GEOGRAPHY_FAILURE = 'FETCH_GEOGRAPHY_FAILURE'

export function fetchGeographyRequest() {
  return {
    type: FETCH_GEOGRAPHY_REQUEST
  }
}

export function fetchGeographySuccess(response) {
  return {
    type: FETCH_GEOGRAPHY_SUCCESS,
    response
  }
}

export function fetchGeographyFailure(error) {
  return {
    type: FETCH_GEOGRAPHY_FAILURE,
    error
  }
}

export function fetchGeography() {
  return function(dispatch) {
    dispatch(fetchGeographyRequest())
    return fetch('/api/geography')
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(fetchGeographySuccess(response))
    })
    .catch(error => {
      dispatch(fetchGeographyFailure(error))
    })
  }
}

export const SELECT_GEOGRAPHY = 'SELECT_GEOGRAPHY'

export function selectGeography(geographyId) {
  return function(dispatch) {
    dispatch({
      type: SELECT_GEOGRAPHY,
      selection: geographyId
    })
    // Todo: surveyID needs to come from somewhere.
    dispatch(fetchSurveyResponseId(1, geographyId))
  }
}

export const FETCH_SURVEY_RESPONSE_ID_REQUEST = 'FETCH_SURVEY_RESPONSE_ID_REQUEST'
export const FETCH_SURVEY_RESPONSE_ID_SUCCESS = 'FETCH_SURVEY_RESPONSE_ID_SUCCESS'
export const FETCH_SURVEY_RESPONSE_ID_FAILURE = 'FETCH_SURVEY_RESPONSE_ID_FAILURE'

export function fetchSurveyResponseIdRequest() {
  return {
    type: FETCH_SURVEY_RESPONSE_ID_REQUEST
  }
}

export function fetchSurveyResponseIdSuccess(response) {
  return {
    type: FETCH_SURVEY_RESPONSE_ID_SUCCESS,
    response
  }
}

export function fetchSurveyResponseIdFailure(error) {
  return {
    type: FETCH_SURVEY_RESPONSE_ID_FAILURE,
    error
  }
}

export function fetchSurveyResponseId(surveyId, geographyId) {
  return function(dispatch) {
    dispatch(fetchSurveyResponseIdRequest())
    return fetch('/api/survey_response', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        surveyId: surveyId,
        geographyId: geographyId
      })
    })
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(fetchSurveyResponseIdSuccess(response))
      browserHistory.push('/survey')
    })
    .catch(error => {
      dispatch(fetchSurveyResponseIdFailure(error))
    })
  }
}

export const FETCH_SURVEY_CANDIDATE_MATCHES_REQUEST = 'FETCH_SURVEY_CANDIDATE_MATCHES_REQUEST'
export const FETCH_SURVEY_CANDIDATE_MATCHES_SUCCESS = 'FETCH_SURVEY_CANDIDATE_MATCHES_SUCCESS'
export const FETCH_SURVEY_CANDIDATE_MATCHES_FAILURE = 'FETCH_SURVEY_CANDIDATE_MATCHES_FAILURE'

export function fetchSurveyCandidateMatchesRequest() {
  return {
    type: FETCH_SURVEY_CANDIDATE_MATCHES_REQUEST
  }
}

export function fetchSurveyCandidateMatchesSuccess(response) {
  return {
    type: FETCH_SURVEY_CANDIDATE_MATCHES_SUCCESS,
    response
  }
}

export function fetchSurveyCandidateMatchesFailure(error) {
  return {
    type: FETCH_SURVEY_CANDIDATE_MATCHES_FAILURE,
    error
  }
}

export function fetchSurveyCandidateMatches(id) {
  return function(dispatch) {
    dispatch(fetchSurveyCandidateMatchesRequest())
    fetch(`/api/candidate_match/${id}`)
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(fetchSurveyCandidateMatchesSuccess(response))
    })
    .catch(error => {
      dispatch(fetchSurveyCandidateMatchesFailure(error))
    })
  }
}

export const HIDE_ELECTION_DAY_REMINDER_PROMPT =
  'HIDE_ELECTION_DAY_REMINDER_PROMPT'
export const SHOW_ELECTION_DAY_REMINDER_MODAL =
  'SHOW_ELECTION_DAY_REMINDER_MODAL'
export const HIDE_ELECTION_DAY_REMINDER_MODAL =
  'HIDE_ELECTION_DAY_REMINDER_MODAL'

export const SET_ELECTION_DAY_REMINDER_EMAIL_ADDRESS =
  'SET_ELECTION_DAY_REMINDER_EMAIL_ADDRESS'
export const SET_ELECTION_DAY_REMINDER_TELEPHONE_NUMBER =
  'SET_ELECTION_DAY_REMINDER_TELEPHONE_NUMBER'

export const SUBMIT_ELECTION_DAY_REMINDER_REQUEST = 'SUBMIT_ELECTION_DAY_REMINDER_REQUEST'
export const SUBMIT_ELECTION_DAY_REMINDER_SUCCESS = 'SUBMIT_ELECTION_DAY_REMINDER_SUCCESS'
export const SUBMIT_ELECTION_DAY_REMINDER_FAILURE = 'SUBMIT_ELECTION_DAY_REMINDER_FAILURE'

export function hideElectionDayReminderPrompt () {
  return {
    type: HIDE_ELECTION_DAY_REMINDER_PROMPT
  }
}

export function showElectionDayReminderModal() {
  return {
    type: SHOW_ELECTION_DAY_REMINDER_MODAL
  }
}

export function hideElectionDayReminderModal() {
  return {
    type: HIDE_ELECTION_DAY_REMINDER_MODAL
  }
}

export function setElectionDayReminderEmailAddress(value, error = '') {
  return {
    type: SET_ELECTION_DAY_REMINDER_EMAIL_ADDRESS,
    value,
    error
  }
}

export function setElectionDayReminderTelephoneNumber(value, error = '') {
  return {
    type: SET_ELECTION_DAY_REMINDER_TELEPHONE_NUMBER,
    value,
    error
  }
}

export function submitElectionDayReminderRequest() {
  return {
    type: SUBMIT_ELECTION_DAY_REMINDER_REQUEST
  }
}

export function submitElectionDayReminderSuccess(response) {
  return {
    type: SUBMIT_ELECTION_DAY_REMINDER_SUCCESS,
    response
  }
}

export function submitElectionDayReminderFailure(error) {
  return {
    type: SUBMIT_ELECTION_DAY_REMINDER_FAILURE,
    error
  }
}

export function validateElectionDayReminderRequest(email, telephone) {

  const errors = {}

  function validateEmailAddress() {
    if (!validator.isEmail(email.value)) {
      errors.email = {
        value: email.value,
        error: 'Please enter a valid email address (like person@provider.com).'
      }
    }
  }

  function validateTelephoneNumber() {
    if (!validator.isMobilePhone(telephone.value, 'en-US')) {
      errors.telephone = {
        value: telephone.value,
        error: 'Please enter a 10-digit phone number (like 555-555-5555).'
      }
    }
  }

  // If an email address was provided:
  if (email.value) {
    validateEmailAddress()
  }

  // If a telephone number was provided:
  if (telephone.value) {
    validateTelephoneNumber()
  }

  // Nothing is defined.
  else if (!email.value && !telephone.value) {
    errors.alert = {
      message: 'At least one piece of contact information should be provided',
      severity: 'warning'
    }
    return errors
  }

  // If errors were found, return the error object.  If not, return undefined.
  const numberOfErrors = Object.keys(errors).length
  if (numberOfErrors) {

    if (numberOfErrors === 1) {
      errors.alert = {
        message: 'Please correct the error below',
        severity: 'warning'
      }
    }
    else if (numberOfErrors > 1) {
      errors.alert = {
        message: 'Please correct the errors below',
        severity: 'warning'
      }
    }

    return errors

  }

}

export function submitElectionDayReminder(email, telephone, surveyId) {
  return function(dispatch) {

    dispatch(submitElectionDayReminderRequest())
    const errors = validateElectionDayReminderRequest(email, telephone)
    if (errors) {
      return dispatch(submitElectionDayReminderFailure(errors))
    }
    fetch(`/api/survey_response/contact_info/${surveyId}`, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        userEmail: email.value,
        userPhone: telephone.value
      })
    })
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(submitElectionDayReminderSuccess(response))
    })
    .catch(() => {
      dispatch(submitElectionDayReminderFailure({
        alert: {
          message: 'There was an error saving your contact information',
          severity: 'danger'
        }
      }))
    })

  }
}
<commit_msg>Simplify logic for election reminder alert messages.<commit_after>import fetch from 'isomorphic-fetch'
import checkStatus from './../utils/checkStatus'
import { browserHistory } from 'react-router'
import validator from 'validator'

export const FETCH_ACTIVE_SURVEYS_REQUEST = 'FETCH_ACTIVE_SURVEYS_REQUEST'
export const FETCH_ACTIVE_SURVEYS_SUCCESS = 'FETCH_ACTIVE_SURVEYS_SUCCESS'
export const FETCH_ACTIVE_SURVEYS_FAILURE = 'FETCH_ACTIVE_SURVEYS_FAILURE'

export function fetchActiveSurveysRequest() {
  return {
    type: FETCH_ACTIVE_SURVEYS_REQUEST
  }
}

export function fetchActiveSurveysSuccess(response) {
  return {
    type: FETCH_ACTIVE_SURVEYS_SUCCESS,
    response
  }
}

export function fetchActiveSurveysFailure(error) {
  return {
    type: FETCH_ACTIVE_SURVEYS_FAILURE,
    error
  }
}

export function fetchActiveSurveys() {
  return function(dispatch) {
    dispatch(fetchActiveSurveysRequest())
    return fetch('/api/survey')
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(fetchActiveSurveysSuccess(response))
      // If only one survey comes back, go ahead and make the follow up API
      // to set it as active.
      if (response.length === 1) {
        dispatch(selectActiveSurvey(response[0]))
      }
    })
    .catch(error => dispatch(fetchActiveSurveysFailure(error)))
  }
}

export const SELECT_ACTIVE_SURVEY = 'SELECT_ACTIVE_SURVEY'
export const SELECT_ACTIVE_SURVEY_REQUEST = 'SELECT_ACTIVE_SURVEY_REQUEST'
export const SELECT_ACTIVE_SURVEY_SUCCESS = 'SELECT_ACTIVE_SURVEY_SUCCESS'
export const SELECT_ACTIVE_SURVEY_FAILURE = 'SELECT_ACTIVE_SURVEY_FAILURE'

export function selectActiveSurvey(survey) {
  return function(dispatch) {
    dispatch({
      type: SELECT_ACTIVE_SURVEY_REQUEST,
      survey
    })
    return fetch(`/api/survey/${survey.id}`)
    .then(checkStatus)
    .then(response => response.json())
    .then(response => dispatch(selectActiveSurveySuccess(response)))
    .catch(error => dispatch(selectActiveSurveyFailure(error)))
  }

}

export function selectActiveSurveyRequest() {
  return {
    type: SELECT_ACTIVE_SURVEY_REQUEST
  }
}

export function selectActiveSurveySuccess(response) {
  return {
    type: SELECT_ACTIVE_SURVEY_SUCCESS,
    response
  }
}

export function selectActiveSurveyFailure(error) {
  return {
    type: SELECT_ACTIVE_SURVEY_FAILURE,
    error
  }
}

export const TOGGLE_SURVEY_BUILDER_QUESTION_EDITABLE =
  'TOGGLE_SURVEY_BUILDER_QUESTION_EDITABLE'

export function toggleSurveyBuilderQuestionEditable(id) {
  return {
    type: TOGGLE_SURVEY_BUILDER_QUESTION_EDITABLE,
    id
  }
}

export const UPDATE_SURVEY_BUILDER_QUESTION = 'UPDATE_SURVEY_BUILDER_QUESTION'

export function updateSurveyBuilderQuestion(question) {
  return {
    type: UPDATE_SURVEY_BUILDER_QUESTION,
    question
  }
}

export const SELECT_SURVEY_QUESTION_RESPONSE = 'SELECT_SURVEY_QUESTION_RESPONSE'
export const SELECT_SURVEY_QUESTION_RESPONSE_INTENSITY =
  'SELECT_SURVEY_QUESTION_RESPONSE_INTENSITY'

export function selectSurveyQuestionResponse(questionId, answer) {
  return {
    type: SELECT_SURVEY_QUESTION_RESPONSE,
    questionId,
    answer
  }
}

export function selectSurveyQuestionResponseIntensity(questionId, intensity) {
  return {
    type: SELECT_SURVEY_QUESTION_RESPONSE_INTENSITY,
    questionId,
    intensity
  }
}

export const SUBMIT_SURVEY_ANSWERS = 'SUBMIT_SURVEY_ANSWERS'
export const SUBMIT_SURVEY_ANSWERS_REQUEST = 'SUBMIT_SURVEY_ANSWERS_REQUEST'
export const SUBMIT_SURVEY_ANSWERS_SUCCESS = 'SUBMIT_SURVEY_ANSWERS_SUCCESS'
export const SUBMIT_SURVEY_ANSWERS_FAILURE = 'SUBMIT_SURVEY_ANSWERS_FAILURE'

export function submitSurveyAnswersRequest() {
  return {
    type: SUBMIT_SURVEY_ANSWERS_REQUEST
  }
}

export function submitSurveyAnswersSuccess(response) {
  return {
    type: SUBMIT_SURVEY_ANSWERS_SUCCESS,
    response
  }
}

export function submitSurveyAnswersFailure(error) {
  return {
    type: SUBMIT_SURVEY_ANSWERS_FAILURE,
    error
  }
}

export function submitSurveyAnswers(responses) {
  return function(dispatch) {
    dispatch(submitSurveyAnswersRequest())
    return fetch('/api/survey_answer', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        responses: responses
      })
    })
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(submitSurveyAnswersSuccess(response))
      browserHistory.push(`/results/${response[0].survey_response_id}`)
    })
    .catch(error => {
      dispatch(submitSurveyAnswersFailure(error))
    })
  }
}

export const TOGGLE_WARDFINDER_WARD_DROPDOWN = 'TOGGLE_WARDFINDER_WARD_DROPDOWN'

export function toggleWardfinderWardDropdown() {
  return {
    type: TOGGLE_WARDFINDER_WARD_DROPDOWN
  }
}

export const SET_STREET_ADDRESS = 'SET_STREET_ADDRESS'
export const SET_STREET_ADDRESS_ERROR = 'SET_STREET_ADDRESS_ERROR'

export function setStreetAddress(street) {
  return {
    type: SET_STREET_ADDRESS,
    street
  }
}

export const SUBMIT_STREET_ADDRESS_REQUEST = 'SUBMIT_STREET_ADDRESS_REQUEST'
export const SUBMIT_STREET_ADDRESS_SUCCESS = 'SUBMIT_STREET_ADDRESS_SUCCESS'
export const SUBMIT_STREET_ADDRESS_FAILURE = 'SUBMIT_STREET_ADDRESS_FAILURE'

export function submitStreetAddressRequest() {
  return {
    type: SUBMIT_STREET_ADDRESS_REQUEST
  }
}

export function submitStreetAddressSuccess(response) {
  return {
    type: SUBMIT_STREET_ADDRESS_SUCCESS,
    response
  }
}

export function submitStreetAddressFailure(error) {
  return {
    type: SUBMIT_STREET_ADDRESS_FAILURE,
    error
  }
}

export function submitStreetAddress(street) {
  return function(dispatch) {
    dispatch(submitStreetAddressRequest(street))

    if (!street) {
      dispatch(setStreetAddress({
        help: 'This field shouldn\'t be blank'
      }))
      return
    }

    if (street.length < 3) {
      dispatch(setStreetAddress({
        help: 'Please enter a longer address'
      }))
      return
    }

    return fetch('/api/geography/ward', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        street: street
      })
    })
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(submitStreetAddressSuccess(response))
      dispatch(selectGeography(response.id))
    })
    .catch(error => {
      dispatch(submitStreetAddressFailure(error))
    })
  }
}

export const FETCH_GEOGRAPHY_REQUEST = 'FETCH_GEOGRAPHY_REQUEST'
export const FETCH_GEOGRAPHY_SUCCESS = 'FETCH_GEOGRAPHY_SUCCESS'
export const FETCH_GEOGRAPHY_FAILURE = 'FETCH_GEOGRAPHY_FAILURE'

export function fetchGeographyRequest() {
  return {
    type: FETCH_GEOGRAPHY_REQUEST
  }
}

export function fetchGeographySuccess(response) {
  return {
    type: FETCH_GEOGRAPHY_SUCCESS,
    response
  }
}

export function fetchGeographyFailure(error) {
  return {
    type: FETCH_GEOGRAPHY_FAILURE,
    error
  }
}

export function fetchGeography() {
  return function(dispatch) {
    dispatch(fetchGeographyRequest())
    return fetch('/api/geography')
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(fetchGeographySuccess(response))
    })
    .catch(error => {
      dispatch(fetchGeographyFailure(error))
    })
  }
}

export const SELECT_GEOGRAPHY = 'SELECT_GEOGRAPHY'

export function selectGeography(geographyId) {
  return function(dispatch) {
    dispatch({
      type: SELECT_GEOGRAPHY,
      selection: geographyId
    })
    // Todo: surveyID needs to come from somewhere.
    dispatch(fetchSurveyResponseId(1, geographyId))
  }
}

export const FETCH_SURVEY_RESPONSE_ID_REQUEST = 'FETCH_SURVEY_RESPONSE_ID_REQUEST'
export const FETCH_SURVEY_RESPONSE_ID_SUCCESS = 'FETCH_SURVEY_RESPONSE_ID_SUCCESS'
export const FETCH_SURVEY_RESPONSE_ID_FAILURE = 'FETCH_SURVEY_RESPONSE_ID_FAILURE'

export function fetchSurveyResponseIdRequest() {
  return {
    type: FETCH_SURVEY_RESPONSE_ID_REQUEST
  }
}

export function fetchSurveyResponseIdSuccess(response) {
  return {
    type: FETCH_SURVEY_RESPONSE_ID_SUCCESS,
    response
  }
}

export function fetchSurveyResponseIdFailure(error) {
  return {
    type: FETCH_SURVEY_RESPONSE_ID_FAILURE,
    error
  }
}

export function fetchSurveyResponseId(surveyId, geographyId) {
  return function(dispatch) {
    dispatch(fetchSurveyResponseIdRequest())
    return fetch('/api/survey_response', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        surveyId: surveyId,
        geographyId: geographyId
      })
    })
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(fetchSurveyResponseIdSuccess(response))
      browserHistory.push('/survey')
    })
    .catch(error => {
      dispatch(fetchSurveyResponseIdFailure(error))
    })
  }
}

export const FETCH_SURVEY_CANDIDATE_MATCHES_REQUEST = 'FETCH_SURVEY_CANDIDATE_MATCHES_REQUEST'
export const FETCH_SURVEY_CANDIDATE_MATCHES_SUCCESS = 'FETCH_SURVEY_CANDIDATE_MATCHES_SUCCESS'
export const FETCH_SURVEY_CANDIDATE_MATCHES_FAILURE = 'FETCH_SURVEY_CANDIDATE_MATCHES_FAILURE'

export function fetchSurveyCandidateMatchesRequest() {
  return {
    type: FETCH_SURVEY_CANDIDATE_MATCHES_REQUEST
  }
}

export function fetchSurveyCandidateMatchesSuccess(response) {
  return {
    type: FETCH_SURVEY_CANDIDATE_MATCHES_SUCCESS,
    response
  }
}

export function fetchSurveyCandidateMatchesFailure(error) {
  return {
    type: FETCH_SURVEY_CANDIDATE_MATCHES_FAILURE,
    error
  }
}

export function fetchSurveyCandidateMatches(id) {
  return function(dispatch) {
    dispatch(fetchSurveyCandidateMatchesRequest())
    fetch(`/api/candidate_match/${id}`)
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(fetchSurveyCandidateMatchesSuccess(response))
    })
    .catch(error => {
      dispatch(fetchSurveyCandidateMatchesFailure(error))
    })
  }
}

export const HIDE_ELECTION_DAY_REMINDER_PROMPT =
  'HIDE_ELECTION_DAY_REMINDER_PROMPT'
export const SHOW_ELECTION_DAY_REMINDER_MODAL =
  'SHOW_ELECTION_DAY_REMINDER_MODAL'
export const HIDE_ELECTION_DAY_REMINDER_MODAL =
  'HIDE_ELECTION_DAY_REMINDER_MODAL'

export const SET_ELECTION_DAY_REMINDER_EMAIL_ADDRESS =
  'SET_ELECTION_DAY_REMINDER_EMAIL_ADDRESS'
export const SET_ELECTION_DAY_REMINDER_TELEPHONE_NUMBER =
  'SET_ELECTION_DAY_REMINDER_TELEPHONE_NUMBER'

export const SUBMIT_ELECTION_DAY_REMINDER_REQUEST = 'SUBMIT_ELECTION_DAY_REMINDER_REQUEST'
export const SUBMIT_ELECTION_DAY_REMINDER_SUCCESS = 'SUBMIT_ELECTION_DAY_REMINDER_SUCCESS'
export const SUBMIT_ELECTION_DAY_REMINDER_FAILURE = 'SUBMIT_ELECTION_DAY_REMINDER_FAILURE'

export function hideElectionDayReminderPrompt () {
  return {
    type: HIDE_ELECTION_DAY_REMINDER_PROMPT
  }
}

export function showElectionDayReminderModal() {
  return {
    type: SHOW_ELECTION_DAY_REMINDER_MODAL
  }
}

export function hideElectionDayReminderModal() {
  return {
    type: HIDE_ELECTION_DAY_REMINDER_MODAL
  }
}

export function setElectionDayReminderEmailAddress(value, error = '') {
  return {
    type: SET_ELECTION_DAY_REMINDER_EMAIL_ADDRESS,
    value,
    error
  }
}

export function setElectionDayReminderTelephoneNumber(value, error = '') {
  return {
    type: SET_ELECTION_DAY_REMINDER_TELEPHONE_NUMBER,
    value,
    error
  }
}

export function submitElectionDayReminderRequest() {
  return {
    type: SUBMIT_ELECTION_DAY_REMINDER_REQUEST
  }
}

export function submitElectionDayReminderSuccess(response) {
  return {
    type: SUBMIT_ELECTION_DAY_REMINDER_SUCCESS,
    response
  }
}

export function submitElectionDayReminderFailure(error) {
  return {
    type: SUBMIT_ELECTION_DAY_REMINDER_FAILURE,
    error
  }
}

export function validateElectionDayReminderRequest(email, telephone) {

  const errors = {}

  function validateEmailAddress() {
    if (!validator.isEmail(email.value)) {
      errors.email = {
        value: email.value,
        error: 'Please enter a valid email address (like person@provider.com).'
      }
    }
  }

  function validateTelephoneNumber() {
    if (!validator.isMobilePhone(telephone.value, 'en-US')) {
      errors.telephone = {
        value: telephone.value,
        error: 'Please enter a 10-digit phone number (like 555-555-5555).'
      }
    }
  }

  // If an email address was provided:
  if (email.value) {
    validateEmailAddress()
  }

  // If a telephone number was provided:
  if (telephone.value) {
    validateTelephoneNumber()
  }

  // Nothing is defined.
  if (!email.value && !telephone.value) {
    errors.alert = {
      message: 'At least one piece of contact information should be provided',
      severity: 'warning'
    }
  }

  // If errors were found, return the error object.  If not, return undefined.
  const numberOfErrors = Object.keys(errors).length

  if (numberOfErrors > 0) {

    // If a specific alert hasn't been defined already...
    if (!errors.alert) {
      errors.alert = {
        message: `Please correct the ${numberOfErrors > 1 ? 'errors' : 'error'} below`,
        severity: 'warning'
      }
    }

    return errors

  }

}

export function submitElectionDayReminder(email, telephone, surveyId) {
  return function(dispatch) {

    dispatch(submitElectionDayReminderRequest())
    const errors = validateElectionDayReminderRequest(email, telephone)
    if (errors) {
      return dispatch(submitElectionDayReminderFailure(errors))
    }
    fetch(`/api/survey_response/contact_info/${surveyId}`, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        userEmail: email.value,
        userPhone: telephone.value
      })
    })
    .then(checkStatus)
    .then(response => response.json())
    .then(response => {
      dispatch(submitElectionDayReminderSuccess(response))
    })
    .catch(() => {
      dispatch(submitElectionDayReminderFailure({
        alert: {
          message: 'There was an error saving your contact information',
          severity: 'danger'
        }
      }))
    })

  }
}
<|endoftext|>


<commit_before>const utils = require("./utils");
const { compareTwoStrings } = require("string-similarity");

class SpamFilter {
    constructor() {
        /**
         * @type {string[]}
         */
        this.strings = [];
    }

    async check(tweetObj) {
        const compareString = utils.cleanText(tweetObj).toLowerCase().replace(/[^a-zA-Z0-9 ]/g, "");

        for (let string of this.strings) {
            console.log(string, "|", compareString, "|", compareTwoStrings(string, compareString).toFixed(2));
            if (compareTwoStrings(string, compareString) > 0.95) {
                throw new Error("Spamfilter is blocking.");
            }
        }

        this.strings.push(compareString);
        setTimeout(() => {
            this.strings.splice(this.strings.indexOf(compareString), 1);
        }, 1000 * 3600);

        return;
    }
}

module.exports = SpamFilter;
<commit_msg>Update spam_filter.js<commit_after>const utils = require("./utils");
const { compareTwoStrings } = require("string-similarity");

class SpamFilter {
    constructor() {
        /**
         * @type {string[]}
         */
        this.strings = [];
    }

    async check(tweetObj) {
        const compareString = utils.cleanText(tweetObj).toLowerCase().replace(/[^a-zA-Z0-9 ]/g, "");

        for (let string of this.strings) {
            console.log(string, "|", compareString, "|", compareTwoStrings(string, compareString).toFixed(2));
            if (compareTwoStrings(string, compareString) > 0.9) {
                throw new Error("Spamfilter is blocking.");
            }
        }

        this.strings.push(compareString);
        setTimeout(() => {
            this.strings.splice(this.strings.indexOf(compareString), 1);
        }, 1000 * 3600);

        return;
    }
}

module.exports = SpamFilter;
<|endoftext|>


<commit_before><commit_msg>Encoded entry links appropriately<commit_after><|endoftext|>


<commit_before>var expect = require('chai').expect;
var validate = require('../src/validate');

describe('screener-storybook/src/validate', function() {
  describe('validate.storybookConfig', function() {
    it('should throw error when no value passed in', function() {
      return validate.storybookConfig()
        .catch(function(err) {
          expect(err.message).to.equal('"value" is required');
        });
    });

    it('should throw error when no apiKey', function() {
      return validate.storybookConfig({})
        .catch(function(err) {
          expect(err.message).to.equal('child "apiKey" fails because ["apiKey" is required]');
        });
    });

    it('should throw error when no projectRepo', function() {
      return validate.storybookConfig({apiKey: 'key'})
        .catch(function(err) {
          expect(err.message).to.equal('child "projectRepo" fails because ["projectRepo" is required]');
        });
    });

    it('should throw error when no storybookConfigDir', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo'})
        .catch(function(err) {
          expect(err.message).to.equal('child "storybookConfigDir" fails because ["storybookConfigDir" is required]');
        });
    });

    it('should throw error when no storybookPort', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook'})
        .catch(function(err) {
          expect(err.message).to.equal('child "storybookPort" fails because ["storybookPort" is required]');
        });
    });

    it('should throw error when no storybook', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006})
        .catch(function(err) {
          expect(err.message).to.equal('child "storybook" fails because ["storybook" is required]');
        });
    });

    it('should allow storybook with no stories', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: []})
        .catch(function() {
          throw new Error('Should not be here');
        });
    });

    it('should throw error when storybook item is invalid', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [{kind: 'Temp'}]})
        .catch(function(err) {
          expect(err.message).to.equal('child "storybook" fails because ["storybook" at position 0 fails because [child "stories" fails because ["stories" is required]]]');
        });
    });

    it('should allow adding optional fields', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookStaticDir: './public', storybookPort: 6006, storybook: [], build: 'build', branch: 'branch', resolution: '1280x1024', cssAnimations: true, ignore: 'ignore', includeRules: [], excludeRules: [], initialBaselineBranch: 'master', diffOptions: {}})
        .catch(function() {
          throw new Error('Should not be here');
        });
    });

    it('should allow include/exclude rules that are strings', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], includeRules: ['string'], excludeRules: ['string']})
        .catch(function() {
          throw new Error('Should not be here');
        });
    });

    it('should allow include/exclude rules that are regex expressions', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], includeRules: [/^string$/], excludeRules: [/^string$/]})
        .catch(function() {
          throw new Error('Should not be here');
        });
    });

    it('should throw error when include/exclude rules are not in array', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], includeRules: 'string', excludeRules: 'string'})
        .catch(function(err) {
          expect(err.message).to.equal('child "includeRules" fails because ["includeRules" must be an array]');
        });
    });

    it('should throw error when resolution in incorrect format', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], resolution: 'resolution'})
        .catch(function(err) {
          expect(err.message).to.equal('child "resolution" fails because ["resolution" with value "resolution" fails to match the resolution pattern, "resolution" must be an object, "resolution" must be an object]');
        });
    });

    it('should throw error when field is unknown', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], someKey: 'key'})
        .catch(function(err) {
          expect(err.message).to.equal('"someKey" is not allowed');
        });
    });

    describe('validate.browsers', function() {
      it('should error when browsers is empty', function() {
        return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], browsers: []})
          .catch(function(err) {
            expect(err.message).to.equal('child "browsers" fails because ["browsers" must contain at least 1 items]');
          });
      });

      it('should error when browsers is set without sauce', function() {
        return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], browsers: [{ browserName: 'chrome' }]})
          .catch(function(err) {
            expect(err.message).to.equal('"browsers" missing required peer "sauce"');
          });
      });

      it('should allow browsers with sauce config', function() {
        return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], browsers: [{ browserName: 'firefox', version: '53.0' }], sauce: { username: 'user', accessKey: 'key' }})
          .catch(function() {
            throw new Error('Should not be here');
          });
      });
    });

  });
});
<commit_msg>update unit tests<commit_after>var expect = require('chai').expect;
var validate = require('../src/validate');

describe('screener-storybook/src/validate', function() {
  describe('validate.storybookConfig', function() {
    it('should throw error when no value passed in', function() {
      return validate.storybookConfig()
        .catch(function(err) {
          expect(err.message).to.equal('"value" is required');
        });
    });

    it('should throw error when no apiKey', function() {
      return validate.storybookConfig({})
        .catch(function(err) {
          expect(err.message).to.equal('child "apiKey" fails because ["apiKey" is required]');
        });
    });

    it('should throw error when no projectRepo', function() {
      return validate.storybookConfig({apiKey: 'key'})
        .catch(function(err) {
          expect(err.message).to.equal('child "projectRepo" fails because ["projectRepo" is required]');
        });
    });

    it('should throw error when no storybookConfigDir', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo'})
        .catch(function(err) {
          expect(err.message).to.equal('child "storybookConfigDir" fails because ["storybookConfigDir" is required]');
        });
    });

    it('should throw error when no storybookPort', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook'})
        .catch(function(err) {
          expect(err.message).to.equal('child "storybookPort" fails because ["storybookPort" is required]');
        });
    });

    it('should throw error when no storybook', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006})
        .catch(function(err) {
          expect(err.message).to.equal('child "storybook" fails because ["storybook" is required]');
        });
    });

    it('should allow storybook with no stories', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: []})
        .catch(function() {
          throw new Error('Should not be here');
        });
    });

    it('should throw error when storybook item is invalid', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [{kind: 'Temp'}]})
        .catch(function(err) {
          expect(err.message).to.equal('child "storybook" fails because ["storybook" at position 0 fails because [child "stories" fails because ["stories" is required]]]');
        });
    });

    it('should allow adding optional fields', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookStaticDir: './public', storybookPort: 6006, storybook: [], build: 'build', branch: 'branch', resolution: '1280x1024', cssAnimations: true, ignore: 'ignore', includeRules: [], excludeRules: [], initialBaselineBranch: 'master', diffOptions: {}})
        .catch(function() {
          throw new Error('Should not be here');
        });
    });

    it('should allow include/exclude rules that are strings', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], includeRules: ['string'], excludeRules: ['string']})
        .catch(function() {
          throw new Error('Should not be here');
        });
    });

    it('should allow include/exclude rules that are regex expressions', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], includeRules: [/^string$/], excludeRules: [/^string$/]})
        .catch(function() {
          throw new Error('Should not be here');
        });
    });

    it('should throw error when include/exclude rules are not in array', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], includeRules: 'string', excludeRules: 'string'})
        .catch(function(err) {
          expect(err.message).to.equal('child "includeRules" fails because ["includeRules" must be an array]');
        });
    });

    it('should throw error when resolution in incorrect format', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], resolution: 'resolution'})
        .catch(function(err) {
          expect(err.message).to.equal('child "resolution" fails because ["resolution" with value "resolution" fails to match the resolution pattern, "resolution" must be an object, "resolution" must be an object]');
        });
    });

    it('should throw error when field is unknown', function() {
      return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], someKey: 'key'})
        .catch(function(err) {
          expect(err.message).to.equal('"someKey" is not allowed');
        });
    });

    describe('validate.failureExitCode', function() {
      it('should allow setting to 0', function() {
        return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], failureExitCode: 0})
          .catch(function() {
            throw new Error('Should not be here');
          });
      });

      it('should error when setting above 255', function() {
        return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], failureExitCode: 256})
          .catch(function(err) {
            expect(err.message).to.equal('child "failureExitCode" fails because ["failureExitCode" must be less than or equal to 255]');
          });
      });
    });

    describe('validate.browsers', function() {
      it('should error when browsers is empty', function() {
        return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], browsers: []})
          .catch(function(err) {
            expect(err.message).to.equal('child "browsers" fails because ["browsers" must contain at least 1 items]');
          });
      });

      it('should error when browsers is set without sauce', function() {
        return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], browsers: [{ browserName: 'chrome' }]})
          .catch(function(err) {
            expect(err.message).to.equal('"browsers" missing required peer "sauce"');
          });
      });

      it('should allow browsers with sauce config', function() {
        return validate.storybookConfig({apiKey: 'key', projectRepo: 'repo', storybookConfigDir: '.storybook', storybookPort: 6006, storybook: [], browsers: [{ browserName: 'firefox', version: '53.0' }], sauce: { username: 'user', accessKey: 'key' }})
          .catch(function() {
            throw new Error('Should not be here');
          });
      });
    });

  });
});
<|endoftext|>


<commit_before>define([
  'backbone',
  'resources/layouts',
], function (Backbone, Resources) {
  var Layout = Backbone.Model.extend({
    defaults: {
      width: 0,
      height: 0,
      map: [],
    }
  });

  var Square = Backbone.Model.extend({
    defaults: {
      x: undefined,
      y: undefined,
      player: undefined,
      bonus: undefined,
      allows: ['all'],
      type: undefined,
    },

    isBlocker: function () { return !this.get('allows').length },
    allowsAll: function () { return 'all' === this.get('allows').toString() },
    allows: function (direction) {
      if ( this.allowsAll() ) return true;
      return _(this.get('allows')).contains(direction);
    },

    hasPlayer: function (player) {
      if ( !player ) return this.has('player');
      return this.get('player') === player.get('id');
    },

    pos: function () { return { x: this.get('x'), y: this.get('y') } },

    rotate: function (direction) {
      if ( this.isBlocker() ) return;
      if ( this.allowsAll() ) return;
      if ( 'right' === direction ) return this.rotateClockwise();
      if ( 'left'  === direction ) return this.rotateAntiClockwise();
    },

    rotateClockwise: function () {
      var newAllows = [];
      if ( this.allows('left') ) newAllows.push('up');
      if ( this.allows('up') ) newAllows.push('right');
      if ( this.allows('right') ) newAllows.push('down');
      if ( this.allows('down') ) newAllows.push('left');
      this.set('allows', newAllows);
    },

    rotateAntiClockwise: function () {
      var newAllows = [];
      if ( this.allows('right') ) newAllows.push('up');
      if ( this.allows('down') ) newAllows.push('right');
      if ( this.allows('left') ) newAllows.push('down');
      if ( this.allows('up') ) newAllows.push('left');
      this.set('allows', newAllows);
    },

    toJSON: function () {
      var json = _.clone(this.attributes);
      json.allows = this.get('allows').join('-')
      json.cls = {};
      json.cls.selected = this.get('selected') === true ? 'selected' : '';
      return json;
    },
  });

  return Backbone.Collection.extend({
    model: Square,
    layout: undefined,

    loadLayout: function (layoutName) {
      this.layout = new Layout(Resources[layoutName]);
      this.width = this.layout.get('itemsPerRow');
      this.height = this.layout.get('map').length / this.layout.get('itemsPerRow');

      this.reset();

      var map = _.clone(this.layout.get('map'));
      for ( var y = 0; y < this.height; y++ ) {
        for ( var x = 0; x < this.width; x++ ) {
          this.add(_.extend(map.shift(), {x: x, y: y}));
        }
      }
    },

    rows: function () {
      var rows = [];
      for ( var y = 0; y < this.height; y++ ) rows.push(this.getRow(y));
      return rows;
    },

    cols: function () {
      var cols = [];
      for ( var x = 0; x < this.width; x++ ) cols.push(this.getCol(x));
      return cols;
    },

    getWidth: function () { return this.width - 1 },
    getHeight: function () { return this.height - 1 },

    getSquare: function (x, y) {
      var square = this.find(function (item) { return item.get('x') === x && item.get('y') === y });
      if ( !square ) throw "Could not find square [" + x + "," + y + "]";
      return square;
    },

    getRow: function (y) {
      return this.filter(function (item) { return item.get('y') === y });
    },

    getCol: function (x) {
      return this.filter(function (item) { return item.get('x') === x });
    },

    hasPlayer: function (x, y) {
      return this.getSquare(x, y).hasPlayer();
    },

    isBlocker: function (x, y) {
      return this.getSquare(x, y).isBlocker();
    },

    getPlayerSquare: function (player) {
      return this.find(function (item) {
        return item.hasPlayer(player);
      });
    },

    select: function (index) {
      this.invoke('set', 'selected', false);
      this.at(index).set('selected', true);
    },

    getSelected: function () { return this.find(function (item) { return item.get('selected') }) },

    allows: function (options) { // x, y, direction
      if ( !options ) options = {};
      if ( options.square ) {
        options.x = square.get('x');
        options.y = square.get('y');
      }

      if ( this.allowsAll() ) return true;
      return _(this.get('allows')).contains(direction);
    },

    toJSON: function () { return this.map(function (item) { return item.toJSON() }) },

  })
});
<commit_msg>added push(direction, value) and sorting for getRow(y) and getCol(x) for map collection<commit_after>define([
  'backbone',
  'resources/layouts',
], function (Backbone, Resources) {
  var Layout = Backbone.Model.extend({
    defaults: {
      width: 0,
      height: 0,
      map: [],
    }
  });

  var Square = Backbone.Model.extend({
    defaults: {
      x: undefined,
      y: undefined,
      player: undefined,
      bonus: undefined,
      allows: ['all'],
      type: undefined,
    },

    isBlocker: function () { return !this.get('allows').length },
    allowsAll: function () { return 'all' === this.get('allows').toString() },
    allows: function (direction) {
      if ( this.allowsAll() ) return true;
      return _(this.get('allows')).contains(direction);
    },

    hasPlayer: function (player) {
      if ( !player ) return this.has('player');
      return this.get('player') === player.get('id');
    },

    pos: function () { return { x: this.get('x'), y: this.get('y') } },

    rotate: function (direction) {
      if ( this.isBlocker() ) return;
      if ( this.allowsAll() ) return;
      if ( 'right' === direction ) return this.rotateClockwise();
      if ( 'left'  === direction ) return this.rotateAntiClockwise();
    },

    rotateClockwise: function () {
      var newAllows = [];
      if ( this.allows('left') ) newAllows.push('up');
      if ( this.allows('up') ) newAllows.push('right');
      if ( this.allows('right') ) newAllows.push('down');
      if ( this.allows('down') ) newAllows.push('left');
      this.set('allows', newAllows);
    },

    rotateAntiClockwise: function () {
      var newAllows = [];
      if ( this.allows('right') ) newAllows.push('up');
      if ( this.allows('down') ) newAllows.push('right');
      if ( this.allows('left') ) newAllows.push('down');
      if ( this.allows('up') ) newAllows.push('left');
      this.set('allows', newAllows);
    },

    push: function (direction, value) {
      if ( direction === 'up' || direction === 'down' ) this.set('y', value);
      if ( direction === 'left' || direction === 'right' ) this.set('x', value);
    },

    toJSON: function () {
      var json = _.clone(this.attributes);
      json.allows = this.get('allows').join('-')
      json.cls = {};
      json.cls.selected = this.get('selected') === true ? 'selected' : '';
      return json;
    },
  });

  return Backbone.Collection.extend({
    model: Square,
    layout: undefined,

    loadLayout: function (layoutName) {
      this.layout = new Layout(Resources[layoutName]);
      this.width = this.layout.get('itemsPerRow');
      this.height = this.layout.get('map').length / this.layout.get('itemsPerRow');

      this.reset();

      var map = _.clone(this.layout.get('map'));
      for ( var y = 0; y < this.height; y++ ) {
        for ( var x = 0; x < this.width; x++ ) {
          this.add(_.extend(map.shift(), {x: x, y: y}));
        }
      }
    },

    rows: function () {
      var rows = [];
      for ( var y = 0; y < this.height; y++ ) rows.push(this.getRow(y));
      return rows;
    },

    cols: function () {
      var cols = [];
      for ( var x = 0; x < this.width; x++ ) cols.push(this.getCol(x));
      return cols;
    },

    getWidth: function () { return this.width - 1 },
    getHeight: function () { return this.height - 1 },

    getSquare: function (x, y) {
      var square = this.find(function (item) { return item.get('x') === x && item.get('y') === y });
      if ( !square ) throw "Could not find square [" + x + "," + y + "]";
      return square;
    },

    getRow: function (y) {
      var squares = this.filter(function (item) { return item.get('y') === y });
      return _(squares).sortBy(function (item) { return item.get('x') });
    },

    getCol: function (x) {
      var squares = this.filter(function (item) { return item.get('x') === x });
      return _(squares).sortBy(function (item) { return item.get('y') });
    },

    hasPlayer: function (x, y) {
      return this.getSquare(x, y).hasPlayer();
    },

    isBlocker: function (x, y) {
      return this.getSquare(x, y).isBlocker();
    },

    getPlayerSquare: function (player) {
      return this.find(function (item) {
        return item.hasPlayer(player);
      });
    },

    select: function (index) {
      this.invoke('set', 'selected', false);
      this.at(index).set('selected', true);
    },

    getSelected: function () { return this.find(function (item) { return item.get('selected') }) },

    allows: function (options) { // x, y, direction
      if ( !options ) options = {};
      if ( options.square ) {
        options.x = square.get('x');
        options.y = square.get('y');
      }

      if ( this.allowsAll() ) return true;
      return _(this.get('allows')).contains(direction);
    },

    toJSON: function () { return this.map(function (item) { return item.toJSON() }) },
  })
});
<|endoftext|>


<commit_before>import expect from 'expect'

import validate, { rules } from '../lib/validate'

const testRule = (ruleName, value, param, expectedMessage) => {
  const message = rules[ruleName]({ title: 'value', value, param })
  expect(message).toBe(expectedMessage || false)
}

describe('rules', () => {

  it('should validate `required`', () => {
    testRule('required', null, false)
    testRule('required', null, true, 'value is required')
    testRule('required', undefined, true, 'value is required')
    testRule('required', '', true, 'value is required')
    testRule('required', 0, true)
    testRule('required', 1, true)
    testRule('required', 'string', true)
  })

  it('should validate `min`', () => {
    testRule('min', 0, 1, 'value must not be less than 1')
    testRule('min', 1, 1)
    testRule('min', 1, 0)
  })

  it('should validate `max`', () => {
    testRule('max', 1, 0, 'value must not be more than 1')
    testRule('max', 1, 1)
    testRule('max', 0, 1)
  })

  it('should validate `minLength`', () => {
    testRule('minLength', '', 1, 'value must have at least 1 character')
    testRule('minLength', 'f', 2, 'value must have at least 2 characters')
    testRule('minLength', 'fo', 2)
    testRule('minLength', 'foo', 2)

    testRule('minLength', [], 1, 'value must have at least 1 item')
    testRule('minLength', [1], 2, 'value must have at least 2 items')
    testRule('minLength', [1, 1], 2)
    testRule('minLength', [1, 1, 1], 2)
  })

  it('should validate `maxLength`', () => {

    testRule('maxLength', 'foo', 2, 'value must not have more than 2 characters')
    testRule('maxLength', 'fo', 1, 'value must not have more than 1 character')
    testRule('maxLength', 'f', 1)
    testRule('maxLength', '', 1)

    testRule('maxLength', [1, 1, 1], 2, 'value must not have more than 2 items')
    testRule('maxLength', [1, 1], 1, 'value must not have more than 1 item')
    testRule('maxLength', [1], 1)
    testRule('maxLength', [], 1)

  })

  it('should validate `match`', () => {
    const pattern = /foo/
    testRule('match', 'foo', pattern)
    testRule('match', 'bar', pattern, 'value does not match pattern /foo/')
  })
})

describe('validate', () => {

  context('when root is a value', () => {

    it('returns the correct value', async () => {
      const schema = {
        type: 'string',
        rules: {
          required: true
        }
      }

      expect(await validate(schema, 'foo')).toEqual(null)
      expect(await validate(schema, null)).toEqual({
        message: 'Root string is required'
      })
    })
  })

  context('when root is an array', () => {

    it('returns the correct value', async () => {
      const schema = {
        type: 'array',
        rules: {
          minLength: 1
        },
        schema: {
          type: 'string'
        }
      }

      expect(await validate(schema, ['foo'])).toEqual(null)
      expect(await validate(schema, [])).toEqual({
        message: 'Root array must have at least 1 item'
      })
    })
  })

  context('when root is an object', () => {

    it('returns the correct value', async () => {
      const schema = {
        type: 'object',
        rules: {
          required: true
        },
        schema: {
          foo: {
            type: 'string',
            rules: {
              required: true
            }
          },
          bar: {
            type: 'string',
            rules: {
              required: true
            }
          }
        }
      }

      expect(await validate(schema, null)).toEqual({
        message: 'Root object is required',
        fieldErrors: {
          foo: {
            message: 'Foo is required'
          },
          bar: {
            message: 'Bar is required'
          }
        }
      })

      expect(await validate(schema, {})).toEqual({
        fieldErrors: {
          foo: {
            message: 'Foo is required'
          },
          bar: {
            message: 'Bar is required'
          }
        }
      })
    })
  })

  it('should return custom error messages defined as strings', async () => {
    const schema = {
      type: 'object',
      schema: {
        foo: {
          rules: {
            required: {
              param: true,
              errorMessage: 'custom error string'
            }
          }
        }
      }
    }

    expect(await validate(schema, {})).toEqual({
      fieldErrors: {
        foo: {
          message: 'custom error string'
        }
      }
    })
  })

  it('should return custom error messages defined as functions', async () => {
    let fieldOptions
    const schema = {
      type: 'object',
      schema: {
        foo: {
          rules: {
            maxLength: {
              param: 1,
              errorMessage: (options) => {
                fieldOptions = options
                return 'custom error function'
              }
            }
          }
        }
      }
    }

    expect(await validate(schema, {foo: 'bar'})).toEqual({
      fieldErrors: {
        foo: {
          message: 'custom error function'
        }
      }
    })

    expect(fieldOptions.value).toEqual('bar')
    expect(fieldOptions.schema).toEqual(schema.schema.foo)
    expect(fieldOptions.param).toBe(1)
    expect(fieldOptions.title).toBe('Foo')
    expect(fieldOptions.rootValue).toEqual({ foo: 'bar' })
  })

  describe('field titles', () => {

    context('when defined with a schema title', () => {

      it('should be the schema title', async () => {
        expect(await validate({
          type: 'string',
          title: 'foo',
          rules: {
            required: true
          }
        }, null)).toEqual({
          message: 'foo is required'
        })
      })
    })

    context('when defined without a schema title', () => {

      it('should be the field name', async () => {
        expect(await validate({
          type: 'object',
          schema: {
            foo_attr: {
              type: 'string',
              rules: {
                required: true
              }
            }
          }
        }, {})).toEqual({
          fieldErrors: {
            foo_attr: {
              message: 'Foo Attr is required'
            }
          }
        })
      })
    })
  })

  it('should return the first form error raised by a child node', async () => {
    expect(await validate({
      type: 'object',
      schema: {
        foo_attr: {
          type: 'string',
          rules: {
            required: {
              param: true,
              formError: true
            }
          }
        },
        bar_attr: {
          type: 'string',
          rules: {
            required: true,
            param: {
              param: true,
              formError: true
            }
          }
        }
      }
    }, {})).toEqual({
      formError: 'Foo Attr is required',
      fieldErrors: {
        bar_attr: {
          message: 'Bar Attr is required'
        }
      }
    })
  })
})

/*
TODO:

- custom errors (sync and async)
*/
<commit_msg>Added test for valid action<commit_after>import expect from 'expect'

import validate, { rules } from '../lib/validate'

const testRule = (ruleName, value, param, expectedMessage) => {
  const message = rules[ruleName]({ title: 'value', value, param })
  expect(message).toBe(expectedMessage || false)
}

describe('rules', () => {

  it('should validate `required`', () => {
    testRule('required', null, false)
    testRule('required', null, true, 'value is required')
    testRule('required', undefined, true, 'value is required')
    testRule('required', '', true, 'value is required')
    testRule('required', 0, true)
    testRule('required', 1, true)
    testRule('required', 'string', true)
  })

  it('should validate `min`', () => {
    testRule('min', 0, 1, 'value must not be less than 1')
    testRule('min', 1, 1)
    testRule('min', 1, 0)
  })

  it('should validate `max`', () => {
    testRule('max', 1, 0, 'value must not be more than 1')
    testRule('max', 1, 1)
    testRule('max', 0, 1)
  })

  it('should validate `minLength`', () => {
    testRule('minLength', '', 1, 'value must have at least 1 character')
    testRule('minLength', 'f', 2, 'value must have at least 2 characters')
    testRule('minLength', 'fo', 2)
    testRule('minLength', 'foo', 2)

    testRule('minLength', [], 1, 'value must have at least 1 item')
    testRule('minLength', [1], 2, 'value must have at least 2 items')
    testRule('minLength', [1, 1], 2)
    testRule('minLength', [1, 1, 1], 2)
  })

  it('should validate `maxLength`', () => {

    testRule('maxLength', 'foo', 2, 'value must not have more than 2 characters')
    testRule('maxLength', 'fo', 1, 'value must not have more than 1 character')
    testRule('maxLength', 'f', 1)
    testRule('maxLength', '', 1)

    testRule('maxLength', [1, 1, 1], 2, 'value must not have more than 2 items')
    testRule('maxLength', [1, 1], 1, 'value must not have more than 1 item')
    testRule('maxLength', [1], 1)
    testRule('maxLength', [], 1)

  })

  it('should validate `match`', () => {
    const pattern = /foo/
    testRule('match', 'foo', pattern)
    testRule('match', 'bar', pattern, 'value does not match pattern /foo/')
  })
})

describe('validate', () => {

  it('should return null when valid', async () => {
    expect(await validate({
      type: 'object',
      schema: {
        foo: {
          type: 'string',
          rules: {
            required: true
          }
        }
      }
    }, {
      foo: 'bar'
    })).toEqual(null)
  })

  context('when root is a value', () => {

    it('returns the correct value', async () => {
      const schema = {
        type: 'string',
        rules: {
          required: true
        }
      }

      expect(await validate(schema, 'foo')).toEqual(null)
      expect(await validate(schema, null)).toEqual({
        message: 'Root string is required'
      })
    })
  })

  context('when root is an array', () => {

    it('returns the correct value', async () => {
      const schema = {
        type: 'array',
        rules: {
          minLength: 1
        },
        schema: {
          type: 'string'
        }
      }

      expect(await validate(schema, ['foo'])).toEqual(null)
      expect(await validate(schema, [])).toEqual({
        message: 'Root array must have at least 1 item'
      })
    })
  })

  context('when root is an object', () => {

    it('returns the correct value', async () => {
      const schema = {
        type: 'object',
        rules: {
          required: true
        },
        schema: {
          foo: {
            type: 'string',
            rules: {
              required: true
            }
          },
          bar: {
            type: 'string',
            rules: {
              required: true
            }
          }
        }
      }

      expect(await validate(schema, null)).toEqual({
        message: 'Root object is required',
        fieldErrors: {
          foo: {
            message: 'Foo is required'
          },
          bar: {
            message: 'Bar is required'
          }
        }
      })

      expect(await validate(schema, {})).toEqual({
        fieldErrors: {
          foo: {
            message: 'Foo is required'
          },
          bar: {
            message: 'Bar is required'
          }
        }
      })
    })
  })

  it('should return custom error messages defined as strings', async () => {
    const schema = {
      type: 'object',
      schema: {
        foo: {
          rules: {
            required: {
              param: true,
              errorMessage: 'custom error string'
            }
          }
        }
      }
    }

    expect(await validate(schema, {})).toEqual({
      fieldErrors: {
        foo: {
          message: 'custom error string'
        }
      }
    })
  })

  it('should return custom error messages defined as functions', async () => {
    let fieldOptions
    const schema = {
      type: 'object',
      schema: {
        foo: {
          rules: {
            maxLength: {
              param: 1,
              errorMessage: (options) => {
                fieldOptions = options
                return 'custom error function'
              }
            }
          }
        }
      }
    }

    expect(await validate(schema, {foo: 'bar'})).toEqual({
      fieldErrors: {
        foo: {
          message: 'custom error function'
        }
      }
    })

    expect(fieldOptions.value).toEqual('bar')
    expect(fieldOptions.schema).toEqual(schema.schema.foo)
    expect(fieldOptions.param).toBe(1)
    expect(fieldOptions.title).toBe('Foo')
    expect(fieldOptions.rootValue).toEqual({ foo: 'bar' })
  })

  describe('field titles', () => {

    context('when defined with a schema title', () => {

      it('should be the schema title', async () => {
        expect(await validate({
          type: 'string',
          title: 'foo',
          rules: {
            required: true
          }
        }, null)).toEqual({
          message: 'foo is required'
        })
      })
    })

    context('when defined without a schema title', () => {

      it('should be the field name', async () => {
        expect(await validate({
          type: 'object',
          schema: {
            foo_attr: {
              type: 'string',
              rules: {
                required: true
              }
            }
          }
        }, {})).toEqual({
          fieldErrors: {
            foo_attr: {
              message: 'Foo Attr is required'
            }
          }
        })
      })
    })
  })

  it('should return the first form error raised by a child node', async () => {
    expect(await validate({
      type: 'object',
      schema: {
        foo_attr: {
          type: 'string',
          rules: {
            required: {
              param: true,
              formError: true
            }
          }
        },
        bar_attr: {
          type: 'string',
          rules: {
            required: true,
            param: {
              param: true,
              formError: true
            }
          }
        }
      }
    }, {})).toEqual({
      formError: 'Foo Attr is required',
      fieldErrors: {
        bar_attr: {
          message: 'Bar Attr is required'
        }
      }
    })
  })
})

/*
TODO:

- custom errors (sync and async)
*/
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name ausEnvApp.controller:BarCtrl
 * @description
 * # BarCtrl
 * Controller of the ausEnvApp
 */
angular.module('ausEnvApp')
  .controller('BarCtrl', function ($scope,$log,selection,details,downloads) {
    $scope.selection = selection;
    $scope.bar = details.chartMetaData();
    $scope.barColors = [];
    $scope.barData = [];
    $scope.barLabels = [];
    $scope.barOptions =   {
      // Sets the chart to be responsive
      responsive: true,

      //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
      scaleBeginAtZero : false,

      //Boolean - Whether grid lines are shown across the chart
      scaleShowGridLines : true,

      //String - Colour of the grid lines
      scaleGridLineColor : "rgba(0,0,0,.05)",

      //Number - Width of the grid lines
      scaleGridLineWidth : 1,

      //Boolean - If there is a stroke on each bar
      barShowStroke : true,

      //Number - Pixel width of the bar stroke
      barStrokeWidth : 0.1,

      //Number - Spacing between each of the X value sets
      barValueSpacing : 2,

      //Number - Spacing between data sets within X values
      barDatasetSpacing : 1,

      animation:true,
      scaleLabel: "      <%=value%>"
    };

    $scope.barOptions.tooltipTemplate = details.tooltipTextFunction($scope.bar);

    $scope.assignBarChartColours = function(barLabels){
      var firstYear = barLabels[0];
      var currentYearIndex = selection.year - firstYear;
      // +++TODO: Extract to respond to changed year...
      // +++TODO: Tidy up!
      for (var i=0; i<barLabels.length; i++) {
        if (selection.year === parseInt(barLabels[i])) {
          currentYearIndex = i;
          break;
        }
      }

      var barColors = [{fillColor:[details.themeColours.lightGreen]}];
      barColors[0].fillColor[currentYearIndex] = details.themeColours.darkGreen;

      if (currentYearIndex < barLabels.length-1) {
        barColors[0].fillColor[currentYearIndex+1] = details.themeColours.lightGreen;
      }

      return barColors;
    };

    $scope.adjustColours = function(){
      $scope.barOptions.animation = false;
      $scope.barColors = $scope.assignBarChartColours($scope.barLabels);
    };

    $scope.createBarChart = function(){
      $scope.barOptions.animation = true;
      $scope.getBarData().then(function(data){
        var barData = data[0];
        var metadata = data[1];

        $scope.barOptions.animation = true;
        $scope.barData = [barData.map(function(e){return e.value;})];
        $scope.barLabels = barData.map(function(e){return e.label;});
        $scope.barSeries = [metadata.label];
        $scope.adjustColours();
        details.populateLabels($scope.bar,metadata);

        $scope.bar.download = downloads.downloadableTable(barData.map(function(line){return [line.label,line.value];}),['Year','Value']);
        $scope.bar.download_fn = downloads.makeDownloadFilename($scope.locationLabel(),$scope.bar.title,'annual');
      },function(){
        $scope.barData = [];
        $scope.barLabels = [];
        $scope.barSeries = [];
        $scope.bar = details.chartMetaData();
      });
    };

    $scope.watchList.forEach(function(prop){
      $scope.$watch('selection.'+prop,$scope.createBarChart);
    });

    $scope.$watch('selection.year',$scope.adjustColours);
  });
<commit_msg>Fix handling of NaNs in barchart (convert to 0)<commit_after>'use strict';

/**
 * @ngdoc function
 * @name ausEnvApp.controller:BarCtrl
 * @description
 * # BarCtrl
 * Controller of the ausEnvApp
 */
angular.module('ausEnvApp')
  .controller('BarCtrl', function ($scope,$log,selection,details,downloads) {
    $scope.selection = selection;
    $scope.bar = details.chartMetaData();
    $scope.barColors = [];
    $scope.barData = [];
    $scope.barLabels = [];
    $scope.barOptions =   {
      // Sets the chart to be responsive
      responsive: true,

      //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
      scaleBeginAtZero : false,

      //Boolean - Whether grid lines are shown across the chart
      scaleShowGridLines : true,

      //String - Colour of the grid lines
      scaleGridLineColor : "rgba(0,0,0,.05)",

      //Number - Width of the grid lines
      scaleGridLineWidth : 1,

      //Boolean - If there is a stroke on each bar
      barShowStroke : true,

      //Number - Pixel width of the bar stroke
      barStrokeWidth : 0.1,

      //Number - Spacing between each of the X value sets
      barValueSpacing : 2,

      //Number - Spacing between data sets within X values
      barDatasetSpacing : 1,

      animation:true,
      scaleLabel: "      <%=value%>"
    };

    $scope.barOptions.tooltipTemplate = details.tooltipTextFunction($scope.bar);

    $scope.assignBarChartColours = function(barLabels){
      var firstYear = barLabels[0];
      var currentYearIndex = selection.year - firstYear;
      // +++TODO: Extract to respond to changed year...
      // +++TODO: Tidy up!
      for (var i=0; i<barLabels.length; i++) {
        if (selection.year === parseInt(barLabels[i])) {
          currentYearIndex = i;
          break;
        }
      }

      var barColors = [{fillColor:[details.themeColours.lightGreen]}];
      barColors[0].fillColor[currentYearIndex] = details.themeColours.darkGreen;

      if (currentYearIndex < barLabels.length-1) {
        barColors[0].fillColor[currentYearIndex+1] = details.themeColours.lightGreen;
      }

      return barColors;
    };

    $scope.adjustColours = function(){
      $scope.barOptions.animation = false;
      $scope.barColors = $scope.assignBarChartColours($scope.barLabels);
    };

    $scope.createBarChart = function(){
      $scope.barOptions.animation = true;
      $scope.getBarData().then(function(data){
        var barData = data[0];
        var metadata = data[1];

        $scope.barOptions.animation = true;
        $scope.barData = [barData.map(function(e){return isNaN(e.value)?0.0:e.value;})];
        $scope.barLabels = barData.map(function(e){return e.label;});
        $scope.barSeries = [metadata.label];
        $scope.adjustColours();
        details.populateLabels($scope.bar,metadata);

        $scope.bar.download = downloads.downloadableTable(barData.map(function(line){return [line.label,line.value];}),['Year','Value']);
        $scope.bar.download_fn = downloads.makeDownloadFilename($scope.locationLabel(),$scope.bar.title,'annual');
      },function(){
        $scope.barData = [];
        $scope.barLabels = [];
        $scope.barSeries = [];
        $scope.bar = details.chartMetaData();
      });
    };

    $scope.watchList.forEach(function(prop){
      $scope.$watch('selection.'+prop,$scope.createBarChart);
    });

    $scope.$watch('selection.year',$scope.adjustColours);
  });
<|endoftext|>


<commit_before>'use strict';

/*
 *  Controller for Environments
 */
angular.module('ancorDashboardApp')
  .controller('EnvCtrl', function ($scope, $rootScope, $http, $window, $route) {

    // h1 title
    //
    $scope.title = 'ANCOR Enviornments';

    // page title
    //
    $window.document.title = 'Environments | ANCOR Dashboard';

    /*
     *  REST API calls to ANCOR
     */

    // Get Version
    //
    $http.get($rootScope.ancorIPAddress+'/v1').success(function(data) {
      $scope.version = data.version;
    });

    // Get all environments
    //
    $http.get($rootScope.ancorIPAddress+'/v1/environments').success(function(data) {
      $scope.env = data;
      $scope.keys = [];

      angular.forEach($scope.env[0], function(value, key) {
        $scope.keys.push(key);
      });

      if ($scope.keys.length > 1) {
        $scope.keys.push('Delete');
      }
    });

    $scope.data = [{name: 'Loading configurations...', contents: ''}];

    // Delete Environment
    //
    $scope.deleteEnv = function (slug) {
      var url = $rootScope.ancorIPAddress+'/v1/environments/' + slug;
      console.log('delete ' + slug);
      $window.alert('Deleted ' + slug + '!');

      $http.delete(url);
      $route.reload();
    };

    $scope.getState = function (lockState) {
      if (lockState === true) {
        return 'disabled';
      } else {
        return '';
      }
    };

    $scope.awesomeThings = [
      'HTML5 Boilerplate',
      'AngularJS',
      'Karma'
    ];
  });

EnvCtrl.$inject = ['$window'];
<commit_msg>Fix environments typo<commit_after>'use strict';

/*
 *  Controller for Environments
 */
angular.module('ancorDashboardApp')
  .controller('EnvCtrl', function ($scope, $rootScope, $http, $window, $route) {

    // h1 title
    //
    $scope.title = 'ANCOR Environments';

    // page title
    //
    $window.document.title = 'Environments | ANCOR Dashboard';

    /*
     *  REST API calls to ANCOR
     */

    // Get Version
    //
    $http.get($rootScope.ancorIPAddress+'/v1').success(function(data) {
      $scope.version = data.version;
    });

    // Get all environments
    //
    $http.get($rootScope.ancorIPAddress+'/v1/environments').success(function(data) {
      $scope.env = data;
      $scope.keys = [];

      angular.forEach($scope.env[0], function(value, key) {
        $scope.keys.push(key);
      });

      if ($scope.keys.length > 1) {
        $scope.keys.push('Delete');
      }
    });

    $scope.data = [{name: 'Loading configurations...', contents: ''}];

    // Delete Environment
    //
    $scope.deleteEnv = function (slug) {
      var url = $rootScope.ancorIPAddress+'/v1/environments/' + slug;
      console.log('delete ' + slug);
      $window.alert('Deleted ' + slug + '!');

      $http.delete(url);
      $route.reload();
    };

    $scope.getState = function (lockState) {
      if (lockState === true) {
        return 'disabled';
      } else {
        return '';
      }
    };

    $scope.awesomeThings = [
      'HTML5 Boilerplate',
      'AngularJS',
      'Karma'
    ];
  });

EnvCtrl.$inject = ['$window'];
<|endoftext|>