<commit_before>$(function() {

	var HangmanJS = function() {

		/**
		 * Make sure that we're a constructor and not a function call
		 */
		if ((!this instanceof HangmanJS)) {
			return new HangmanJS();
		}

		/**
		 * Default words to pick from
		 */
		HangmanJS.game_words = [
			'technology',
			'fantastic',
			'buckaroo',
			'bandwagon',
			'icebox',
			'jawbreaker',
			'zombie',
			'cheese',
			'because',
			'football',
			'jazz',
			'exponential',
			'atlantis',
			'psychologist',
			'archaeologist',
			'quarantine',
			'champion',
			'protestant'
		];

		/**
		 * Array of used characters
		 */
		HangmanJS.used_characters = [];

		/**
		 * The current word
		 */
		HangmanJS.current_word = {};

		/**
		 * The death score
		 */
		HangmanJS.fail_score = 0;

		/**
		 * The score at which the player fails
		 */
		HangmanJS.failed_score = 8;

		/**
		 * Count of correct chars guessed
		 */
		HangmanJS.correctly_guessed = 0;

		/**
		 * The Character canvas
		 */
		HangmanJS.canvas = null;

		/**
		 * Canvas Context
		 */
		HangmanJS.character_ctx = null;

		/**
		 * Current body part count
		 */
		 HangmanJS.current_body_part = 0;

		/**
		 * Maps part id's to functions
		 */
		HangmanJS.body_parts = [
			'frame',
			'noose',
			'head',
			'body',
			'left_arm',
			'right_arm',
			'left_leg',
			'right_leg'
		];

		/**
		 * Initiate HangmanJS
		 */
		this.init = function() {
			HangmanJS.bind_menu();

			if (document.location.href.indexOf('#game') > -1) {
				HangmanJS.setup_game();
			}

			if (document.location.href.indexOf('#credits') > -1) {
				HangmanJS.view_credits();
			}
		};

		/**
		 * Bind menu buttons
		 */
		HangmanJS.bind_menu = function() {
			var play_button = $('#play-game');
			var credits_button = $('#view-credits');

			play_button.click(function(e) {
				e.preventDefault();
				HangmanJS.setup_game();
			});

			credits_button.click(function(e) {
				e.preventDefault();
				HangmanJS.view_credits();
			});
		};

		/**
		 * View the credits page
		 */
		HangmanJS.view_credits = function() {
			$('#credits').addClass('show');
			HangmanJS.bind_close();
		};

		/**
		 * Setup the game
		 */
		HangmanJS.setup_game = function() {
			HangmanJS.open_game();
			HangmanJS.bind_close();

			HangmanJS.current_word = HangmanJS.pick_word();

			HangmanJS.setup_characters();
			HangmanJS.setup_info();

			HangmanJS.setup_canvas();

			HangmanJS.bind_enter_character();
			HangmanJS.setup_alphabet();
		};

		/**
		 * Open the game window
		 */
		HangmanJS.open_game = function() {
			var stage = $('#game-stage');
			stage.addClass('show');
		};

		/**
		 * Close button bindings
		 */
		HangmanJS.bind_close = function() {
			$('.js-to-menu').click(function(e) {
				HangmanJS.to_menu($(this).data('from'));
			});
		};

		/**
		 * Go to the menu
		 */
		HangmanJS.to_menu = function(from) {
			$('.game-section.' + from).removeClass('show');

			if (from == 'game') {
				HangmanJS.revert_game();
			}
		};

		/**
		 * Pick a random word
		 */
		HangmanJS.pick_word = function() {
			var chars;
			var uppercase_chars = [];
			var item = HangmanJS.game_words[Math.floor(Math.random() * HangmanJS.game_words.length)];

			chars = item.split('');

			for (var i = 0; i < chars.length; i++) {
    			uppercase_chars.push(chars[i].toUpperCase());
			}

			return {
				word: item,
				length: item.length,
				chars: uppercase_chars
			};
		};

		/**
		 * Setup the list of the alphabet
		 */
		HangmanJS.setup_alphabet = function() {
			var list = $('#alphabet');
			var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

			for (var i = 0; i < alphabet.length; i++) {
				var next_char = alphabet.charAt(i);
				list.append('
' + next_char + '
');
			}
		};

		/**
		 * Bind the event handler for character enter
		 */
		HangmanJS.bind_enter_character = function() {
			var input = $('#enter-char-input');

			input.keypress(function(e) {
				if (e.which  == 13) {
					var c = input.val();

					if (c != '' && c.match(/^[a-zA-Z]+$/)) {
						HangmanJS.input_char(c);
					}

					input.val('');
				}
			});
		};

		/**
		 * Character delegate
		 */
		HangmanJS.input_char = function(character) {
			character = character.toUpperCase();

			if ($.inArray(character, HangmanJS.used_characters) == -1) {
				HangmanJS.used_characters.push(character);

				$('#alphabet li[data-char=' + character + ']').addClass('used');

				if ($.inArray(character, HangmanJS.current_word.chars) > -1) {

					$('#alphabet li[data-char=' + character + ']').addClass('in-word');

					for (var i = 0; i < HangmanJS.current_word.chars.length; i++) {

						if (HangmanJS.current_word.chars[i] == character) {
							HangmanJS.correctly_guessed++;
							$('.word-characters li[data-id="' + i + '"]').html(character);
						}

					}

					if (HangmanJS.correctly_guessed >= HangmanJS.current_word.length) {
						$('input').blur();
						$('#overlay').addClass('show');
						$('#overlay .modal.success').addClass('show');
					}

				} else {
					HangmanJS.fail_score++;

					HangmanJS.add_body_part();

					HangmanJS.update_score();

					if (HangmanJS.fail_score >= HangmanJS.failed_score) {
						$('input').blur();
						$('#overlay').addClass('show');
						$('#overlay .modal.failed').addClass('show');
					}
				}
			}
		};

		/**
		 * Setup the allowed characters list
		 */
		HangmanJS.setup_characters = function() {
			var list = $('.word-characters');

			for (var i = 0; i < HangmanJS.current_word.chars.length; i++) {
				list.append('

');
			}
		};

		/**
		 * Setup the game information
		 */
		HangmanJS.setup_info = function() {
			$('.word-length').html(HangmanJS.current_word.length);

			HangmanJS.update_score();
		};

		/**
		 * Update the game score view
		 */
		HangmanJS.update_score = function() {
			var holder = $('.attempts-left');

			holder.html(HangmanJS.failed_score - HangmanJS.fail_score);
		};

		/**
		 * Setup the game canvas
		 */
		HangmanJS.setup_canvas = function() {
			HangmanJS.character = $('#character');
			HangmanJS.character = HangmanJS.character[0];

			HangmanJS.character_ctx = HangmanJS.character.getContext('2d');
		};

		/**
		 * Adds another body part to the canvas
		 */
		HangmanJS.add_body_part = function() {
			var part;

			if (HangmanJS.current_body_part < HangmanJS.body_parts.length) {
				part = HangmanJS.body_parts[HangmanJS.current_body_part];

				HangmanJS.character_parts[part]();
				HangmanJS.current_body_part++;
			}

		};

		/**
		 * Character parts - Canvas
		 *
		 * 1) Frame
		 * 2) Noose
		 * 3) Head
		 * 4) Body
		 * 5) Left Arm
		 * 6) Right Arm
		 * 7) Left Lef
		 * 8) Right Leg
		 *
		 * Needs work...just looks ugly.
		 * Arm and legs could be refactored into one
		 */
		HangmanJS.character_parts =  {

			frame: function() {

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(0, 399);
				HangmanJS.character_ctx.lineTo(300, 399);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(40, 5);
				HangmanJS.character_ctx.lineTo(40, 400);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

			},

			noose: function() {

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(40, 5);
				HangmanJS.character_ctx.lineTo(HangmanJS.character.width / 2, 5);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(HangmanJS.character.width / 2, 5);
				HangmanJS.character_ctx.lineTo(HangmanJS.character.width / 2, 30);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

			},

			head: function() {

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.arc(HangmanJS.character.width / 2, 65, 35, 0, 2 * Math.PI, false);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

			},

			body: function() {

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(HangmanJS.character.width / 2, 100);
				HangmanJS.character_ctx.lineTo(HangmanJS.character.width / 2, 250);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

			},

			arm: function(side) {
				side = side || 'left';

				var start_x = (HangmanJS.character.width / 2) - 50;

				if (side == 'right') {
					start_x = (HangmanJS.character.width / 2) + 50;
				}

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(HangmanJS.character.width / 2, 150);
				HangmanJS.character_ctx.lineTo(start_x, 180);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();
			},

			leg: function(side) {
				side = side || 'left';

				var start_x = (HangmanJS.character.width / 2) - 50;

				if (side == 'right') {
					start_x = (HangmanJS.character.width / 2) + 50;
				}

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(HangmanJS.character.width / 2, 250);
				HangmanJS.character_ctx.lineTo(start_x, 350);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();
			},

			left_arm: function() {
				this.arm('left');
			},

			right_arm: function() {
				this.arm('right');
			},

			left_leg: function() {
				this.leg('left');
			},

			right_leg: function() {
				this.leg('right');
			}

		};

		/**
		 * Reverts the game
		 */
		HangmanJS.revert_game = function() {
			$('#alphabet').empty();
			HangmanJS.used_characters = [];

			HangmanJS.fail_score = 0;
			HangmanJS.correctly_guessed = 0;

			HangmanJS.character.width = HangmanJS.character.width;
			HangmanJS.current_body_part = 0;

			$('.word-characters').html('');

			$('#overlay').removeClass('show');
			$('#overlay .modal.failed').removeClass('show');
			$('#overlay .modal.succuess').removeClass('show');
		};

	}

	//Move this shit
	var hangman = new HangmanJS();
	hangman.init();

});<commit_msg>TODO SECTS<commit_after>$(function() {

	/**
	 * HangmanJS - Andrew Hathaway
	 *
	 * @todo: Replace "current_body_part" with "fail_score"
	 * @todo: Refactor body_parts. Legs/arms should use same function
	 * @todo: Consider moving words to private place
	 *
	 */

	var HangmanJS = function() {

		/**
		 * Make sure that we're a constructor and not a function call
		 */
		if ((!this instanceof HangmanJS)) {
			return new HangmanJS();
		}

		/**
		 * Default words to pick from
		 */
		HangmanJS.game_words = [
			'technology',
			'fantastic',
			'buckaroo',
			'bandwagon',
			'icebox',
			'jawbreaker',
			'zombie',
			'cheese',
			'because',
			'football',
			'jazz',
			'exponential',
			'atlantis',
			'psychologist',
			'archaeologist',
			'quarantine',
			'champion',
			'protestant'
		];

		/**
		 * Array of used characters
		 */
		HangmanJS.used_characters = [];

		/**
		 * The current word
		 */
		HangmanJS.current_word = {};

		/**
		 * The death score
		 */
		HangmanJS.fail_score = 0;

		/**
		 * The score at which the player fails
		 */
		HangmanJS.failed_score = 8;

		/**
		 * Count of correct chars guessed
		 */
		HangmanJS.correctly_guessed = 0;

		/**
		 * The Character canvas
		 */
		HangmanJS.canvas = null;

		/**
		 * Canvas Context
		 */
		HangmanJS.character_ctx = null;

		/**
		 * Current body part count
		 */
		 HangmanJS.current_body_part = 0;

		/**
		 * Maps part id's to functions
		 */
		HangmanJS.body_parts = [
			'frame',
			'noose',
			'head',
			'body',
			'left_arm',
			'right_arm',
			'left_leg',
			'right_leg'
		];

		/**
		 * Initiate HangmanJS
		 */
		this.init = function() {
			HangmanJS.bind_menu();

			if (document.location.href.indexOf('#game') > -1) {
				HangmanJS.setup_game();
			}

			if (document.location.href.indexOf('#credits') > -1) {
				HangmanJS.view_credits();
			}
		};

		/**
		 * Bind menu buttons
		 */
		HangmanJS.bind_menu = function() {
			var play_button = $('#play-game');
			var credits_button = $('#view-credits');

			play_button.click(function(e) {
				e.preventDefault();
				HangmanJS.setup_game();
			});

			credits_button.click(function(e) {
				e.preventDefault();
				HangmanJS.view_credits();
			});
		};

		/**
		 * View the credits page
		 */
		HangmanJS.view_credits = function() {
			$('#credits').addClass('show');
			HangmanJS.bind_close();
		};

		/**
		 * Setup the game
		 */
		HangmanJS.setup_game = function() {
			HangmanJS.open_game();
			HangmanJS.bind_close();

			HangmanJS.current_word = HangmanJS.pick_word();

			HangmanJS.setup_characters();
			HangmanJS.setup_info();

			HangmanJS.setup_canvas();

			HangmanJS.bind_enter_character();
			HangmanJS.setup_alphabet();
		};

		/**
		 * Open the game window
		 */
		HangmanJS.open_game = function() {
			var stage = $('#game-stage');
			stage.addClass('show');
		};

		/**
		 * Close button bindings
		 */
		HangmanJS.bind_close = function() {
			$('.js-to-menu').click(function(e) {
				HangmanJS.to_menu($(this).data('from'));
			});
		};

		/**
		 * Go to the menu
		 */
		HangmanJS.to_menu = function(from) {
			$('.game-section.' + from).removeClass('show');

			if (from == 'game') {
				HangmanJS.revert_game();
			}
		};

		/**
		 * Pick a random word
		 */
		HangmanJS.pick_word = function() {
			var chars;
			var uppercase_chars = [];
			var item = HangmanJS.game_words[Math.floor(Math.random() * HangmanJS.game_words.length)];

			chars = item.split('');

			for (var i = 0; i < chars.length; i++) {
    			uppercase_chars.push(chars[i].toUpperCase());
			}

			return {
				word: item,
				length: item.length,
				chars: uppercase_chars
			};
		};

		/**
		 * Setup the list of the alphabet
		 */
		HangmanJS.setup_alphabet = function() {
			var list = $('#alphabet');
			var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

			for (var i = 0; i < alphabet.length; i++) {
				var next_char = alphabet.charAt(i);
				list.append('
' + next_char + '
');
			}
		};

		/**
		 * Bind the event handler for character enter
		 */
		HangmanJS.bind_enter_character = function() {
			var input = $('#enter-char-input');

			input.keypress(function(e) {
				if (e.which  == 13) {
					var c = input.val();

					if (c != '' && c.match(/^[a-zA-Z]+$/)) {
						HangmanJS.input_char(c);
					}

					input.val('');
				}
			});
		};

		/**
		 * Character delegate
		 */
		HangmanJS.input_char = function(character) {
			character = character.toUpperCase();

			if ($.inArray(character, HangmanJS.used_characters) == -1) {
				HangmanJS.used_characters.push(character);

				$('#alphabet li[data-char=' + character + ']').addClass('used');

				if ($.inArray(character, HangmanJS.current_word.chars) > -1) {

					$('#alphabet li[data-char=' + character + ']').addClass('in-word');

					for (var i = 0; i < HangmanJS.current_word.chars.length; i++) {

						if (HangmanJS.current_word.chars[i] == character) {
							HangmanJS.correctly_guessed++;
							$('.word-characters li[data-id="' + i + '"]').html(character);
						}

					}

					if (HangmanJS.correctly_guessed >= HangmanJS.current_word.length) {
						$('input').blur();
						$('#overlay').addClass('show');
						$('#overlay .modal.success').addClass('show');
					}

				} else {
					HangmanJS.fail_score++;

					HangmanJS.add_body_part();
					HangmanJS.update_score();

					if (HangmanJS.fail_score >= HangmanJS.failed_score) {
						$('input').blur();
						$('#overlay').addClass('show');
						$('#overlay .modal.failed').addClass('show');
					}
				}
			}
		};

		/**
		 * Setup the allowed characters list
		 */
		HangmanJS.setup_characters = function() {
			var list = $('.word-characters');

			for (var i = 0; i < HangmanJS.current_word.chars.length; i++) {
				list.append('

');
			}
		};

		/**
		 * Setup the game information
		 */
		HangmanJS.setup_info = function() {
			$('.word-length').html(HangmanJS.current_word.length);

			HangmanJS.update_score();
		};

		/**
		 * Update the game score view
		 */
		HangmanJS.update_score = function() {
			var holder = $('.attempts-left');

			holder.html(HangmanJS.failed_score - HangmanJS.fail_score);
		};

		/**
		 * Setup the game canvas
		 */
		HangmanJS.setup_canvas = function() {
			HangmanJS.character = $('#character');
			HangmanJS.character = HangmanJS.character[0];

			HangmanJS.character_ctx = HangmanJS.character.getContext('2d');
		};

		/**
		 * Adds another body part to the canvas
		 */
		HangmanJS.add_body_part = function() {
			var part;

			if (HangmanJS.current_body_part < HangmanJS.body_parts.length) {
				part = HangmanJS.body_parts[HangmanJS.current_body_part];

				HangmanJS.character_parts[part]();
				HangmanJS.current_body_part++;
			}

		};

		/**
		 * Character parts - Canvas
		 *
		 * 1) Frame
		 * 2) Noose
		 * 3) Head
		 * 4) Body
		 * 5) Left Arm
		 * 6) Right Arm
		 * 7) Left Lef
		 * 8) Right Leg
		 *
		 * Needs work...just looks ugly.
		 */
		HangmanJS.character_parts =  {

			frame: function() {

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(0, 399);
				HangmanJS.character_ctx.lineTo(300, 399);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(40, 5);
				HangmanJS.character_ctx.lineTo(40, 400);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

			},

			noose: function() {

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(40, 5);
				HangmanJS.character_ctx.lineTo(HangmanJS.character.width / 2, 5);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(HangmanJS.character.width / 2, 5);
				HangmanJS.character_ctx.lineTo(HangmanJS.character.width / 2, 30);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

			},

			head: function() {

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.arc(HangmanJS.character.width / 2, 65, 35, 0, 2 * Math.PI, false);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

			},

			body: function() {

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(HangmanJS.character.width / 2, 100);
				HangmanJS.character_ctx.lineTo(HangmanJS.character.width / 2, 250);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();

			},

			arm: function(side) {
				side = side || 'left';

				var start_x = (HangmanJS.character.width / 2) - 50;

				if (side == 'right') {
					start_x = (HangmanJS.character.width / 2) + 50;
				}

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(HangmanJS.character.width / 2, 150);
				HangmanJS.character_ctx.lineTo(start_x, 180);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();
			},

			leg: function(side) {
				side = side || 'left';

				var start_x = (HangmanJS.character.width / 2) - 50;

				if (side == 'right') {
					start_x = (HangmanJS.character.width / 2) + 50;
				}

				HangmanJS.character_ctx.beginPath();
				HangmanJS.character_ctx.moveTo(HangmanJS.character.width / 2, 250);
				HangmanJS.character_ctx.lineTo(start_x, 350);
				HangmanJS.character_ctx.lineWidth = 2;
				HangmanJS.character_ctx.strokeStyle = '#ffffff';
				HangmanJS.character_ctx.stroke();
			},

			left_arm: function() {
				this.arm('left');
			},

			right_arm: function() {
				this.arm('right');
			},

			left_leg: function() {
				this.leg('left');
			},

			right_leg: function() {
				this.leg('right');
			}

		};

		/**
		 * Reverts the game
		 */
		HangmanJS.revert_game = function() {
			$('#alphabet').empty();
			HangmanJS.used_characters = [];

			HangmanJS.fail_score = 0;
			HangmanJS.correctly_guessed = 0;

			HangmanJS.character.width = HangmanJS.character.width;
			HangmanJS.current_body_part = 0;

			$('.word-characters').html('');

			$('#overlay').removeClass('show');
			$('#overlay .modal.failed').removeClass('show');
			$('#overlay .modal.succuess').removeClass('show');
		};

	}

	//Move this shit
	var hangman = new HangmanJS();
	hangman.init();

});<|endoftext|>


<commit_before>import rssFinder from 'rss-finder';
import normalizeUrl from 'normalize-url';
import entities from 'entities';
import validUrl from 'valid-url';

import RSS from '../models/rss';

import personalization from '../utils/personalization';
import moment from 'moment';
import search from '../utils/search';
import asyncTasks from '../asyncTasks';

exports.list = async (req, res) => {
	const query = req.query || {};
	let feeds = [];

	if (query.type === 'recommended') {
		let recommendedRssIds = await personalization({
			endpoint: '/winds_rss_recommendations', userId: req.user.sub,
		});
		feeds = await RSS.find({_id: {$in: recommendedRssIds}});
	} else {
		feeds = await RSS.apiQuery(req.query);
	}

	res.json(feeds);
};

exports.get = async (req, res) => {
	if (req.params.rssId === 'undefined') {
		return res.sendStatus(404);
	}
	let rss = await RSS.findById(req.params.rssId).exec();
	if (!rss) {
		return res.sendStatus(404);
	}
	res.json(rss);
};

exports.post = async (req, res) => {
	const data = req.body || {};

	if (!data.feedUrl || !validUrl.isUri(normalizeUrl(data.feedUrl))) {
		return res.status(400).json({ error: 'Please provide a valid RSS URL.' });
	}

	let foundRSS = await rssFinder(normalizeUrl(data.feedUrl));

	if (!foundRSS.feedUrls.length) {
		return res.status(404).json({ error: 'We couldn\'t find any feeds for that RSS feed URL :(' });
	}

	let insertedFeeds = [];
	let feeds = [];

	for (let feed of foundRSS.feedUrls.slice(0,10)) {
		let feedTitle = feed.title;
		if (feedTitle.toLowerCase() === 'rss') {
			feedTitle = foundRSS.site.title;
		}
		let feedUrl = normalizeUrl(feed.url)
		if (!validUrl.isWebUri(feedUrl)) {
			continue
		}
		let rss
		rss = await RSS.findOne({feedUrl: feedUrl})
		// don't update featured RSS feeds since that ends up removing images etc
		if (!rss || (rss && !rss.featured)) {
			let response = await RSS.findOneAndUpdate(
				{feedUrl: feedUrl},
				{
					categories: 'RSS',
					description: entities.decodeHTML(feed.title),
					feedUrl: feedUrl,
					images: {
						favicon: foundRSS.site.favicon,
					},
					lastScraped: moment().format(),
					title: entities.decodeHTML(feedTitle),
					url: foundRSS.site.url,
					valid: true,
				},
				{
					new: true,
					rawResult: true,
					upsert: true,
				},
			);

			rss = response.value
			if (response.lastErrorObject.upserted) {
				insertedFeeds.push(rss);
			}
		}
		feeds.push(rss)
	}

	let promises = []
	insertedFeeds.map(f => {
		promises.push(search(f.searchDocument()))
		let rssScrapingPromise = asyncTasks.RssQueueAdd(
			{
				rss: f._id,
				url: f.feedUrl,
			},
			{
				priority: 1,
				removeOnComplete: true,
				removeOnFail: true,
			},
		)
		promises.push(rssScrapingPromise)
		if (!f.images.og && f.url) {
			let ogPromise = asyncTasks.OgQueueAdd(
				{
					url: f.url,
					type: 'rss',
				},
				{
					removeOnComplete: true,
					removeOnFail: true,
				},
			);
			promises.push(ogPromise)
		}
	});
	await Promise.all(promises)

	res.status(201);
	res.json(feeds);
};

exports.put = async (req, res) => {
	if (!req.User.admin) {
		return res.status(403).json({ error: 'You must be an admin to perform this action.' });
	}

	if (!req.params.rssId) {
		return res.status(401).json({ error: 'You must provide a valid RSS ID to perform this action' });
	}

	let rss = await RSS.findByIdAndUpdate(
		{
			_id: req.params.rssId,
		},
		req.body,
		{new: true},
	);

	if (!rss) {
		return res.status(404).json({ error: `Can't find RSS feed with id ${req.params.rssId}` });
	}

	res.json(rss);
};
<commit_msg>squahs 575977697<commit_after>import rssFinder from 'rss-finder';
import normalizeUrl from 'normalize-url';
import entities from 'entities';
import validUrl from 'valid-url';

import RSS from '../models/rss';

import personalization from '../utils/personalization';
import moment from 'moment';
import search from '../utils/search';
import asyncTasks from '../asyncTasks';
import mongoose from 'mongoose';

exports.list = async (req, res) => {
	const query = req.query || {};
	let feeds = [];

	if (query.type === 'recommended') {
		let recommendedRssIds = await personalization({
			endpoint: '/winds_rss_recommendations', userId: req.user.sub,
		});
		feeds = await RSS.find({_id: {$in: recommendedRssIds}});
	} else {
		feeds = await RSS.apiQuery(req.query);
	}

	res.json(feeds);
};

exports.get = async (req, res) => {
	let rssID = req.params.rssId

	if (!mongoose.Types.ObjectId.isValid(rssID)) {
		return res.status(422).json({ error: `RSS ID ${rssID} is invalid.`});
	}

	let rss = await RSS.findById(rssID).exec();
	if (!rss) {
		return res.sendStatus(404);
	}
	res.json(rss);
};

exports.post = async (req, res) => {
	const data = req.body || {};

	if (!data.feedUrl || !validUrl.isUri(normalizeUrl(data.feedUrl))) {
		return res.status(400).json({ error: 'Please provide a valid RSS URL.' });
	}

	let foundRSS = await rssFinder(normalizeUrl(data.feedUrl));

	if (!foundRSS.feedUrls.length) {
		return res.status(404).json({ error: 'We couldn\'t find any feeds for that RSS feed URL :(' });
	}

	let insertedFeeds = [];
	let feeds = [];

	for (let feed of foundRSS.feedUrls.slice(0,10)) {
		let feedTitle = feed.title;
		if (feedTitle.toLowerCase() === 'rss') {
			feedTitle = foundRSS.site.title;
		}
		let feedUrl = normalizeUrl(feed.url)
		if (!validUrl.isWebUri(feedUrl)) {
			continue
		}
		let rss
		rss = await RSS.findOne({feedUrl: feedUrl})
		// don't update featured RSS feeds since that ends up removing images etc
		if (!rss || (rss && !rss.featured)) {
			let response = await RSS.findOneAndUpdate(
				{feedUrl: feedUrl},
				{
					categories: 'RSS',
					description: entities.decodeHTML(feed.title),
					feedUrl: feedUrl,
					images: {
						favicon: foundRSS.site.favicon,
					},
					lastScraped: moment().format(),
					title: entities.decodeHTML(feedTitle),
					url: foundRSS.site.url,
					valid: true,
				},
				{
					new: true,
					rawResult: true,
					upsert: true,
				},
			);

			rss = response.value
			if (response.lastErrorObject.upserted) {
				insertedFeeds.push(rss);
			}
		}
		feeds.push(rss)
	}

	let promises = []
	insertedFeeds.map(f => {
		promises.push(search(f.searchDocument()))
		let rssScrapingPromise = asyncTasks.RssQueueAdd(
			{
				rss: f._id,
				url: f.feedUrl,
			},
			{
				priority: 1,
				removeOnComplete: true,
				removeOnFail: true,
			},
		)
		promises.push(rssScrapingPromise)
		if (!f.images.og && f.url) {
			let ogPromise = asyncTasks.OgQueueAdd(
				{
					url: f.url,
					type: 'rss',
				},
				{
					removeOnComplete: true,
					removeOnFail: true,
				},
			);
			promises.push(ogPromise)
		}
	});
	await Promise.all(promises)

	res.status(201);
	res.json(feeds);
};

exports.put = async (req, res) => {
	if (!req.User.admin) {
		return res.status(403).json({ error: 'You must be an admin to perform this action.' });
	}

	if (!req.params.rssId) {
		return res.status(401).json({ error: 'You must provide a valid RSS ID to perform this action' });
	}

	let rss = await RSS.findByIdAndUpdate(
		{
			_id: req.params.rssId,
		},
		req.body,
		{new: true},
	);

	if (!rss) {
		return res.status(404).json({ error: `Can't find RSS feed with id ${req.params.rssId}` });
	}

	res.json(rss);
};
<|endoftext|>


<commit_before>'use strict';
/* global SPARKS */

(function() {
  var shaders = {
    vertex: [,
      'attribute float size;',
      'attribute vec3 pcolor;',
      'varying vec3 vColor;',
      'void main() {',
        'vColor = pcolor;',
        'vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
        'gl_PointSize = size * ( 200.0 / length( mvPosition.xyz ) );',
        'gl_Position = projectionMatrix * mvPosition;',
      '}'
    ].join('\n'),
    fragment: [
      'uniform sampler2D texture;',
      'varying vec3 vColor;',
      'void main() {',
        'vec4 outColor = texture2D( texture, gl_PointCoord );',
        'gl_FragColor = outColor * vec4( vColor, 1.0 );',
     ' }'
    ].join('\n')
  };

  function generateSprite() {
    var canvas = document.createElement( 'canvas' );
    canvas.width = 128;
    canvas.height = 128;

    var context = canvas.getContext( '2d' );
    context.beginPath();
    context.arc( 64, 64, 60, 0, Math.PI * 2, false) ;

    context.lineWidth = 0.5; //0.05
    context.stroke();
    context.restore();

    var gradient = context.createRadialGradient( canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2 );

    gradient.addColorStop( 0, 'rgba(255,255,255,1)' );
    gradient.addColorStop( 0.2, 'rgba(255,255,255,1)' );
    gradient.addColorStop( 0.4, 'rgba(200,200,200,1)' );
    gradient.addColorStop( 1, 'rgba(0,0,0,1)' );

    context.fillStyle = gradient;

    context.fill();

    return canvas;

  }

  var Pool = {
    __pools: [],
    // Get a new Vector
    get: function() {
      if ( this.__pools.length > 0 ) {
        return this.__pools.pop();
      }
      console.error( "pool ran out!");
    },
    // Release a vector back into the pool
    add: function( v ) {
      this.__pools.push( v );
    }
  };

  var updaterObject = function(updater_func, update_type) {
    var Obj = function() {
      this[update_type] = updater_func;
    };
    return new Obj();
  };

  var addInitializer = function(emitter, init_func) {
    emitter.addInitializer(updaterObject(init_func, 'initialize'));
  };
  var addAction = function(emitter, init_func) {
    emitter.addAction(updaterObject(init_func, 'update'));
  };

  // returns an object { particleCloud, attributes, uniforms, particles }
  var prepareParticleGeometry = function(particle_count) {
    // sets up three.js side of things, the passing of values to shader.
    var particles = new THREE.Geometry();
    for (var i = 0; i < particle_count; i++) {
      particles.vertices.push(new THREE.Vector3(0, 0, 0));
      Pool.add(i);
    }

    var attributes = {
      size:  { type: 'f', value: [] },
      pcolor: { type: 'c', value: [] }
    };

    var sprite = generateSprite() ;

    var texture = new THREE.Texture( sprite );
    texture.needsUpdate = true;

    var uniforms = {
      texture:   { type: 't', value: texture }
    };
    var shaderMaterial = new THREE.ShaderMaterial( {
      uniforms: uniforms,
      attributes: attributes,
      vertexShader: shaders.vertex,
      fragmentShader: shaders.fragment,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      transparent: true
    });

    var particleCloud = new THREE.ParticleSystem( particles, shaderMaterial );

    particleCloud.dynamic = true;

    var vertices = particleCloud.geometry.vertices;
    var values_size = attributes.size.value;
    var values_color = attributes.pcolor.value;

    for (var v = 0; v < vertices.length; v++) {
      values_size[ v ] = 50;
      values_color[ v ] = new THREE.Color( 0x000000 );
      particles.vertices[ v ].set( Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY );
    }

    return {
      attributes: attributes,
      uniforms: uniforms,
      particleCloud: particleCloud,
      particles: particles,
      Pool: Pool
    };
  };

  window.prepareParticleGeometry = prepareParticleGeometry;
  window.addAction = addAction;
  window.addInitializer = addInitializer;
})();

angular.module('audioVizApp')
  .service('BeatCounter', function(Dancer) {
    return function(length) {
      var cache = {}, at = 0, count = 0;
      var result = {};
      result.tick = function() {
        var beat = Dancer.is_beat;
        if (beat) count++;
        if (cache.length >= length && cache[at])
          count--;
        cache[at] = Dancer.is_beat;
        at = (at+1) % length;
      };
      result.count = function() {
        return count;
      };

      return result;
    };
  })
  .controller('Particles', function($scope, AudioService, Dancer, BeatCounter) {
    $scope.Dancer = Dancer;

    $scope.camera = {
      fov: 45,
      near: 1,
      far: 5000
    };

    $scope.particles = {
      count: 75000
    };

    var scene, camera, attributes, uniforms, particleCloud, particles, Pool;
    var emitter, pointLight, emitter_position;
    $scope.scene_init = function(renderer, width, height) {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 2000 );
      camera.position.set( 0, 150, 400 );
      camera.lookAt( scene.position );
      renderer.setClearColor(0x000000);

      var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
      directionalLight.position.set( 0, -1, 1 );
      directionalLight.position.normalize();
      scene.add( directionalLight );

      pointLight = new THREE.PointLight( 0xffffff, 2, 300 );
      pointLight.position.set( 0, 0, 0 );
      scene.add( pointLight );

      // Particle objects
      //initParticles();
      var a = prepareParticleGeometry($scope.particles.count);
      particleCloud = a.particleCloud;
      particleCloud.y = 800;
      attributes = a.attributes;
      uniforms = a.uniforms;
      particles = a.particles;
      Pool = a.Pool;

      scene.add(particleCloud);
      initEmitters();
    };

    var drift = function(base, distance) {
      var r = Math.random() * 2-1;
      return base + Math.floor(r * distance);
    };

    var initEmitters = function() {
      var vertices = particleCloud.geometry.vertices;
      var values_size = attributes.size.value;
      var values_color = attributes.pcolor.value;

      var hue = 0;

      var setTargetParticle = function() {
        var target = Pool.get();
        values_size[ target ] = Math.random() * 2000 + 100;
        return target;
      };

      var onParticleCreated = function( p ) {
        var position = p.position;
        p.target.position = position;
        var target = p.target;

        if ( target ) {
          hue += 0.003 * 1.0;
          if ( hue > 1 ) hue -= 1;

          particles.vertices[ target ] = p.position;

          values_color[ target ].setHSL( hue, 0.6, 0.1 );
        }
      };

      var onParticleDead = function( particle ) {
        var target = particle.target;
        if ( target ) {
          // Hide the particle
          attributes.pcolor.value[ target ].setRGB( 0, 0, 0 );
          particles.vertices[ target ].set( Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY );
          // Mark particle system as available by returning to pool
          Pool.add( particle.target );
        }
      };

      var emitter_position = new THREE.Vector3(0, 0, 0);
      emitter = new SPARKS.Emitter(new RythmCounter());
      addInitializer(emitter, function(emitter, particle) {
        var pos = emitter_position;
        var x = drift(pos.x, 100),
            y = drift(pos.y, 100),
            z = drift(pos.z, 100);
        particle.position.set(x, y, z);
      });

      emitter.addInitializer( new SPARKS.Lifetime( 3, 5 ));
      emitter.addInitializer( new SPARKS.Target( null, setTargetParticle ) );
      addInitializer(emitter, function(e, particle) {
        var d = Dancer.is_beat ? 200 : 200;
        //values_size[particle.target] = Math.random() * d + 100;
      });

      emitter.addInitializer( new SPARKS.Velocity( new SPARKS.PointZone( new THREE.Vector3( 0, -5, 1 ) ) ) );
      // TOTRY Set velocity to move away from centroid

      emitter.addAction( new SPARKS.Age() );
      emitter.addAction( new SPARKS.Accelerate( 0, 0, -50 ) );
      emitter.addAction( new SPARKS.Move() );
      emitter.addAction( new SPARKS.RandomDrift( 10000, 1000, 20000 ) );

      emitter.addCallback( 'created', onParticleCreated );
      emitter.addCallback( 'dead', onParticleDead );
      emitter.start();
    };

    var RythmCounter = function() {
      this.leftover = 0;
      this.rate = 10;
      this.updateEmitter = function(emitter, time) {
        var rate = Dancer.is_beat ? 50 : 1;
        var targetRelease = time * rate + this.leftover;
        var actualRelease = Math.floor(targetRelease);

        this.leftover = targetRelease - actualRelease;
        return actualRelease;
      };
    };

    $scope.render = function(renderer, time_delta) {
      particleCloud.geometry.verticesNeedUpdate = true;

      attributes.size.needsUpdate = true;
      attributes.pcolor.needsUpdate = true;
      renderer.render(scene, camera);
    };

  });<commit_msg>Particle animation tracks tempo (no of beats out of last 60 frames), animates accordingly<commit_after>'use strict';
/* global SPARKS */

(function() {
  var shaders = {
    vertex: [,
      'attribute float size;',
      'attribute vec3 pcolor;',
      'varying vec3 vColor;',
      'void main() {',
        'vColor = pcolor;',
        'vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
        'gl_PointSize = size * ( 200.0 / length( mvPosition.xyz ) );',
        'gl_Position = projectionMatrix * mvPosition;',
      '}'
    ].join('\n'),
    fragment: [
      'uniform sampler2D texture;',
      'varying vec3 vColor;',
      'void main() {',
        'vec4 outColor = texture2D( texture, gl_PointCoord );',
        'gl_FragColor = outColor * vec4( vColor, 1.0 );',
     ' }'
    ].join('\n')
  };

  function generateSprite() {
    var canvas = document.createElement( 'canvas' );
    canvas.width = 128;
    canvas.height = 128;

    var context = canvas.getContext( '2d' );
    context.beginPath();
    context.arc( 64, 64, 60, 0, Math.PI * 2, false) ;

    context.lineWidth = 0.5; //0.05
    context.stroke();
    context.restore();

    var gradient = context.createRadialGradient( canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2 );

    gradient.addColorStop( 0, 'rgba(255,255,255,1)' );
    gradient.addColorStop( 0.2, 'rgba(255,255,255,1)' );
    gradient.addColorStop( 0.4, 'rgba(200,200,200,1)' );
    gradient.addColorStop( 1, 'rgba(0,0,0,1)' );

    context.fillStyle = gradient;

    context.fill();

    return canvas;

  }

  var Pool = {
    __pools: [],
    // Get a new Vector
    get: function() {
      if ( this.__pools.length > 0 ) {
        return this.__pools.pop();
      }
      console.error( "pool ran out!");
    },
    // Release a vector back into the pool
    add: function( v ) {
      this.__pools.push( v );
    }
  };

  var updaterObject = function(updater_func, update_type) {
    var Obj = function() {
      this[update_type] = updater_func;
    };
    return new Obj();
  };

  var addInitializer = function(emitter, init_func) {
    emitter.addInitializer(updaterObject(init_func, 'initialize'));
  };
  var addAction = function(emitter, init_func) {
    emitter.addAction(updaterObject(init_func, 'update'));
  };

  // returns an object { particleCloud, attributes, uniforms, particles }
  var prepareParticleGeometry = function(particle_count) {
    // sets up three.js side of things, the passing of values to shader.
    var particles = new THREE.Geometry();
    for (var i = 0; i < particle_count; i++) {
      particles.vertices.push(new THREE.Vector3(0, 0, 0));
      Pool.add(i);
    }

    var attributes = {
      size:  { type: 'f', value: [] },
      pcolor: { type: 'c', value: [] }
    };

    var sprite = generateSprite() ;

    var texture = new THREE.Texture( sprite );
    texture.needsUpdate = true;

    var uniforms = {
      texture:   { type: 't', value: texture }
    };
    var shaderMaterial = new THREE.ShaderMaterial( {
      uniforms: uniforms,
      attributes: attributes,
      vertexShader: shaders.vertex,
      fragmentShader: shaders.fragment,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      transparent: true
    });

    var particleCloud = new THREE.ParticleSystem( particles, shaderMaterial );

    particleCloud.dynamic = true;

    var vertices = particleCloud.geometry.vertices;
    var values_size = attributes.size.value;
    var values_color = attributes.pcolor.value;

    for (var v = 0; v < vertices.length; v++) {
      values_size[ v ] = 50;
      values_color[ v ] = new THREE.Color( 0x000000 );
      particles.vertices[ v ].set( Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY );
    }

    return {
      attributes: attributes,
      uniforms: uniforms,
      particleCloud: particleCloud,
      particles: particles,
      Pool: Pool
    };
  };

  window.prepareParticleGeometry = prepareParticleGeometry;
  window.addAction = addAction;
  window.addInitializer = addInitializer;
})();

angular.module('audioVizApp')
  .service('BeatCounter', function(Dancer) {
    return function(length) {
      var cache = {}, at = 0, count = 0, done = false;
      var result = {};
      result.tick = function() {
        var beat = Dancer.is_beat;
        if (beat) count++;
        if (done && cache[at])
          count--;
        cache[at] = Dancer.is_beat;
        at = (at+1) % length;
        done = done || (at == 0);
      };
      result.count = function() {
        return count;
      };
      result.percentage = function() {
        if (!done) return count / at;
        return count / length;
      };

      return result;
    };
  })
  .controller('Particles', function($scope, AudioService, Dancer, BeatCounter) {
    $scope.Dancer = Dancer;

    $scope.camera = {
      fov: 45,
      near: 1,
      far: 5000
    };

    $scope.particles = {
      count: 75000
    };

    var scene, camera, attributes, uniforms, particleCloud, particles, Pool, beat_counter;
    var emitter, pointLight, emitter_position;
    $scope.scene_init = function(renderer, width, height) {
      beat_counter = BeatCounter(60);
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 2000 );
      camera.position.set( 0, 150, 400 );
      camera.lookAt( scene.position );
      renderer.setClearColor(0x000000);

      var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
      directionalLight.position.set( 0, -1, 1 );
      directionalLight.position.normalize();
      scene.add( directionalLight );

      pointLight = new THREE.PointLight( 0xffffff, 2, 300 );
      pointLight.position.set( 0, 0, 0 );
      scene.add( pointLight );

      // Particle objects
      //initParticles();
      var a = prepareParticleGeometry($scope.particles.count);
      particleCloud = a.particleCloud;
      particleCloud.y = 800;
      attributes = a.attributes;
      uniforms = a.uniforms;
      particles = a.particles;
      Pool = a.Pool;

      scene.add(particleCloud);
      initEmitters();
    };

    var drift = function(base, distance) {
      var r = Math.random() * 2-1;
      return base + Math.floor(r * distance);
    };

    var initEmitters = function() {
      var vertices = particleCloud.geometry.vertices;
      var values_size = attributes.size.value;
      var values_color = attributes.pcolor.value;

      var hue = 0;

      var setTargetParticle = function() {
        var target = Pool.get();
        values_size[ target ] = Math.random() * 2000 + 100;
        return target;
      };

      var onParticleCreated = function( p ) {
        var position = p.position;
        p.target.position = position;
        var target = p.target;

        if ( target ) {
          hue += 0.009 * 1.0;
          if ( hue > 1 ) hue -= 1;

          particles.vertices[ target ] = p.position;

          values_color[ target ].setHSL( hue, 0.6, 0.1 );
        }
      };

      var onParticleDead = function( particle ) {
        var target = particle.target;
        if ( target ) {
          // Hide the particle
          attributes.pcolor.value[ target ].setRGB( 0, 0, 0 );
          particles.vertices[ target ].set( Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY );
          // Mark particle system as available by returning to pool
          Pool.add( particle.target );
        }
      };

      var emitter_position = new THREE.Vector3(0, 0, 0);
      emitter = new SPARKS.Emitter(new RythmCounter());
      addInitializer(emitter, function(emitter, particle) {
        var pos = emitter_position;
        var x = drift(pos.x, 100),
            y = drift(pos.y, 100),
            z = drift(pos.z, 100);
        particle.position.set(x, y, z);
      });

      emitter.addInitializer( new SPARKS.Lifetime( 3, 10 ));
      emitter.addInitializer( new SPARKS.Target( null, setTargetParticle ) );
      addInitializer(emitter, function(e, particle) {
        var d = Dancer.is_beat ? 200 : 200;
        //values_size[particle.target] = Math.random() * d + 100;
        var drift = new THREE.Vector3(1000, 1000, 1000);
        particle.acceleration = new THREE.Vector3(
          2*(Math.random() - 0.5), 2*(Math.random() - 0.5), 2*(Math.random() - 0.5)
        );
        particle.acceleration.multiply(drift);
      });

      emitter.addInitializer( new SPARKS.Velocity(
        new SPARKS.PointZone( new THREE.Vector3( 0, -5, 1 ) ) ) );
      // TOTRY Set velocity to move away from centroid

      emitter.addAction( new SPARKS.Age() );
      addAction(emitter, function(e, particle, time) {
        var acc = particle.acceleration;

        var v = particle.velocity;

        particle._oldvelocity.set(v.x, v.y, v.z);

        v.x += acc.x * time;
        v.y += acc.y * time;
        v.z += acc.z * time;
      });

      //emitter.addAction( new SPARKS.Accelerate( 0, 0, 50 ) );
      emitter.addAction( new SPARKS.Move() );
      addAction(emitter, function(e, particle, time) {
        var p = beat_counter.percentage(),
            v = particle.velocity,
            new_speed = p * 3000 + 100;
        v.setLength(new_speed);
      });
      emitter.addAction( new SPARKS.RandomDrift( 100, 100, 100 ) );

      emitter.addCallback( 'created', onParticleCreated );
      emitter.addCallback( 'dead', onParticleDead );
      emitter.start();
    };

    var RythmCounter = function() {
      this.leftover = 0;
      this.rate = 10;
      this.updateEmitter = function(emitter, time) {
        var rate = beat_counter.percentage() * 40 + 3;
        if (Dancer.is_beat) rate += 30;
        var targetRelease = time * rate + this.leftover;
        var actualRelease = Math.floor(targetRelease);

        this.leftover = targetRelease - actualRelease;
        return actualRelease;
      };
    };

    $scope.render = function(renderer, time_delta) {
      beat_counter.tick();

      particleCloud.geometry.verticesNeedUpdate = true;

      attributes.size.needsUpdate = true;
      attributes.pcolor.needsUpdate = true;
      renderer.render(scene, camera);
    };

  });<|endoftext|>


<commit_before>var express = require('express');
var request = require('superagent');
var dbUtils = require('../utils/dbUtils');
var qiniuUtils = require('../utils/qiniuUtils');
var resolutions = require('../configs/config').resolutions;
var cookie = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' };

var router = express.Router();

/**
 * v1 默认返回最新的bing壁纸信息
 */
router.get('/', function(req, res, next) {
    v1(req, res, next);
});
router.post('/', function(req, res, next) {
    v1(req, res, next);
});

var v1 = function(req, res, next) {
    var d = req.query.d || req.body.d;
    var w = req.query.w || req.body.w;
    var h = req.query.h || req.body.h;
    var size = w + 'x' + h;
    var enddate = 0;
    if (!isNaN(d)) {
        var date = new Date().getTime() - parseInt(d) * 1000 * 60 * 60 * 24;
        var newDate = new Date(date);
        enddate = newDate.getFullYear() + '' + ((newDate.getMonth() + 1) > 9 ? (newDate.getMonth() + 1) : '0' + (newDate.getMonth() + 1)) + '' + (newDate.getDate() > 9 ? newDate.getDate() : '0' + newDate.getDate());
    }
    var params = {
        page: {
            no: 1,
            size: 1
        },
        body: {}
    };
    if (!!enddate) {
        params.body = {
            enddate: enddate
        }
    }
    dbUtils.get('bing', params, function(rows) {
        if (rows.length > 0) {
            var data = rows[0];
            if (!!w || !!h) {
                if (resolutions.indexOf(size) > -1) {
                    data['url'] = 'http://images.ioliu.cn/bing/' + data.qiniu_url + '_' + size + '.jpg';
                }
                var qiniu_url = /images\.ioliu\.cn/.test(data.url) ? data.url : qiniuUtils.imageView(data.qiniu_url, w, h);
                request.get(qiniu_url)
                    .set(cookie)
                    .end(function(err, response) {
                        res.header('content-type', 'image/jpg');
                        res.send(response.body);
                    });
            } else {
                var output = {
                    data: data,
                    status: {
                        code: 200,
                        message: ''
                    }
                };
                if (req.method === 'GET' && req.query.callback) {
                    res.jsonp(output);
                } else {
                    res.json(output);
                }
            }
        } else {
            res.json({
                data: {},
                status: {
                    code: -1,
                    message: '好厉害啊，这么隐秘的地方都被你发现了n(*≧▽≦*)n，我已经通知主人咯♪(＾∀＾●)ﾉ，请您稍后再来试一下吧(❤´艸｀❤)!'
                }
            });
        }
    });
}

/**
 * 获得随机图片
 */
router.get('/rand', function(req, res, next) {
    random(req, res, next);
});
router.post('/rand', function(req, res, next) {
    random(req, res, next);
});

var random = function(req, res, next) {
    var t = req.query.type || req.body.type;
    var w = req.query.w || req.body.w || '1920';
    var h = req.query.h || req.body.h || '1080';
    var size = w + 'x' + h;
    var callback = req.query.callback || req.body.callback;
    dbUtils.getCount('bing', {}, function(rows) {
        if (rows.length > 0) {
            var sum = Number(rows[0].sum);
            var rand = Math.floor(Math.random() * (sum - 1) + 1);
            dbUtils.get('bing', {
                id: rand
            }, function(rs) {
                if (rs.length > 0) {
                    var data = rs[0];
                    if (resolutions.indexOf(size) > -1) {
                        data['url'] = 'http://images.ioliu.cn/bing/' + data.qiniu_url + '_' + size + '.jpg';
                    }
                    if (t === 'json' || !!callback) {
                        console.log(callback);
                        var output = {
                            data: data,
                            status: {
                                code: 200,
                                message: ''
                            }
                        };
                        if (callback) {
                            res.jsonp(output);
                        } else {
                            res.json(output);
                        }
                    } else {
                        var qiniu_url = /images\.ioliu\.cn/.test(data.url) ? data.url : qiniuUtils.imageView(data.qiniu_url, w, h);
                        request.get(qiniu_url)
                            .set(cookie)
                            .end(function(err, response) {
                                res.header('content-type', 'image/jpg');
                                res.send(response.body);
                            });
                    }
                } else {
                    var parmas = '?';
                    params += !!t ? '&t=' + t : '';
                    params += !!w ? '&w=' + w : '';
                    params += !!h ? '&h=' + h : '';
                    params += !!callback ? '&&callback=' + callback : '';
                    res.redirect('/v1/rand' + params);
                }
            });
        } else {
            var parmas = '?';
            params += !!t ? '&t=' + t : '';
            params += !!w ? '&w=' + w : '';
            params += !!h ? '&h=' + h : '';
            params += !!callback ? '&&callback=' + callback : '';
            res.redirect('/v1/rand' + params);
        }
    });
}

/**
 * 获取高斯模糊图片
 */
router.get('/blur', function(req, res, next) {
    blur(req, res, next);
});
router.post('/blur', function(req, res, next) {
    blur(req, res, next);
});

var blur = function(req, res, next) {
    var d = req.query.d || req.body.d;
    var w = req.query.w || req.body.w;
    var h = req.query.h || req.body.h;
    var r = req.query.r || req.body.r;
    r = isNaN(r) ? 10 : parseInt(r) > 50 ? 50 : parseInt(r) <= 0 ? 1 : r;
    var size = w + 'x' + h;
    var enddate = 0;
    if (!isNaN(d)) {
        var date = new Date().getTime() - parseInt(d) * 1000 * 60 * 60 * 24;
        var newDate = new Date(date);
        enddate = newDate.getFullYear() + '' + ((newDate.getMonth() + 1) > 10 ? (newDate.getMonth() + 1) : '0' + (newDate.getMonth() + 1)) + '' + (newDate.getDate() > 10 ? newDate.getDate() : '0' + newDate.getDate());
    }
    var params = {
        page: {
            no: 1,
            size: 1
        },
        body: {}
    };
    if (!!enddate) {
        params.body = {
            enddate: enddate
        }
    }
    dbUtils.get('bing', params, function(rows) {
        if (rows.length > 0) {
            var data = rows[0];
            var base = 'http://images.ioliu.cn/bing/';
            if (resolutions.indexOf(size) > -1) {
                data['url'] = base + data.qiniu_url + '_' + size + '.jpg';
            }
            var qiniu_url = /images\.ioliu\.cn/.test(data.url) ? data.url : base + data.qiniu_url + '_1920x1080.jpg';
            qiniu_url += '?imageMogr2/blur/' + r + 'x50'
            request.get(qiniu_url)
                .set(cookie)
                .end(function(err, response) {
                    res.header('content-type', 'image/jpg');
                    res.send(response.body);
                });
        } else {
            res.json({
                data: {},
                status: {
                    code: -1,
                    message: '好厉害啊，这么隐秘的地方都被你发现了n(*≧▽≦*)n，我已经通知主人咯♪(＾∀＾●)ﾉ，请您稍后再来试一下吧(❤´艸｀❤)!'
                }
            });
        }
    });
}

module.exports = router;<commit_msg>add new interface<commit_after>var express = require('express');
var request = require('superagent');
var dbUtils = require('../utils/dbUtils');
var qiniuUtils = require('../utils/qiniuUtils');
var resolutions = require('../configs/config').resolutions;
var cookie = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' };

var router = express.Router();

/**
 * v1 默认返回最新的bing壁纸信息
 */
router.get('/', function(req, res, next) {
    v1(req, res, next);
});
router.post('/', function(req, res, next) {
    v1(req, res, next);
});

var v1 = function(req, res, next) {
    var d = req.query.d || req.body.d;
    var w = req.query.w || req.body.w;
    var h = req.query.h || req.body.h;
    var size = w + 'x' + h;
    var enddate = 0;
    if (!isNaN(d)) {
        var date = new Date().getTime() - parseInt(d) * 1000 * 60 * 60 * 24;
        var newDate = new Date(date);
        enddate = newDate.getFullYear() + '' + ((newDate.getMonth() + 1) > 9 ? (newDate.getMonth() + 1) : '0' + (newDate.getMonth() + 1)) + '' + (newDate.getDate() > 9 ? newDate.getDate() : '0' + newDate.getDate());
    }
    var params = {
        page: {
            no: 1,
            size: 1
        },
        body: {}
    };
    if (!!enddate) {
        params.body = {
            enddate: enddate
        }
    }
    dbUtils.get('bing', params, function(rows) {
        if (rows.length > 0) {
            var data = rows[0];
            if (!!w || !!h) {
                if (resolutions.indexOf(size) > -1) {
                    data['url'] = 'http://images.ioliu.cn/bing/' + data.qiniu_url + '_' + size + '.jpg';
                }
                var qiniu_url = /images\.ioliu\.cn/.test(data.url) ? data.url : qiniuUtils.imageView(data.qiniu_url, w, h);
                request.get(qiniu_url)
                    .set(cookie)
                    .end(function(err, response) {
                        res.header('content-type', 'image/jpg');
                        res.send(response.body);
                    });
            } else {
                var output = {
                    data: data,
                    status: {
                        code: 200,
                        message: ''
                    }
                };
                if (req.method === 'GET' && req.query.callback) {
                    res.jsonp(output);
                } else {
                    res.json(output);
                }
            }
        } else {
            res.json({
                data: {},
                status: {
                    code: -1,
                    message: '好厉害啊，这么隐秘的地方都被你发现了n(*≧▽≦*)n，我已经通知主人咯♪(＾∀＾●)ﾉ，请您稍后再来试一下吧(❤´艸｀❤)!'
                }
            });
        }
    });
}

/**
 * 获得随机图片
 */
router.get('/rand', function(req, res, next) {
    random(req, res, next);
});
router.post('/rand', function(req, res, next) {
    random(req, res, next);
});

var random = function(req, res, next) {
    var t = req.query.type || req.body.type;
    var w = req.query.w || req.body.w || '1920';
    var h = req.query.h || req.body.h || '1080';
    var size = w + 'x' + h;
    var callback = req.query.callback || req.body.callback;
    dbUtils.getCount('bing', {}, function(rows) {
        if (rows.length > 0) {
            var sum = Number(rows[0].sum);
            var rand = Math.floor(Math.random() * (sum - 1) + 1);
            dbUtils.get('bing', {
                id: rand
            }, function(rs) {
                if (rs.length > 0) {
                    var data = rs[0];
                    if (resolutions.indexOf(size) > -1) {
                        data['url'] = 'http://images.ioliu.cn/bing/' + data.qiniu_url + '_' + size + '.jpg';
                    }
                    if (t === 'json' || !!callback) {
                        console.log(callback);
                        var output = {
                            data: data,
                            status: {
                                code: 200,
                                message: ''
                            }
                        };
                        if (callback) {
                            res.jsonp(output);
                        } else {
                            res.json(output);
                        }
                    } else {
                        var qiniu_url = /images\.ioliu\.cn/.test(data.url) ? data.url : qiniuUtils.imageView(data.qiniu_url, w, h);
                        request.get(qiniu_url)
                            .set(cookie)
                            .end(function(err, response) {
                                res.header('content-type', 'image/jpg');
                                res.send(response.body);
                            });
                    }
                } else {
                    var parmas = '?';
                    params += !!t ? '&t=' + t : '';
                    params += !!w ? '&w=' + w : '';
                    params += !!h ? '&h=' + h : '';
                    params += !!callback ? '&&callback=' + callback : '';
                    res.redirect('/v1/rand' + params);
                }
            });
        } else {
            var parmas = '?';
            params += !!t ? '&t=' + t : '';
            params += !!w ? '&w=' + w : '';
            params += !!h ? '&h=' + h : '';
            params += !!callback ? '&&callback=' + callback : '';
            res.redirect('/v1/rand' + params);
        }
    });
}

/**
 * 获取高斯模糊图片
 */
router.get('/blur', function(req, res, next) {
    blur(req, res, next);
});
router.post('/blur', function(req, res, next) {
    blur(req, res, next);
});

var blur = function(req, res, next) {
    var d = req.query.d || req.body.d;
    var w = req.query.w || req.body.w;
    var h = req.query.h || req.body.h;
    var r = req.query.r || req.body.r;
    r = isNaN(r) ? 10 : parseInt(r) > 50 ? 50 : parseInt(r) <= 0 ? 1 : r;
    var size = w + 'x' + h;
    var enddate = 0;
    if (!isNaN(d)) {
        var date = new Date().getTime() - parseInt(d) * 1000 * 60 * 60 * 24;
        var newDate = new Date(date);
        enddate = newDate.getFullYear() + '' + ((newDate.getMonth() + 1) > 9 ? (newDate.getMonth() + 1) : '0' + (newDate.getMonth() + 1)) + '' + (newDate.getDate() > 9 ? newDate.getDate() : '0' + newDate.getDate());
    }
    var params = {
        page: {
            no: 1,
            size: 1
        },
        body: {}
    };
    if (!!enddate) {
        params.body = {
            enddate: enddate
        }
    }
    dbUtils.get('bing', params, function(rows) {
        if (rows.length > 0) {
            var data = rows[0];
            var base = 'http://images.ioliu.cn/bing/';
            if (resolutions.indexOf(size) > -1) {
                data['url'] = base + data.qiniu_url + '_' + size + '.jpg';
            }
            var qiniu_url = /images\.ioliu\.cn/.test(data.url) ? data.url : base + data.qiniu_url + '_1920x1080.jpg';
            qiniu_url += '?imageMogr2/blur/' + r + 'x50'
            request.get(qiniu_url)
                .set(cookie)
                .end(function(err, response) {
                    res.header('content-type', 'image/jpg');
                    res.send(response.body);
                });
        } else {
            res.json({
                data: {},
                status: {
                    code: -1,
                    message: '好厉害啊，这么隐秘的地方都被你发现了n(*≧▽≦*)n，我已经通知主人咯♪(＾∀＾●)ﾉ，请您稍后再来试一下吧(❤´艸｀❤)!'
                }
            });
        }
    });
}

module.exports = router;<|endoftext|>


<commit_before>const Plugin = require("../Plugin");
const assert = require("assert");
const GoogleSearch = require("google-search");

module.exports = class GoogleImages extends Plugin {
    constructor(obj) {
        super(obj);

        assert("GOOGLE_API_KEY" in obj.config, "string", "You must supply a Google API key.");
        assert.notStrictEqual(obj.config.GOOGLE_API_KEY, "", "Please supply a valid Google API key.");
        assert("GOOGLE_CSE_ID" in obj.config, "string", "You must supply a Google CX key.");
        assert.notStrictEqual(obj.config.GOOGLE_CSE_ID, "", "Please supply a valid Google CX key.");

        this.client = new GoogleSearch({
            key: obj.config.GOOGLE_API_KEY,
            cx: obj.config.GOOGLE_CSE_ID
        });
    }

    static get plugin() {
        return {
            name: "GoogleImages",
            description: "Search for images on Google.",
            help: "/images query",
            needs: {
                config: {
                    GOOGLE_API_KEY: "Google API key",
                    GOOGLE_CSE_ID: "Google CSE ID"
                }
            }
        };
    }

    async onCommand({command, args}) {
        if (command !== "images") return;
        const query = args.join(" ");
        const response = await new Promise((resolve, reject) => this.client.build({q: query, searchtype: "image", num: 1}, (error, response) => error ? reject(error) : resolve(response)));
        if (!response.items)
            return "No results found.";
        const item = response.items[0];
        return {
            type: "photo",
            photo: item.pagemap.cse_image[0].src,
            options: {
                caption: `${item.snippet.replace(/\n/g, "")}`
            }
        };
    }
};<commit_msg>Fix GoogleImages<commit_after>const Plugin = require("../Plugin");
const assert = require("assert");
const GoogleSearch = require("google-search");

module.exports = class GoogleImages extends Plugin {
    constructor(obj) {
        super(obj);

        assert("GOOGLE_API_KEY" in obj.config, "string", "You must supply a Google API key.");
        assert.notStrictEqual(obj.config.GOOGLE_API_KEY, "", "Please supply a valid Google API key.");
        assert("GOOGLE_CSE_ID" in obj.config, "string", "You must supply a Google CX key.");
        assert.notStrictEqual(obj.config.GOOGLE_CSE_ID, "", "Please supply a valid Google CX key.");

        this.client = new GoogleSearch({
            key: obj.config.GOOGLE_API_KEY,
            cx: obj.config.GOOGLE_CSE_ID
        });
    }

    static get plugin() {
        return {
            name: "GoogleImages",
            description: "Search for images on Google.",
            help: "/images query",
            needs: {
                config: {
                    GOOGLE_API_KEY: "Google API key",
                    GOOGLE_CSE_ID: "Google CSE ID"
                }
            }
        };
    }

    async onCommand({command, args}) {
        if (command !== "images") return;
        const query = args.join(" ");
        const response = await new Promise((resolve, reject) => this.client.build({q: query, searchtype: "image", num: 1}, (error, response) => error ? reject(error) : resolve(response)));
        if ((!response.items) || (response.items.length === 0))
            return "No results found.";
        const item = response.items[0];
        return {
            type: "photo",
            photo: item.pagemap.cse_image[0].src,
            options: {
                caption: `${item.snippet.replace(/\n/g, "")}`
            }
        };
    }
};<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment-timezone';
import { Link } from 'react-router';
import _ from 'lodash';

import Separator from '../../components/Separator';

/* Render a single item
*/

class AdminEventListItem extends React.Component {
  static propTypes = {
    data: PropTypes.object.isRequired,
  };

  render() {
    return (

          <Link to={`/event/${this.props.data.id}`}>{this.props.data.title}</Link>

        {this.props.data.date ? moment(this.props.data.date).format('DD.MM.YYYY') : ''}
        Luonnos
        {_.sumBy(this.props.data.quota, n => n.going)}

          <Link to={`/admin/edit/${this.props.data.id}`}>Muokkaa tapahtumaa</Link>
          <Separator />
          <Link>Lataa osallistujalista</Link>

    );
  }
}

export default AdminEventListItem;
<commit_msg>Event 'tila' is now either draft or published in admin panel<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment-timezone';
import { Link } from 'react-router';
import _ from 'lodash';

import Separator from '../../components/Separator';

/* Render a single item
*/

class AdminEventListItem extends React.Component {
  static propTypes = {
    data: PropTypes.object.isRequired,
  };

  render() {
    return (

          <Link to={`/event/${this.props.data.id}`}>{this.props.data.title}</Link>

        {this.props.data.date ? moment(this.props.data.date).format('DD.MM.YYYY') : ''}
        {this.props.data.draft === 1 ? "Luonnos" : "Julkaistu"}
        {_.sumBy(this.props.data.quota, n => n.going)}

          <Link to={`/admin/edit/${this.props.data.id}`}>Muokkaa tapahtumaa</Link>
          <Separator />
          <Link>Lataa osallistujalista</Link>

    );
  }
}

export default AdminEventListItem;
<|endoftext|>


<commit_before>/**
 * Author: Jeff Whelpley
 * Date: 10/19/14
 *
 * Common functions used by all server plugins
 */
var Q        = require('q');
var _        = require('lodash');
var lruCache = require('lru-cache');
var utensils = require('./utensils');

var defaultPageCache = lruCache({ max: 100, maxAge: 60000 });
var routeInfoCache = {};
var appRouteCache = {};
var injector, clientPlugin, appConfigs;
var regexSlash = /\//g;
//var regexCapture = /\{.+}/g; // like {varName}

/**
 * Initialize the handler with the dependency injector
 * @param opts
 */
function init(opts) {
    injector = opts.injector;
    clientPlugin = opts.clientPlugin;
    appConfigs = injector.loadModule('appConfigs');
}

/**
 * Convert a segment of URL to a regex pattern for full URL casting below
 * @param segment
 * @returns string A segment as a URL pattern string
 */
function convertUrlSegmentToRegex(segment) {

    // if it has this format "{stuff}" - then it's regex-y
    var beginIdx = segment.indexOf('{');
    var endIdx = segment.indexOf('}');

    if (beginIdx < 0) {
        return segment;
    }

    // if capturing regex and trim trailing "}"
    // this could be a named regex {id:[0-9]+} or just name {companySlug}
    var pieces = segment.split(':');
    if (pieces.length === 2) {
        return '(' + pieces[1].substring(0, pieces[1].length - 1) + ')';
    }
    else if (pieces.length === 1) {
        return segment.substring(0, beginIdx) + '[^\\/]+' + segment.substring(endIdx + 1);
    }
    else {
        throw new Error('Weird URL segment- don\'t know how to parse! ' + segment);
    }
}

/***
 * Convert a pattern from the database into a legit regex pattern
 * @param pattern
 */
function convertUrlPatternToRegex(pattern) {

    // pattern = pattern.replace(new RegExp('{[a-zA-Z0-9\\-_~]*}', 'gi'), '[a-zA-Z0-9\\-_~]+');

    // let's try a different way...
    // first, ui-router doesn't allow use of fwd slash in URL patterns because it's THE separator:
    var segments = pattern.split('/');
    var convertedSegments = segments.map(function (segment) {
        return convertUrlSegmentToRegex(segment); // so let's just convert each segment separately
    });
    pattern = convertedSegments.join('/');

    pattern = pattern.replace(regexSlash, '\\/');
    pattern = '^' + pattern + '$';
    return new RegExp(pattern);
}

/**
 * Use a given pattern to extract tokens from a given URL
 * @param pattern
 * @param url
 * @returns {{}}
 */
function getTokenValuesFromUrl(pattern, url) {
    var tokenValues = {},
        urlSegments = url.split('/');

    pattern.split('/').forEach(function (segment, idx) {

        // this has this form {stuff}, so let's dig
        var beginIdx = segment.indexOf('{');
        var endIdx = segment.indexOf('}');
        var endLen = segment.length - endIdx - 1;
        var urlSegment = urlSegments[idx];

        if (beginIdx > -1) {

            // peel off the { and } at the ends
            segment = segment.substring(beginIdx + 1, endIdx);

            var pieces = segment.split(':');
            if (pieces.length === 2 || pieces.length === 1) {
                tokenValues[pieces[0]] = urlSegment.substring(beginIdx, urlSegment.length - endLen);
            }
        } // else no token to grab
    });

    return tokenValues;
}

/**
 * Get array of routes for app. This is essentially information from
 * the main app config file that is the root dir of each app (i.e. in
 * the {projectRoot}/app/{appName}/{appName}.app.js file)
 *
 * @param appName
 * @returns [] Array of routeInfo objects
 */
function getRoutes(appName) {
    if (appRouteCache[appName]) { return appRouteCache[appName]; }      // if route info already cached, return that

    var appConfig = appConfigs[appName];
    var routes = [];

    _.each(appConfig.routes, function (route) {                 // loop through routes in the .app file
        _.each(route.urls, function (urlPattern) {              // create separate routeInfo for each URL

            var urlRegex = convertUrlPatternToRegex(urlPattern);
            routes.push(_.extend({
                urlPattern:     urlPattern,
                urlRegex:       urlRegex,
                layout:         route.layout || appConfig.defaultLayout || appName,
                wrapper:        route.wrapper || appConfig.defaultWrapper || 'server.page',
                strip:          route.strip || appConfig.defaultStrip || true,
                contentType:    route.contentType,
                data:           route.data || appConfig.data,
                serverOnly:     appConfig.serverOnly
            }, route));
        });
    });

    appRouteCache[appName] = routes;
    return routes;
}

/**
 * Get info for particular route
 * @param appName
 * @param urlRequest
 * @param query
 * @param lang
 * @returns {{}} The routeInfo for a particular request
 */
function getRouteInfo(appName, urlRequest, query, lang) {

    // if route info already in cache, return it
    var cacheKey = appName + '||' + lang + '||' + urlRequest;
    var cachedRouteInfo = routeInfoCache[cacheKey];
    if (cachedRouteInfo) {
        cachedRouteInfo.query = query;  // query shouldn't be cached
        return cachedRouteInfo;
    }

    // get the routes and then find the info that matches the current URL
    var url = urlRequest.toLowerCase();
    var i, route, routeInfo;
    var routes = getRoutes(appName);
    if (routes) {

        // loop through routes trying to find the one that matches
        for (i = 0; i < routes.length; i++) {
            route = routes[i];

            // if there is a match, save the info to cache and return it
            if (route.urlRegex.test(url)) {
                routeInfo = _.extend({
                    appName:    appName,
                    lang:       lang,
                    url:        urlRequest,
                    query:      query,
                    tokens:     getTokenValuesFromUrl(route.urlPattern, urlRequest)
                }, route);

                routeInfoCache[cacheKey] = routeInfo;
                return routeInfo;
            }
        }
    }

    // if we get here, then no route found, so throw 404 error
    throw new Error('404: ' + appName + ' ' + urlRequest + ' is not a valid request');
}

/**
 * If a default value doesn't exist in the model, set it
 * @param model
 * @param defaults
 */
function setDefaults(model, defaults) {
    if (!defaults) { return; }

    _.each(defaults, function (value, key) {
        if (model[key] === undefined) {
            model[key] = value;
        }
    });
}

/**
 * Get the initial model for a given page
 * @param routeInfo
 * @param page
 */
function getInitialModel(routeInfo, page) {
    var initModelDeps = {
        appName:    routeInfo.appName,
        tokens:     routeInfo.tokens,
        routeInfo:  routeInfo,
        defaults:   page.defaults,
        currentScope: {}
    };

    // if no model, just return empty object
    if (!page.model) {
        return Q.when({});
    }
    // if function, inject and the returned value is the model
    else if (_.isFunction(page.model)) {
        return Q.when(injector.loadModule(page.model, null, { dependencies: initModelDeps }));
    }
    else {
        throw new Error(routeInfo.name + ' page invalid model() format: ' + page.model);
    }
}

/**
 * This function is called by the server plugin when we have a request and we need to
 * process it. The plugin should handle translating the server platform specific values
 * into our routeInfo
 *
 * @param routeInfo
 * @param callbacks
 */
function processWebRequest(routeInfo, callbacks) {
    var appName = routeInfo.appName;
    var serverOnly = !!routeInfo.query.server;
    var page = injector.loadModule('app/' + appName + '/pages/' + routeInfo.name + '.page');

    // get the callbacks
    var serverPreprocessing = callbacks.serverPreprocessing || function () { return false; };
    var appAddToModel = callbacks.addToModel || function () {};
    var pageCacheService = callbacks.pageCacheService || defaultPageCache;
    var initialModel, cacheKey;

    return getInitialModel(routeInfo, page)
        .then(function (model) {
            initialModel = model || {};

            // if the server pre-processing returns true, then return without doing anything
            // this is because the pre-processor sent a reply to the user already
            if (serverPreprocessing(routeInfo, page, model)) {
                return true;
            }

            //TODO: may be weird edge cases with this. need to test more to ensure performance and uniqueness
            // but note that we have an hour default timeout for the redis cache just in case
            // and we have the redis LRU config set up so the least recently used will get pushed out
            cacheKey = utensils.checksum(routeInfo.lang + '||' + routeInfo.appName + '||' +
                routeInfo.url + '||' + JSON.stringify(model));

            return pageCacheService.get({ key: cacheKey });
        })
        .then(function (cachedPage) {
            if (cachedPage === true) { return null; }
            if (cachedPage && !serverOnly) { return cachedPage; }

            // allow the app level to modify the model before rendering
            appAddToModel(initialModel, routeInfo);

            // finally use the client side plugin to do the rendering
            return clientPlugin.renderPage(routeInfo, page, initialModel);
        })
        .then(function (renderedPage) {
            if (!serverOnly) {
                pageCacheService.set({ key: cacheKey, value: renderedPage });
            }

            return renderedPage;
        });
}

// expose functions for testing
module.exports = {
    init: init,
    getRouteInfo: getRouteInfo,
    getRoutes: getRoutes,
    getTokenValuesFromUrl: getTokenValuesFromUrl,
    convertUrlPatternToRegex: convertUrlPatternToRegex,
    processWebRequest: processWebRequest,
    setDefaults: setDefaults,
    getInitialModel: getInitialModel
};
<commit_msg>allow special case tail url matching pattern with .*<commit_after>/**
 * Author: Jeff Whelpley
 * Date: 10/19/14
 *
 * Common functions used by all server plugins
 */
var Q        = require('q');
var _        = require('lodash');
var lruCache = require('lru-cache');
var utensils = require('./utensils');

var defaultPageCache = lruCache({ max: 100, maxAge: 60000 });
var routeInfoCache = {};
var appRouteCache = {};
var injector, clientPlugin, appConfigs;
var regexSlash = /\//g;
var urlPathPattern = '([\\w#!:.?+=&%@!\\-\\/]+)';
var urlPathPatternSymbol = '.*';
//var regexCapture = /\{.+}/g; // like {varName}

/**
 * Initialize the handler with the dependency injector
 * @param opts
 */
function init(opts) {
    injector = opts.injector;
    clientPlugin = opts.clientPlugin;
    appConfigs = injector.loadModule('appConfigs');
}

/**
 * Convert a segment of URL to a regex pattern for full URL casting below
 * @param segment
 * @returns string A segment as a URL pattern string
 */
function convertUrlSegmentToRegex(segment) {

    // if it has this format "{stuff}" - then it's regex-y
    var beginIdx = segment.indexOf('{');
    var endIdx = segment.indexOf('}');

    if (beginIdx < 0) {
        return segment;
    }

    // if capturing regex and trim trailing "}"
    // this could be a named regex {id:[0-9]+} or just name {companySlug}
    var pieces = segment.split(':');
    if (pieces.length === 2) {
        if ( pieces[1] === (urlPathPatternSymbol + '}') ) {
            // special case where we want to capture the whole remaining path, even slashes, like /search/a/b/c/
            return urlPathPattern;
        }
        else {
            return '(' + pieces[1].substring(0, pieces[1].length - 1) + ')';
        }
    }
    else if (pieces.length === 1) {
        return segment.substring(0, beginIdx) + '[^\\/]+' + segment.substring(endIdx + 1);
    }
    else {
        throw new Error('Weird URL segment- don\'t know how to parse! ' + segment);
    }
}

/***
 * Convert a pattern from the database into a legit regex pattern
 * @param pattern
 */
function convertUrlPatternToRegex(pattern) {

    // pattern = pattern.replace(new RegExp('{[a-zA-Z0-9\\-_~]*}', 'gi'), '[a-zA-Z0-9\\-_~]+');

    // let's try a different way...
    // first, ui-router doesn't allow use of fwd slash in URL patterns because it's THE separator:

    // however. we /do/ sometimes want to be able to capture the rest of a huge url, at least on the server-side
    // case & point, old urls that point to /search/stuff/morestuff/otherstuff
    // so now we have to respect a trailing /search/{term:*}, which just grabs the whole damn tail

    var segments = pattern.split('/');
    var convertedSegments = [];
    for ( var i = 0, len = segments.length; i < len; i++ ) {
        var converted = convertUrlSegmentToRegex(segments[i]);
        convertedSegments.push(converted);
        if ( converted === urlPathPattern ) {
            // short-circuit out of this- we mean to capture the rest of the url with this...
            break;
        }
    }
    // more clever, but since we sometimes want to short-circuit, we use the loop instead
    /*
    var convertedSegments = segments.map(function (segment) {
        return convertUrlSegmentToRegex(segment); // so let's just convert each segment separately
    });
    */
    pattern = convertedSegments.join('/');

    pattern = pattern.replace(regexSlash, '\\/');
    pattern = '^' + pattern + '$';
    return new RegExp(pattern);
}

/**
 * Use a given pattern to extract tokens from a given URL
 * @param pattern
 * @param url
 * @returns {{}}
 */
function getTokenValuesFromUrl(pattern, url) {
    var tokenValues = {},
        urlSegments = url.split('/');

    pattern.split('/').forEach(function (segment, idx) {

        // this has this form {stuff}, so let's dig
        var beginIdx = segment.indexOf('{');
        var endIdx = segment.indexOf('}');
        var endLen = segment.length - endIdx - 1;
        var urlSegment = urlSegments[idx];

        if (beginIdx > -1) {

            // peel off the { and } at the ends
            segment = segment.substring(beginIdx + 1, endIdx);

            var pieces = segment.split(':');
            if (pieces.length === 2 && pieces[1] === urlPathPatternSymbol ) {
                var vals = [urlSegment.substring(beginIdx, urlSegment.length - endLen)];
                for ( var i = idx + 1, len = urlSegments.length; i < len; i++ ) {
                    vals.push(urlSegments[i]);
                }
                tokenValues[pieces[0]] = vals.join('%20'); // join any remaining segments with a space
            }
            else if (pieces.length === 2 || pieces.length === 1) {
                tokenValues[pieces[0]] = urlSegment.substring(beginIdx, urlSegment.length - endLen);
            }
        } // else no token to grab
    });

    return tokenValues;
}

/**
 * Get array of routes for app. This is essentially information from
 * the main app config file that is the root dir of each app (i.e. in
 * the {projectRoot}/app/{appName}/{appName}.app.js file)
 *
 * @param appName
 * @returns [] Array of routeInfo objects
 */
function getRoutes(appName) {
    if (appRouteCache[appName]) { return appRouteCache[appName]; }      // if route info already cached, return that

    var appConfig = appConfigs[appName];
    var routes = [];

    _.each(appConfig.routes, function (route) {                 // loop through routes in the .app file
        _.each(route.urls, function (urlPattern) {              // create separate routeInfo for each URL

            var urlRegex = convertUrlPatternToRegex(urlPattern);
            routes.push(_.extend({
                urlPattern:     urlPattern,
                urlRegex:       urlRegex,
                layout:         route.layout || appConfig.defaultLayout || appName,
                wrapper:        route.wrapper || appConfig.defaultWrapper || 'server.page',
                strip:          route.strip || appConfig.defaultStrip || true,
                contentType:    route.contentType,
                data:           route.data || appConfig.data,
                serverOnly:     appConfig.serverOnly
            }, route));
        });
    });

    appRouteCache[appName] = routes;
    return routes;
}

/**
 * Get info for particular route
 * @param appName
 * @param urlRequest
 * @param query
 * @param lang
 * @returns {{}} The routeInfo for a particular request
 */
function getRouteInfo(appName, urlRequest, query, lang) {

    // if route info already in cache, return it
    var cacheKey = appName + '||' + lang + '||' + urlRequest;
    var cachedRouteInfo = routeInfoCache[cacheKey];
    if (cachedRouteInfo) {
        cachedRouteInfo.query = query;  // query shouldn't be cached
        return cachedRouteInfo;
    }

    // get the routes and then find the info that matches the current URL
    var url = urlRequest.toLowerCase();
    var i, route, routeInfo;
    var routes = getRoutes(appName);
    if (routes) {

        // loop through routes trying to find the one that matches
        for (i = 0; i < routes.length; i++) {
            route = routes[i];

            // if there is a match, save the info to cache and return it
            if (route.urlRegex.test(url)) {
                routeInfo = _.extend({
                    appName:    appName,
                    lang:       lang,
                    url:        urlRequest,
                    query:      query,
                    tokens:     getTokenValuesFromUrl(route.urlPattern, urlRequest)
                }, route);

                routeInfoCache[cacheKey] = routeInfo;
                return routeInfo;
            }
        }
    }

    // if we get here, then no route found, so throw 404 error
    throw new Error('404: ' + appName + ' ' + urlRequest + ' is not a valid request');
}

/**
 * If a default value doesn't exist in the model, set it
 * @param model
 * @param defaults
 */
function setDefaults(model, defaults) {
    if (!defaults) { return; }

    _.each(defaults, function (value, key) {
        if (model[key] === undefined) {
            model[key] = value;
        }
    });
}

/**
 * Get the initial model for a given page
 * @param routeInfo
 * @param page
 */
function getInitialModel(routeInfo, page) {
    var initModelDeps = {
        appName:    routeInfo.appName,
        tokens:     routeInfo.tokens,
        routeInfo:  routeInfo,
        defaults:   page.defaults,
        currentScope: {}
    };

    // if no model, just return empty object
    if (!page.model) {
        return Q.when({});
    }
    // if function, inject and the returned value is the model
    else if (_.isFunction(page.model)) {
        return Q.when(injector.loadModule(page.model, null, { dependencies: initModelDeps }));
    }
    else {
        throw new Error(routeInfo.name + ' page invalid model() format: ' + page.model);
    }
}

/**
 * This function is called by the server plugin when we have a request and we need to
 * process it. The plugin should handle translating the server platform specific values
 * into our routeInfo
 *
 * @param routeInfo
 * @param callbacks
 */
function processWebRequest(routeInfo, callbacks) {
    var appName = routeInfo.appName;
    var serverOnly = !!routeInfo.query.server;
    var page = injector.loadModule('app/' + appName + '/pages/' + routeInfo.name + '.page');

    // get the callbacks
    var serverPreprocessing = callbacks.serverPreprocessing || function () { return false; };
    var appAddToModel = callbacks.addToModel || function () {};
    var pageCacheService = callbacks.pageCacheService || defaultPageCache;
    var initialModel, cacheKey;

    return getInitialModel(routeInfo, page)
        .then(function (model) {
            initialModel = model || {};

            // if the server pre-processing returns true, then return without doing anything
            // this is because the pre-processor sent a reply to the user already
            if (serverPreprocessing(routeInfo, page, model)) {
                return true;
            }

            //TODO: may be weird edge cases with this. need to test more to ensure performance and uniqueness
            // but note that we have an hour default timeout for the redis cache just in case
            // and we have the redis LRU config set up so the least recently used will get pushed out
            cacheKey = utensils.checksum(routeInfo.lang + '||' + routeInfo.appName + '||' +
                routeInfo.url + '||' + JSON.stringify(model));

            return pageCacheService.get({ key: cacheKey });
        })
        .then(function (cachedPage) {
            if (cachedPage === true) { return null; }
            if (cachedPage && !serverOnly) { return cachedPage; }

            // allow the app level to modify the model before rendering
            appAddToModel(initialModel, routeInfo);

            // finally use the client side plugin to do the rendering
            return clientPlugin.renderPage(routeInfo, page, initialModel);
        })
        .then(function (renderedPage) {
            if (!serverOnly) {
                pageCacheService.set({ key: cacheKey, value: renderedPage });
            }

            return renderedPage;
        });
}

// expose functions for testing
module.exports = {
    init: init,
    getRouteInfo: getRouteInfo,
    getRoutes: getRoutes,
    getTokenValuesFromUrl: getTokenValuesFromUrl,
    convertUrlPatternToRegex: convertUrlPatternToRegex,
    processWebRequest: processWebRequest,
    setDefaults: setDefaults,
    getInitialModel: getInitialModel
};
<|endoftext|>