<commit_before>import {
  COMPOSE_MOUNT,
  COMPOSE_UNMOUNT,
  COMPOSE_CHANGE,
  COMPOSE_REPLY,
  COMPOSE_REPLY_CANCEL,
  COMPOSE_MENTION,
  COMPOSE_SUBMIT_REQUEST,
  COMPOSE_SUBMIT_SUCCESS,
  COMPOSE_SUBMIT_FAIL,
  COMPOSE_UPLOAD_REQUEST,
  COMPOSE_UPLOAD_SUCCESS,
  COMPOSE_UPLOAD_FAIL,
  COMPOSE_UPLOAD_UNDO,
  COMPOSE_UPLOAD_PROGRESS,
  COMPOSE_SUGGESTIONS_CLEAR,
  COMPOSE_SUGGESTIONS_READY,
  COMPOSE_SUGGESTION_SELECT,
  COMPOSE_SENSITIVITY_CHANGE,
  COMPOSE_SPOILERNESS_CHANGE,
  COMPOSE_SPOILER_TEXT_CHANGE,
  COMPOSE_VISIBILITY_CHANGE,
<<<<<<< HEAD
  COMPOSE_LISTABILITY_CHANGE,
  COMPOSE_HASH_TAG_CLEAR,
  COMPOSE_HASH_TAG_READY,
  COMPOSE_HASH_TAG_SELECT,
=======
>>>>>>> v1.4.7
  COMPOSE_EMOJI_INSERT,
  COMPOSE_TAG_INSERT,
} from '../actions/compose';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
import uuid from '../uuid';

const initialState = Immutable.Map({
  mounted: false,
  sensitive: false,
  spoiler: false,
  spoiler_text: '',
  privacy: null,
  text: '',
  focusDate: null,
  preselectDate: null,
  in_reply_to: null,
  is_submitting: false,
  is_uploading: false,
  progress: 0,
  media_attachments: Immutable.List(),
  suggestion_token: null,
  suggestions: Immutable.List(),
  hash_tag_suggestions: Immutable.List(),
  me: null,
  default_privacy: 'public',
  resetFileKey: Math.floor((Math.random() * 0x10000)),
  idempotencyKey: null,
});

function statusToTextMentions(state, status) {
  let set = Immutable.OrderedSet([]);
  let me  = state.get('me');

  if (status.getIn(['account', 'id']) !== me) {
    set = set.add(`@${status.getIn(['account', 'acct'])} `);
  }

  return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
};

function clearAll(state) {
  return state.withMutations(map => {
    map.set('text', '');
    map.set('spoiler', false);
    map.set('spoiler_text', '');
    map.set('is_submitting', false);
    map.set('in_reply_to', null);
    map.set('privacy', state.get('default_privacy'));
    map.set('sensitive', false);
    map.update('media_attachments', list => list.clear());
    map.set('idempotencyKey', uuid());
  });
};

function appendMedia(state, media) {
  return state.withMutations(map => {
    map.update('media_attachments', list => list.push(media));
    map.set('is_uploading', false);
    map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
    map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

function removeMedia(state, mediaId) {
  const media    = state.get('media_attachments').find(item => item.get('id') === mediaId);
  const prevSize = state.get('media_attachments').size;

  return state.withMutations(map => {
    map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
    map.update('text', text => text.replace(media.get('text_url'), '').trim());
    map.set('idempotencyKey', uuid());

    if (prevSize === 1) {
      map.set('sensitive', false);
    }
  });
};

const insertSuggestion = (state, position, token, completion) => {
  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
    map.set('suggestion_token', null);
    map.update('suggestions', Immutable.List(), list => list.clear());
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const insertHashTag = (state, position, token, completion) => {
  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
    map.set('hash_tag_token', null);
    map.update('hash_tag_suggestions', Immutable.List(), list => list.clear());
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const insertEmoji = (state, position, emojiData) => {
  const emoji = emojiData.shortname;

  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const insertTag = (state, tag) => {
  return state.withMutations(map => {
    map.update('text', oldText => `${oldText} ${tag}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const privacyPreference = (a, b) => {
  if (a === 'direct' || b === 'direct') {
    return 'direct';
  } else if (a === 'private' || b === 'private') {
    return 'private';
  } else if (a === 'unlisted' || b === 'unlisted') {
    return 'unlisted';
  } else {
    return 'public';
  }
};

export default function compose(state = initialState, action) {
  switch(action.type) {
  case STORE_HYDRATE:
    return clearAll(state.merge(action.state.get('compose')));
  case COMPOSE_MOUNT:
    return state.set('mounted', true);
  case COMPOSE_UNMOUNT:
    return state.set('mounted', false);
  case COMPOSE_SENSITIVITY_CHANGE:
    return state
      .set('sensitive', !state.get('sensitive'))
      .set('idempotencyKey', uuid());
  case COMPOSE_SPOILERNESS_CHANGE:
    return state.withMutations(map => {
      map.set('spoiler_text', '');
      map.set('spoiler', !state.get('spoiler'));
      map.set('idempotencyKey', uuid());
    });
  case COMPOSE_SPOILER_TEXT_CHANGE:
    return state
      .set('spoiler_text', action.text)
      .set('idempotencyKey', uuid());
  case COMPOSE_VISIBILITY_CHANGE:
    return state
      .set('privacy', action.value)
      .set('idempotencyKey', uuid());
  case COMPOSE_CHANGE:
    return state
      .set('text', action.text)
      .set('idempotencyKey', uuid());
  case COMPOSE_REPLY:
    return state.withMutations(map => {
      map.set('in_reply_to', action.status.get('id'));
      map.set('text', statusToTextMentions(state, action.status));
      map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
      map.set('focusDate', new Date());
      map.set('preselectDate', new Date());
      map.set('idempotencyKey', uuid());

      if (action.status.get('spoiler_text').length > 0) {
        map.set('spoiler', true);
        map.set('spoiler_text', action.status.get('spoiler_text'));
      } else {
        map.set('spoiler', false);
        map.set('spoiler_text', '');
      }
    });
  case COMPOSE_REPLY_CANCEL:
    return state.withMutations(map => {
      map.set('in_reply_to', null);
      map.set('text', '');
      map.set('spoiler', false);
      map.set('spoiler_text', '');
      map.set('privacy', state.get('default_privacy'));
      map.set('idempotencyKey', uuid());
    });
  case COMPOSE_SUBMIT_REQUEST:
    return state.set('is_submitting', true);
  case COMPOSE_SUBMIT_SUCCESS:
    return clearAll(state);
  case COMPOSE_SUBMIT_FAIL:
    return state.set('is_submitting', false);
  case COMPOSE_UPLOAD_REQUEST:
    return state.withMutations(map => {
      map.set('is_uploading', true);
    });
  case COMPOSE_UPLOAD_SUCCESS:
    return appendMedia(state, Immutable.fromJS(action.media));
  case COMPOSE_UPLOAD_FAIL:
    return state.set('is_uploading', false);
  case COMPOSE_UPLOAD_UNDO:
    return removeMedia(state, action.media_id);
  case COMPOSE_UPLOAD_PROGRESS:
    return state.set('progress', Math.round((action.loaded / action.total) * 100));
  case COMPOSE_MENTION:
    return state
      .update('text', text => `${text}@${action.account.get('acct')} `)
      .set('focusDate', new Date())
      .set('idempotencyKey', uuid());
  case COMPOSE_SUGGESTIONS_CLEAR:
    return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null);
  case COMPOSE_SUGGESTIONS_READY:
    return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id))).set('suggestion_token', action.token);
  case COMPOSE_SUGGESTION_SELECT:
    return insertSuggestion(state, action.position, action.token, action.completion);
  case COMPOSE_HASH_TAG_CLEAR:
    return state.update('hash_tag_suggestions', Immutable.List(), list => list.clear()).set('hash_tag_token', null);
  case COMPOSE_HASH_TAG_READY:
    return state.set('hash_tag_suggestions', Immutable.List(action.tags)).set('hash_tag_token', action.token);
  case COMPOSE_HASH_TAG_SELECT:
    return insertHashTag(state, action.position, action.token, action.completion);
  case TIMELINE_DELETE:
    if (action.id === state.get('in_reply_to')) {
      return state.set('in_reply_to', null);
    } else {
      return state;
    }
  case COMPOSE_EMOJI_INSERT:
    return insertEmoji(state, action.position, action.emoji);
  case COMPOSE_TAG_INSERT:
    return insertTag(state, action.tag);
  default:
    return state;
  }
};
<commit_msg>Resolve a merged conflict - app/javascript/mastodon/reducers/compose.js<commit_after>import {
  COMPOSE_MOUNT,
  COMPOSE_UNMOUNT,
  COMPOSE_CHANGE,
  COMPOSE_REPLY,
  COMPOSE_REPLY_CANCEL,
  COMPOSE_MENTION,
  COMPOSE_SUBMIT_REQUEST,
  COMPOSE_SUBMIT_SUCCESS,
  COMPOSE_SUBMIT_FAIL,
  COMPOSE_UPLOAD_REQUEST,
  COMPOSE_UPLOAD_SUCCESS,
  COMPOSE_UPLOAD_FAIL,
  COMPOSE_UPLOAD_UNDO,
  COMPOSE_UPLOAD_PROGRESS,
  COMPOSE_SUGGESTIONS_CLEAR,
  COMPOSE_SUGGESTIONS_READY,
  COMPOSE_SUGGESTION_SELECT,
  COMPOSE_SENSITIVITY_CHANGE,
  COMPOSE_SPOILERNESS_CHANGE,
  COMPOSE_SPOILER_TEXT_CHANGE,
  COMPOSE_VISIBILITY_CHANGE,
  COMPOSE_HASH_TAG_CLEAR,
  COMPOSE_HASH_TAG_READY,
  COMPOSE_HASH_TAG_SELECT,
  COMPOSE_EMOJI_INSERT,
  COMPOSE_TAG_INSERT,
} from '../actions/compose';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
import uuid from '../uuid';

const initialState = Immutable.Map({
  mounted: false,
  sensitive: false,
  spoiler: false,
  spoiler_text: '',
  privacy: null,
  text: '',
  focusDate: null,
  preselectDate: null,
  in_reply_to: null,
  is_submitting: false,
  is_uploading: false,
  progress: 0,
  media_attachments: Immutable.List(),
  suggestion_token: null,
  suggestions: Immutable.List(),
  hash_tag_suggestions: Immutable.List(),
  me: null,
  default_privacy: 'public',
  resetFileKey: Math.floor((Math.random() * 0x10000)),
  idempotencyKey: null,
});

function statusToTextMentions(state, status) {
  let set = Immutable.OrderedSet([]);
  let me  = state.get('me');

  if (status.getIn(['account', 'id']) !== me) {
    set = set.add(`@${status.getIn(['account', 'acct'])} `);
  }

  return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
};

function clearAll(state) {
  return state.withMutations(map => {
    map.set('text', '');
    map.set('spoiler', false);
    map.set('spoiler_text', '');
    map.set('is_submitting', false);
    map.set('in_reply_to', null);
    map.set('privacy', state.get('default_privacy'));
    map.set('sensitive', false);
    map.update('media_attachments', list => list.clear());
    map.set('idempotencyKey', uuid());
  });
};

function appendMedia(state, media) {
  return state.withMutations(map => {
    map.update('media_attachments', list => list.push(media));
    map.set('is_uploading', false);
    map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
    map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

function removeMedia(state, mediaId) {
  const media    = state.get('media_attachments').find(item => item.get('id') === mediaId);
  const prevSize = state.get('media_attachments').size;

  return state.withMutations(map => {
    map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
    map.update('text', text => text.replace(media.get('text_url'), '').trim());
    map.set('idempotencyKey', uuid());

    if (prevSize === 1) {
      map.set('sensitive', false);
    }
  });
};

const insertSuggestion = (state, position, token, completion) => {
  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
    map.set('suggestion_token', null);
    map.update('suggestions', Immutable.List(), list => list.clear());
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const insertHashTag = (state, position, token, completion) => {
  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
    map.set('hash_tag_token', null);
    map.update('hash_tag_suggestions', Immutable.List(), list => list.clear());
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const insertEmoji = (state, position, emojiData) => {
  const emoji = emojiData.shortname;

  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const insertTag = (state, tag) => {
  return state.withMutations(map => {
    map.update('text', oldText => `${oldText} ${tag}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const privacyPreference = (a, b) => {
  if (a === 'direct' || b === 'direct') {
    return 'direct';
  } else if (a === 'private' || b === 'private') {
    return 'private';
  } else if (a === 'unlisted' || b === 'unlisted') {
    return 'unlisted';
  } else {
    return 'public';
  }
};

export default function compose(state = initialState, action) {
  switch(action.type) {
  case STORE_HYDRATE:
    return clearAll(state.merge(action.state.get('compose')));
  case COMPOSE_MOUNT:
    return state.set('mounted', true);
  case COMPOSE_UNMOUNT:
    return state.set('mounted', false);
  case COMPOSE_SENSITIVITY_CHANGE:
    return state
      .set('sensitive', !state.get('sensitive'))
      .set('idempotencyKey', uuid());
  case COMPOSE_SPOILERNESS_CHANGE:
    return state.withMutations(map => {
      map.set('spoiler_text', '');
      map.set('spoiler', !state.get('spoiler'));
      map.set('idempotencyKey', uuid());
    });
  case COMPOSE_SPOILER_TEXT_CHANGE:
    return state
      .set('spoiler_text', action.text)
      .set('idempotencyKey', uuid());
  case COMPOSE_VISIBILITY_CHANGE:
    return state
      .set('privacy', action.value)
      .set('idempotencyKey', uuid());
  case COMPOSE_CHANGE:
    return state
      .set('text', action.text)
      .set('idempotencyKey', uuid());
  case COMPOSE_REPLY:
    return state.withMutations(map => {
      map.set('in_reply_to', action.status.get('id'));
      map.set('text', statusToTextMentions(state, action.status));
      map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
      map.set('focusDate', new Date());
      map.set('preselectDate', new Date());
      map.set('idempotencyKey', uuid());

      if (action.status.get('spoiler_text').length > 0) {
        map.set('spoiler', true);
        map.set('spoiler_text', action.status.get('spoiler_text'));
      } else {
        map.set('spoiler', false);
        map.set('spoiler_text', '');
      }
    });
  case COMPOSE_REPLY_CANCEL:
    return state.withMutations(map => {
      map.set('in_reply_to', null);
      map.set('text', '');
      map.set('spoiler', false);
      map.set('spoiler_text', '');
      map.set('privacy', state.get('default_privacy'));
      map.set('idempotencyKey', uuid());
    });
  case COMPOSE_SUBMIT_REQUEST:
    return state.set('is_submitting', true);
  case COMPOSE_SUBMIT_SUCCESS:
    return clearAll(state);
  case COMPOSE_SUBMIT_FAIL:
    return state.set('is_submitting', false);
  case COMPOSE_UPLOAD_REQUEST:
    return state.withMutations(map => {
      map.set('is_uploading', true);
    });
  case COMPOSE_UPLOAD_SUCCESS:
    return appendMedia(state, Immutable.fromJS(action.media));
  case COMPOSE_UPLOAD_FAIL:
    return state.set('is_uploading', false);
  case COMPOSE_UPLOAD_UNDO:
    return removeMedia(state, action.media_id);
  case COMPOSE_UPLOAD_PROGRESS:
    return state.set('progress', Math.round((action.loaded / action.total) * 100));
  case COMPOSE_MENTION:
    return state
      .update('text', text => `${text}@${action.account.get('acct')} `)
      .set('focusDate', new Date())
      .set('idempotencyKey', uuid());
  case COMPOSE_SUGGESTIONS_CLEAR:
    return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null);
  case COMPOSE_SUGGESTIONS_READY:
    return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id))).set('suggestion_token', action.token);
  case COMPOSE_SUGGESTION_SELECT:
    return insertSuggestion(state, action.position, action.token, action.completion);
  case COMPOSE_HASH_TAG_CLEAR:
    return state.update('hash_tag_suggestions', Immutable.List(), list => list.clear()).set('hash_tag_token', null);
  case COMPOSE_HASH_TAG_READY:
    return state.set('hash_tag_suggestions', Immutable.List(action.tags)).set('hash_tag_token', action.token);
  case COMPOSE_HASH_TAG_SELECT:
    return insertHashTag(state, action.position, action.token, action.completion);
  case TIMELINE_DELETE:
    if (action.id === state.get('in_reply_to')) {
      return state.set('in_reply_to', null);
    } else {
      return state;
    }
  case COMPOSE_EMOJI_INSERT:
    return insertEmoji(state, action.position, action.emoji);
  case COMPOSE_TAG_INSERT:
    return insertTag(state, action.tag);
  default:
    return state;
  }
};
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();
var Game = require('../../models/game/game.model');
var GameList = require('../../models/gameList/gameList.model');
var User = require('../../models/user/user.model');
var verifyToken = require('../login/verifyToken');
var Rating = require('../../models/rating/rating.model');
var constants = require('../../config/constants.config');
var validator = require('../../validator/validator');
var check = require('check-types');
var _ = require('lodash');
var bruteforce = require('../../bruteforce/bruteForce');

router.use(function (req, res, next) {
	next();
});

module.exports = function (app) {
	var validateGameQuery = function (req, res, next) {
		if (!req.body.title || !req.body.shortDescription) return res.status(422).json({message: constants.httpResponseMessages.unprocessableEntity});
		else next();
	};

	router.post('/games', verifyToken, validateGameQuery, function (req, res) {
		var newgame = new Game(_.extend(req.body, {owner: req.verified.id}));
		if (!req.body.parentGame) game.parentGame = undefined;
		Game.findOne({title: req.body.title}, function (err, game){
			if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
			if (game) return res.status(409).json({message: constants.httpResponseMessages.conflict});
			newgame.save(function (err) {
				if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				newgame.populate('owner', 'username', function (err) {
					if (err) res.status(500).json({message: constants.httpResponseMessages.internalServerError});
					else return res.status(201).json(newgame);
				});
			})
		});
	});

	var parseSearchQuery = function (req, res, next) {
		var query = {};
		var options = {};
		if (req.query.title) query.title = {'$regex': req.query.title, '$options': 'i'};
		if (req.query.singles) query['pieces.singles'] = {'$lte': req.query.singles};
		if (req.query.doubles) query['pieces.doubles'] = {'$lte': req.query.doubles};
		if (req.query.triples) query['pieces.triples'] = {'$lte': req.query.triples};
		if (req.query.numberOfPlayers) {
			query.$or = [{'numberOfPlayers': req.query.numberOfPlayers}, {'isPlayableWithMorePlayers': true, numberOfPlayers: {$lte: req.query.numberOfPlayers}}];
		};
		if (req.query.otherObjects === 'true') query.otherObjects = {$gt: []};
		else if (req.query.otherObjects === 'false') query.otherObjects = {$lte: []};
		if (req.query.teams === 'true') query.isPlayableInTeams = true;
		else if (req.query.teams === 'false') query.isPlayableInTeams = false;
		if (req.query.owner) query.owner = req.query.owner;
		if (req.query.rating) query.rating = {'$gte': req.query.rating};
		if (req.query.search) query['$text'] = {$search: req.query.search};
		if (req.query.start) options.skip = parseInt(req.query.start);
		if (req.query.quantity) options.limit = parseInt(req.query.quantity);
		if (req.query.sortBy) options['sort'] = {rating: -1};
		req.query = query;
		req.options = options;
		next();
	};

	var populateOwnerField = function (req, res, next) {
		if (!req.query) {
			req.query = {};
			next();
		}
		if (req.query.owner) {
			User.findOne({username: req.query.owner}, '-password -__v', function (err, user) {
				if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				if (user == null) return res.status(404).json({});
				else req.query.owner = user._id;
				next();
			});
		}
		else next();
	};

	router.route('/games').get(populateOwnerField, parseSearchQuery, function (req, res) {
		Game.find(req.query, '-__v', req.options, function (err, games) {
			if (err) {
				res.status(500).json({mesage: constants.httpResponseMessages.internalServerError});
				console.log(err);
			}
			else res.status(200).json({games: games});
		}).populate('owner', 'username');
	});

	router.route('/games/:game_id')
		.get(function (req, res) {
			Game.findById(req.params.game_id, function (err, game) {
				if (err) return res.send(constants.httpResponseMessages.internalServerError);
				if (game == null) return res.status(404).json({message: constants.httpResponseMessages.notFound});
				else return res.json(game);
			}).populate('owner', 'username');
		})
		.put(verifyToken, function (req, res) {
			Game.findById({_id: req.params.game_id}, function (err, game) { // TODO: Validate id before doing db call
				if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				if (!game) return res.status(404).json({message: constants.httpResponseMessages.badRequest});
				if (game.owner !== req.verified.id) return res.status(401).json({message: constants.httpResponseMessages.unauthorized});
				if (req.body.title) game.title = req.body.title;
				if (req.body.mainDescription) game.mainDescription = req.body.mainDescription;
				game.save(function (err, game) {
					if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
					game.populate('owner', 'username', function (err) {
						if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
						return res.status(200).json(game);
					});
				});
			});
		})
		.delete(verifyToken, function (req, res) {
			Game.findById({_id: req.params.game_id}, function (err, game) {
				if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				if (!game) return res.status(404).json({message: constants.httpResponseMessages.notFound});
				if (game.owner !== req.verified.id) return res.status(401).json({message: constants.httpResponseMessages.unauthorized});
				Game.remove({_id: req.params.game_id}, function (err, game) {
					if (!game) return res.status(404).json({message: constants.httpResponseMessages.notFound});
					if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
					else res.status(204).json({message: constants.httpResponseMessages.deleted});
				});
			});
		});

	router.route('/games/:game_id/fork').get(function (req, res) {
		if (!validator.isValidId(req.params.game_id)) return res.status(422).json({message: constants.httpResponseMessages.unprocessableEntity});
		Game.findById({_id: req.params.game_id}, function (err, game) {
			if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
			if (!game) return res.status(404).json({message: constants.httpResponseMessages.notFound});
			game.parentGame = game._id;
			game._id = undefined;
			game.owner = undefined;
			game.numberOfVotes = 0;
			game.sumOfVotes = 0;
			game.rating = 0;
			game.reviews = undefined;
			return res.status(200).json(game);
		});
	});

	router.route('/games/:game_id/ratings')
		.put(verifyToken, function (req, res) {
			if (!req.body.rating) {
				return res.status(400).json({message: constants.httpResponseMessages.badRequest});
			}
			Game.findById(req.params.game_id, function (err, game) {
				if (err) {
					return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				}
				if (game === null) return res.status(404).json({message: constants.httpResponseMessages.notFound});
				var newRating;
				var newVoteCount;
				Rating.findOne({_userId: req.verified.id, _gameId: req.params.game_id}, function (err, rating) {
					if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
					if (rating !== null) {
						if (rating.rating === req.params.rating) {
							return res.status(409).json({message: constants.httpResponseMessages.conflict});
						}
						newRating = game.sumOfVotes;
						newRating -= rating.rating;
						newRating += req.body.rating;
						newVoteCount = game.numberOfVotes;
					}
					else {
						newRating = game.sumOfVotes + req.body.rating;
						newVoteCount = game.numberOfVotes;
						newVoteCount++;
					};
					Rating.findOneAndUpdate({_userId: req.verified.id, _gameId: req.params.game_id}, {rating: req.body.rating}, {upsert: true}, function (err) {
						if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
						Game.findOneAndUpdate({_id: req.params.game_id}, {numberOfVotes: newVoteCount, sumOfVotes: newRating}, {upsert: false}, function (err) {
							if (err) {
								return;
							}
							return res.status(204).json({message: constants.httpResponseMessages.ok});
						});
					});
				});
			});
		});
	return router;
};
<commit_msg>fixed typo in game-route.<commit_after>var express = require('express');
var router = express.Router();
var Game = require('../../models/game/game.model');
var GameList = require('../../models/gameList/gameList.model');
var User = require('../../models/user/user.model');
var verifyToken = require('../login/verifyToken');
var Rating = require('../../models/rating/rating.model');
var constants = require('../../config/constants.config');
var validator = require('../../validator/validator');
var check = require('check-types');
var _ = require('lodash');
var bruteforce = require('../../bruteforce/bruteForce');

router.use(function (req, res, next) {
	next();
});

module.exports = function (app) {
	var validateGameQuery = function (req, res, next) {
		if (!req.body.title || !req.body.shortDescription) return res.status(422).json({message: constants.httpResponseMessages.unprocessableEntity});
		else next();
	};

	router.post('/games', verifyToken, validateGameQuery, function (req, res) {
		var newgame = new Game(_.extend(req.body, {owner: req.verified.id}));
		if (!req.body.parentGame) newgame.parentGame = undefined;
		Game.findOne({title: req.body.title}, function (err, game) {
			if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
			if (game) return res.status(409).json({message: constants.httpResponseMessages.conflict});
			newgame.save(function (err) {
				if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				newgame.populate('owner', 'username', function (err) {
					if (err) res.status(500).json({message: constants.httpResponseMessages.internalServerError});
					else return res.status(201).json(newgame);
				});
			});
		});
	});

	var parseSearchQuery = function (req, res, next) {
		var query = {};
		var options = {};
		if (req.query.title) query.title = {'$regex': req.query.title, '$options': 'i'};
		if (req.query.singles) query['pieces.singles'] = {'$lte': req.query.singles};
		if (req.query.doubles) query['pieces.doubles'] = {'$lte': req.query.doubles};
		if (req.query.triples) query['pieces.triples'] = {'$lte': req.query.triples};
		if (req.query.numberOfPlayers) {
			query.$or = [{'numberOfPlayers': req.query.numberOfPlayers}, {'isPlayableWithMorePlayers': true, numberOfPlayers: {$lte: req.query.numberOfPlayers}}];
		};
		if (req.query.otherObjects === 'true') query.otherObjects = {$gt: []};
		else if (req.query.otherObjects === 'false') query.otherObjects = {$lte: []};
		if (req.query.teams === 'true') query.isPlayableInTeams = true;
		else if (req.query.teams === 'false') query.isPlayableInTeams = false;
		if (req.query.owner) query.owner = req.query.owner;
		if (req.query.rating) query.rating = {'$gte': req.query.rating};
		if (req.query.search) query['$text'] = {$search: req.query.search};
		if (req.query.start) options.skip = parseInt(req.query.start);
		if (req.query.quantity) options.limit = parseInt(req.query.quantity);
		if (req.query.sortBy) options['sort'] = {rating: -1};
		req.query = query;
		req.options = options;
		next();
	};

	var populateOwnerField = function (req, res, next) {
		if (!req.query) {
			req.query = {};
			next();
		}
		if (req.query.owner) {
			User.findOne({username: req.query.owner}, '-password -__v', function (err, user) {
				if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				if (user == null) return res.status(404).json({});
				else req.query.owner = user._id;
				next();
			});
		}
		else next();
	};

	router.route('/games').get(populateOwnerField, parseSearchQuery, function (req, res) {
		Game.find(req.query, '-__v', req.options, function (err, games) {
			if (err) {
				res.status(500).json({mesage: constants.httpResponseMessages.internalServerError});
				console.log(err);
			}
			else res.status(200).json({games: games});
		}).populate('owner', 'username');
	});

	router.route('/games/:game_id')
		.get(function (req, res) {
			Game.findById(req.params.game_id, function (err, game) {
				if (err) return res.send(constants.httpResponseMessages.internalServerError);
				if (game == null) return res.status(404).json({message: constants.httpResponseMessages.notFound});
				else return res.json(game);
			}).populate('owner', 'username');
		})
		.put(verifyToken, function (req, res) {
			Game.findById({_id: req.params.game_id}, function (err, game) { // TODO: Validate id before doing db call
				if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				if (!game) return res.status(404).json({message: constants.httpResponseMessages.badRequest});
				if (game.owner !== req.verified.id) return res.status(401).json({message: constants.httpResponseMessages.unauthorized});
				if (req.body.title) game.title = req.body.title;
				if (req.body.mainDescription) game.mainDescription = req.body.mainDescription;
				game.save(function (err, game) {
					if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
					game.populate('owner', 'username', function (err) {
						if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
						return res.status(200).json(game);
					});
				});
			});
		})
		.delete(verifyToken, function (req, res) {
			Game.findById({_id: req.params.game_id}, function (err, game) {
				if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				if (!game) return res.status(404).json({message: constants.httpResponseMessages.notFound});
				if (game.owner !== req.verified.id) return res.status(401).json({message: constants.httpResponseMessages.unauthorized});
				Game.remove({_id: req.params.game_id}, function (err, game) {
					if (!game) return res.status(404).json({message: constants.httpResponseMessages.notFound});
					if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
					else res.status(204).json({message: constants.httpResponseMessages.deleted});
				});
			});
		});

	router.route('/games/:game_id/fork').get(function (req, res) {
		if (!validator.isValidId(req.params.game_id)) return res.status(422).json({message: constants.httpResponseMessages.unprocessableEntity});
		Game.findById({_id: req.params.game_id}, function (err, game) {
			if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
			if (!game) return res.status(404).json({message: constants.httpResponseMessages.notFound});
			game.parentGame = game._id;
			game._id = undefined;
			game.owner = undefined;
			game.numberOfVotes = 0;
			game.sumOfVotes = 0;
			game.rating = 0;
			game.reviews = undefined;
			return res.status(200).json(game);
		});
	});

	router.route('/games/:game_id/ratings')
		.put(verifyToken, function (req, res) {
			if (!req.body.rating) {
				return res.status(400).json({message: constants.httpResponseMessages.badRequest});
			}
			Game.findById(req.params.game_id, function (err, game) {
				if (err) {
					return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
				}
				if (game === null) return res.status(404).json({message: constants.httpResponseMessages.notFound});
				var newRating;
				var newVoteCount;
				Rating.findOne({_userId: req.verified.id, _gameId: req.params.game_id}, function (err, rating) {
					if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
					if (rating !== null) {
						if (rating.rating === req.params.rating) {
							return res.status(409).json({message: constants.httpResponseMessages.conflict});
						}
						newRating = game.sumOfVotes;
						newRating -= rating.rating;
						newRating += req.body.rating;
						newVoteCount = game.numberOfVotes;
					}
					else {
						newRating = game.sumOfVotes + req.body.rating;
						newVoteCount = game.numberOfVotes;
						newVoteCount++;
					};
					Rating.findOneAndUpdate({_userId: req.verified.id, _gameId: req.params.game_id}, {rating: req.body.rating}, {upsert: true}, function (err) {
						if (err) return res.status(500).json({message: constants.httpResponseMessages.internalServerError});
						Game.findOneAndUpdate({_id: req.params.game_id}, {numberOfVotes: newVoteCount, sumOfVotes: newRating}, {upsert: false}, function (err) {
							if (err) {
								return;
							}
							return res.status(204).json({message: constants.httpResponseMessages.ok});
						});
					});
				});
			});
		});
	return router;
};
<|endoftext|>


<commit_before>/**
	* Tracks processing steps status changes and updates direct isReady.
	* It's needed to maintain desired isReady's stamp.
	* This service is complementary to business-process-flow
*/

'use strict';

var aFrom                         = require('es5-ext/array/from')
  , capitalize                    = require('es5-ext/string/#/capitalize')
  , Map                           = require('es6-map')
  , Set                           = require('es6-set')
  , deferred                      = require('deferred')
  , debug                         = require('debug-ext')('business-process-flow')
  , resolveProcessingStepFullPath = require('../../utils/resolve-processing-step-full-path');

var copyIsReady = function (storage, stepId) {
	var isReadyPath = stepId + '/isReady';
	return storage.get(isReadyPath)(function (record) {
		if (record && (record.value[0] === '1')) return;
		return storage.getComputed(isReadyPath)(function (computedRecord) {
			if (!computedRecord || (computedRecord.value !== '11')) {
				console.error("\nUnexpected isReady value for" + isReadyPath + "\n");
				console.log("Direct record", record);
				console.log("Computed record", computedRecord);
				return;
			}
			var shortPath = stepId.split('/map/').slice(1).map(function (part) {
				return part.replace(/\/steps$/, '');
			}).join('/');
			debug('%s %s step shadow isReady', stepId.split('/', 1)[0], shortPath);
			return storage.store(isReadyPath, computedRecord.value, computedRecord.stamp);
		});
	});
};

module.exports = function (driver, processingStepsMeta) {
	var storageStepMap = new Map();
	Object.keys(processingStepsMeta).forEach(function (stepMetaKey) {
		var stepPath = 'processingSteps/map/' + resolveProcessingStepFullPath(stepMetaKey);
		processingStepsMeta[stepMetaKey]._services.forEach(function (serviceName) {
			var storage = driver.getStorage('businessProcess' + capitalize.call(serviceName));
			if (!storageStepMap.has(storage)) storageStepMap.set(storage, new Set());
			storageStepMap.get(storage).add(stepPath);
		});
	});
	deferred.map(aFrom(storageStepMap), function (data) {
		var storage = data[0], stepPaths = data[1];
		stepPaths.forEach(function (stepPath) {
			storage.on('key:' + stepPath + '/status', function (event) {
				if (event.type !== 'direct') return;
				if (event.data.value[0] !== '3') return;
				copyIsReady(storage, event.ownerId + '/' + stepPath).done();
			});
		});
		var statusPaths = new Set(aFrom(stepPaths).map(function (stepPath) {
			return stepPath + '/status';
		}));
		return storage.search(function (id, data) {
			if (!statusPaths.has(id)) return;
			if (data.value[0] !== '3') return;
			return copyIsReady(storage, id.slice(0, -'/status'.length));
		});
	}).done();
};
<commit_msg>Fix initial setup<commit_after>/**
	* Tracks processing steps status changes and updates direct isReady.
	* It's needed to maintain desired isReady's stamp.
	* This service is complementary to business-process-flow
*/

'use strict';

var aFrom                         = require('es5-ext/array/from')
  , capitalize                    = require('es5-ext/string/#/capitalize')
  , Map                           = require('es6-map')
  , Set                           = require('es6-set')
  , deferred                      = require('deferred')
  , debug                         = require('debug-ext')('business-process-flow')
  , resolveProcessingStepFullPath = require('../../utils/resolve-processing-step-full-path');

var copyIsReady = function (storage, stepId) {
	var isReadyPath = stepId + '/isReady';
	return storage.get(isReadyPath)(function (record) {
		if (record && (record.value[0] === '1')) return;
		return storage.getComputed(isReadyPath)(function (computedRecord) {
			if (!computedRecord || (computedRecord.value !== '11')) {
				console.error("\nUnexpected isReady value for" + isReadyPath + "\n");
				console.log("Direct record", record);
				console.log("Computed record", computedRecord);
				return;
			}
			var shortPath = stepId.split('/map/').slice(1).map(function (part) {
				return part.replace(/\/steps$/, '');
			}).join('/');
			debug('%s %s step shadow isReady', stepId.split('/', 1)[0], shortPath);
			return storage.store(isReadyPath, computedRecord.value, computedRecord.stamp);
		});
	});
};

module.exports = function (driver, processingStepsMeta) {
	var storageStepMap = new Map();
	Object.keys(processingStepsMeta).forEach(function (stepMetaKey) {
		var stepPath = 'processingSteps/map/' + resolveProcessingStepFullPath(stepMetaKey);
		processingStepsMeta[stepMetaKey]._services.forEach(function (serviceName) {
			var storage = driver.getStorage('businessProcess' + capitalize.call(serviceName));
			if (!storageStepMap.has(storage)) storageStepMap.set(storage, new Set());
			storageStepMap.get(storage).add(stepPath);
		});
	});
	deferred.map(aFrom(storageStepMap), function (data) {
		var storage = data[0], stepPaths = data[1];
		stepPaths.forEach(function (stepPath) {
			storage.on('key:' + stepPath + '/status', function (event) {
				if (event.type !== 'direct') return;
				if (event.data.value[0] !== '3') return;
				copyIsReady(storage, event.ownerId + '/' + stepPath).done();
			});
		});
		var statusPaths = new Set(aFrom(stepPaths).map(function (stepPath) {
			return stepPath + '/status';
		}));
		return storage.search(function (id, data) {
			var index = id.indexOf('/');
			if (index === -1) return;
			if (!statusPaths.has(id.slice(index + 1))) return;
			if (data.value[0] !== '3') return;
			return copyIsReady(storage, id.slice(0, -'/status'.length));
		});
	}).done();
};
<|endoftext|>


<commit_before>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

describe('Ghost Inspector', function() {

  var charmData, cleanIconHelper, conn, container, db, env, inspector, juju,
      jujuViews, models, service, utils, view, Y;

  before(function(done) {
    var requires = [
      'juju-gui', 'juju-views', 'juju-tests-utils', 'juju-charm-store',
      'juju-charm-models', 'juju-ghost-inspector', 'event-valuechange'];
    Y = YUI(GlobalConfig).use(requires, function(Y) {
          jujuViews = Y.namespace('juju.views');
          juju = Y.namespace('juju');
          models = Y.namespace('juju.models');
          utils = Y.namespace('juju-tests.utils');

          charmData = utils.loadFixture(
              'data/mediawiki-api-response.json', true);
          done();
        });

  });

  beforeEach(function() {
    cleanIconHelper = utils.stubCharmIconPath();
    container = utils.makeContainer('container');
    conn = new utils.SocketStub();
    db = new models.Database();
    env = juju.newEnvironment({conn: conn});
    window.flags.serviceInspector = true;
  });

  afterEach(function(done) {
    cleanIconHelper();
    if (view) {
      if (inspector) {
        view.setInspector(inspector, true);
      }
      view.destroy();
    }
    env.after('destroy', function() { done(); });
    env.destroy();
    container.remove(true);
    window.flags = {};
  });

  var setUpInspector = function(options) {
    var charm = new models.BrowserCharm(charmData.charm);
    db.charms.add(charm);

    // Create a ghost service with the fake charm.
    service = db.services.ghostService(charm);

    var fakeStore = new Y.juju.Charmworld2({});
    fakeStore.iconpath = function(id) {
      return '/icon/' + id;
    };

    view = new jujuViews.environment({
      container: container,
      db: db,
      env: env,
      store: fakeStore
    });

    view.render();
    Y.Node.create('
').appendTo(container);

    // interval: 0 must be set to test sync data updates else there will be
    // an aprox 150ms delay
    return view.createServiceInspector(service, {databinding: {interval: 0}});
  };

  it('updates the service name in the topology when changed in the inspector',
      function(done) {
        // XXX (Jeff) YUI's simulate can't properly simulate focus or blur in
        // IE10 as of 3.9.1, 3.11 https://github.com/yui/yui3/issues/489
        if (Y.UA.ie === 10) {
          done();
        }
        inspector = setUpInspector();
        var serviceIcon = Y.one('tspan.name');
        assert.equal(serviceIcon.get('textContent'), '(mediawiki 1)');

        var serviceNameInput = Y.one('input[name=service-name]'),
            vmContainer = inspector.viewletManager.get('container');
        // In order to properly detect the change event of the input we needed
        // to create our own event handler listening for the special Y.View
        // bubbling valuechange (note not valueChange) event.
        var handler = vmContainer.delegate('valuechange', function() {
          view.update(); // simulating a db.fire('update') call
          // Reselecting the service node because it is replaced not actually
          // updated by the topo service d3 system.
          assert.equal(Y.one('tspan.name').get('textContent'), '(foo)');
          handler.detach();
          done();
        }, 'input[name=service-name]');

        serviceNameInput.simulate('focus');
        serviceNameInput.set('value', 'foo');
      });

  it('displays the charms icon when rendered', function() {
    inspector = setUpInspector();
    var icon = container.one('.icon img');

    // The icon url is from the fakestore we manually defined.
    assert.equal(icon.getAttribute('src'), '/icon/cs:precise/mediawiki-8');
  });

  it('deploys a service with the specified unit count & config', function() {
    inspector = setUpInspector();
    env.connect();
    var vmContainer = inspector.viewletManager.get('container'),
        numUnits = 10;

    vmContainer.one('input[name=number-units]').set('value', numUnits);
    vmContainer.one('textarea[name=name]').set('value', 'foo');
    vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click');

    var message = env.ws.last_message();
    var params = message.Params;
    var config = {
      name: 'foo'
    };
    assert.equal('ServiceDeploy', message.Request);
    assert.equal('mediawiki', params.ServiceName);
    assert.equal(numUnits, params.NumUnits);
    assert.deepEqual(config, params.Config);
  });

  it('presents the contraints to the user in python env', function() {
    // Create our own env to make sure we know which backend we're creating it
    // against.
    env.destroy();
    env = juju.newEnvironment({conn: conn}, 'python');
    inspector = setUpInspector();
    var constraintsNode = container.all('.service-constraints');
    assert.equal(constraintsNode.size(), 1);

    var inputNodes = container.all('.service-constraints input');
    assert.equal(inputNodes.size(), 3);
  });

  it('presents the contraints to the user in go env', function() {
    // Create our own env to make sure we know which backend we're creating it
    // against.
    env.destroy();
    env = juju.newEnvironment({conn: conn}, 'go');
    inspector = setUpInspector();
    var constraintsNode = container.all('.service-constraints');
    assert.equal(constraintsNode.size(), 1);

    var inputNodes = container.all('.service-constraints input');
    assert.equal(inputNodes.size(), 4);
  });

  it('deploys with constraints in python env', function() {
    env.destroy();
    env = juju.newEnvironment({conn: conn}, 'python');
    inspector = setUpInspector();
    env.connect();
    var vmContainer = inspector.viewletManager.get('container');

    vmContainer.one('input[name=cpu]').set('value', 2);
    // Called the deploy button, but the css if confirm.
    vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click');

    var message = env.ws.last_message();
    assert.deepEqual(message.constraints, ['cpu=2', 'mem=', 'arch=']);
  });

  it('deploys with constraints in go env', function() {
    env.destroy();
    env = juju.newEnvironment({conn: conn}, 'go');
    inspector = setUpInspector();
    env.connect();
    var vmContainer = inspector.viewletManager.get('container');

    vmContainer.one('input[name=cpu-power]').set('value', 2);
    // Called the deploy button, but the css if confirm.
    vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click');

    var message = env.ws.last_message();
    assert.deepEqual(message.Params.Constraints, { 'cpu-power': 2 });
  });

  it('disables and resets input fields when \'use default config\' is active',
      function() {
        function testDisabled(hasAttr) {
          var settings = vmContainer.one(
              '.service-configuration .ghost-config-content');
          var inputs = settings.all('textarea');
          inputs.each(function(input) {
            assert.equal(input.hasAttribute('disabled'), hasAttr,
                         'textarea missing disabled');
          });
          inputs = settings.all('input');
          inputs.each(function(input) {
            // The import config field is not disabled.
            var id = input.get('id');
            if (id != 'config-file' || id !== 'use-default-toggle') {
              assert.equal(input.hasAttribute('disabled'), hasAttr,
                           'input missing disabled');
            }
          });
        }
        inspector = setUpInspector();
        var vmContainer = inspector.viewletManager.get('container');
        // 'use default config' should be on set by default
        var toggle = vmContainer.one('input#use-default-toggle');
        assert.equal(toggle.hasAttribute('checked'), true,
                     'toggle is not checked');
        // inputs should be disabled by default within the settings
        testDisabled(true);
        // clicking the toggle enables the input elements
        toggle.simulate('click');
        testDisabled(false);
        // set an input to a new value and then toggle the checkbox should
        // reset it to its previous value.
        var nameInput = vmContainer.one('textarea[name=name]'),
            oldVal = nameInput.get('value'),
            newVal = 'foo';

        assert.equal(nameInput.get('value'), oldVal);
        nameInput.set('value', newVal);
        assert.equal(nameInput.get('value'), newVal);
        toggle.simulate('click');
        testDisabled(true);
        assert.equal(nameInput.get('value'), oldVal);
      });

  it('Saves the config when closing the inspector via \'X\' or \'Save\'');

  it('renders into the dom when instantiated', function() {
    inspector = setUpInspector();
    assert.isObject(container.one('.ghost-config-wrapper'));
  });

  it('properly renders a service without charm options', function() {
    // Mutate charmData before the render.
    delete charmData.charm.options;
    inspector = setUpInspector();
    // Verify the viewlet rendered, previously it would raise.
    assert.isObject(container.one('.ghost-config-wrapper'));
    // Restore the test global
    charmData = utils.loadFixture('data/mediawiki-api-response.json', true);
  });

  it('hides configuration panel when a file is uploaded', function() {
    inspector = setUpInspector();
    var fileContents = 'yaml yaml yaml';

    inspector.onFileLoaded('a.yaml', {target: {result: fileContents}});
    inspector.viewletManager.configFileContent.should.equal(fileContents);
    var settings = container.all('.settings-wrapper');
    settings.each(function(node) {
      node.getStyle('display').should.equal('none');
    });
  });

  it('must restore file input when config is removed', function() {
    inspector = setUpInspector();
    var fileContents = 'yaml yaml yaml';

    inspector.onFileLoaded('a.yaml', {target: {result: fileContents}});
    inspector.viewletManager.configFileContent.should.equal(fileContents);
    var settings = container.all('.settings-wrapper');
    settings.each(function(node) {
      node.getStyle('display').should.equal('none');
    });
    // Load the file.
    var fileInput = container.one('.config-file input');
    inspector.onFileLoaded('a.yaml', {target: {result: fileContents}});

    // And then click to remove it.
    container.one('.config-file .fakebutton').simulate('click');

    // The content should be gone now.
    assert.equal(
        inspector.viewletManager.configFileContent,
        undefined);
    assert.equal(
        container.one('.config-file input').get('files').size(),
        0
    );
    assert.equal(
        container.one('.config-file .fakebutton').getContent(),
        'Import config file...'
    );
  });

  it('must be able to deploy with configuration from a file', function() {
    var received_config,
        received_config_raw;

    inspector = setUpInspector();
    var env = view.get('env');
    env.deploy = function(charm_url, service_name, config, config_raw) {
      received_config = config;
      received_config_raw = config_raw;
    };

    var config_raw = 'admins: \n user:pass';
    inspector.onFileLoaded('a.yaml', {target: {result: config_raw}});

    container.one('.confirm').simulate('click');
    // The config from the charm model should be emptied out.
    assert.equal(received_config, undefined);
    // The config from the file should take precedence.
    assert.equal(received_config_raw, config_raw);
  });

  describe('Service destroy UI', function() {
    it('has a button to destroy the service', function() {
      inspector = setUpInspector();
      assert.isObject(container.one('.destroy-service-trigger span'));
    });

    it('shows the destroy service prompt if the trigger is clicked',
        function() {
          inspector = setUpInspector();
          var promptBox = container.one('.destroy-service-prompt');
          assert.isTrue(promptBox.hasClass('closed'));
          container.one('.destroy-service-trigger span').simulate('click');
          assert.isFalse(promptBox.hasClass('closed'));
        });

    it('hides the destroy service prompt if cancel is clicked', function() {
      inspector = setUpInspector();
      var promptBox = container.one('.destroy-service-prompt');
      assert.isTrue(promptBox.hasClass('closed'));
      // First we have to open the prompt.
      container.one('.destroy-service-trigger span').simulate('click');
      assert.isFalse(promptBox.hasClass('closed'));
      // Now we can close it.
      container.one('.cancel-destroy').simulate('click');
      assert.isTrue(promptBox.hasClass('closed'));
    });

    it('initiates a destroy if the "Destroy" button is clicked',
        function(done) {
          inspector = setUpInspector();
          var promptBox = container.one('.destroy-service-prompt');
          // First we have to open the prompt.
          container.one('.destroy-service-trigger span').simulate('click');
          assert.isFalse(promptBox.hasClass('closed'));
          // If the test times out, it failed (because the expected
          // function call didn't happen).
          inspector.initiateServiceDestroy = function() {
            done();
          };
          container.one('.initiate-destroy').simulate('click');
        });

    it('wires up UI elements to handlers for destroy service', function() {
      // There are UI elements and they all have to be wired up to something.
      inspector = setUpInspector();
      var events = inspector.viewletManager.events;
      assert.equal(
          typeof events['.destroy-service-trigger span'].click, 'function');
      assert.equal(typeof events['.initiate-destroy'].click, 'function');
      assert.equal(typeof events['.cancel-destroy'].click, 'function');
    });
  });

});
<commit_msg>Fix equals<commit_after>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

describe('Ghost Inspector', function() {

  var charmData, cleanIconHelper, conn, container, db, env, inspector, juju,
      jujuViews, models, service, utils, view, Y;

  before(function(done) {
    var requires = [
      'juju-gui', 'juju-views', 'juju-tests-utils', 'juju-charm-store',
      'juju-charm-models', 'juju-ghost-inspector', 'event-valuechange'];
    Y = YUI(GlobalConfig).use(requires, function(Y) {
          jujuViews = Y.namespace('juju.views');
          juju = Y.namespace('juju');
          models = Y.namespace('juju.models');
          utils = Y.namespace('juju-tests.utils');

          charmData = utils.loadFixture(
              'data/mediawiki-api-response.json', true);
          done();
        });

  });

  beforeEach(function() {
    cleanIconHelper = utils.stubCharmIconPath();
    container = utils.makeContainer('container');
    conn = new utils.SocketStub();
    db = new models.Database();
    env = juju.newEnvironment({conn: conn});
    window.flags.serviceInspector = true;
  });

  afterEach(function(done) {
    cleanIconHelper();
    if (view) {
      if (inspector) {
        view.setInspector(inspector, true);
      }
      view.destroy();
    }
    env.after('destroy', function() { done(); });
    env.destroy();
    container.remove(true);
    window.flags = {};
  });

  var setUpInspector = function(options) {
    var charm = new models.BrowserCharm(charmData.charm);
    db.charms.add(charm);

    // Create a ghost service with the fake charm.
    service = db.services.ghostService(charm);

    var fakeStore = new Y.juju.Charmworld2({});
    fakeStore.iconpath = function(id) {
      return '/icon/' + id;
    };

    view = new jujuViews.environment({
      container: container,
      db: db,
      env: env,
      store: fakeStore
    });

    view.render();
    Y.Node.create('
').appendTo(container);

    // interval: 0 must be set to test sync data updates else there will be
    // an aprox 150ms delay
    return view.createServiceInspector(service, {databinding: {interval: 0}});
  };

  it('updates the service name in the topology when changed in the inspector',
      function(done) {
        // XXX (Jeff) YUI's simulate can't properly simulate focus or blur in
        // IE10 as of 3.9.1, 3.11 https://github.com/yui/yui3/issues/489
        if (Y.UA.ie === 10) {
          done();
        }
        inspector = setUpInspector();
        var serviceIcon = Y.one('tspan.name');
        assert.equal(serviceIcon.get('textContent'), '(mediawiki 1)');

        var serviceNameInput = Y.one('input[name=service-name]'),
            vmContainer = inspector.viewletManager.get('container');
        // In order to properly detect the change event of the input we needed
        // to create our own event handler listening for the special Y.View
        // bubbling valuechange (note not valueChange) event.
        var handler = vmContainer.delegate('valuechange', function() {
          view.update(); // simulating a db.fire('update') call
          // Reselecting the service node because it is replaced not actually
          // updated by the topo service d3 system.
          assert.equal(Y.one('tspan.name').get('textContent'), '(foo)');
          handler.detach();
          done();
        }, 'input[name=service-name]');

        serviceNameInput.simulate('focus');
        serviceNameInput.set('value', 'foo');
      });

  it('displays the charms icon when rendered', function() {
    inspector = setUpInspector();
    var icon = container.one('.icon img');

    // The icon url is from the fakestore we manually defined.
    assert.equal(icon.getAttribute('src'), '/icon/cs:precise/mediawiki-8');
  });

  it('deploys a service with the specified unit count & config', function() {
    inspector = setUpInspector();
    env.connect();
    var vmContainer = inspector.viewletManager.get('container'),
        numUnits = 10;

    vmContainer.one('input[name=number-units]').set('value', numUnits);
    vmContainer.one('textarea[name=name]').set('value', 'foo');
    vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click');

    var message = env.ws.last_message();
    var params = message.Params;
    var config = {
      name: 'foo'
    };
    assert.equal('ServiceDeploy', message.Request);
    assert.equal('mediawiki', params.ServiceName);
    assert.equal(numUnits, params.NumUnits);
    assert.deepEqual(config, params.Config);
  });

  it('presents the contraints to the user in python env', function() {
    // Create our own env to make sure we know which backend we're creating it
    // against.
    env.destroy();
    env = juju.newEnvironment({conn: conn}, 'python');
    inspector = setUpInspector();
    var constraintsNode = container.all('.service-constraints');
    assert.equal(constraintsNode.size(), 1);

    var inputNodes = container.all('.service-constraints input');
    assert.equal(inputNodes.size(), 3);
  });

  it('presents the contraints to the user in go env', function() {
    // Create our own env to make sure we know which backend we're creating it
    // against.
    env.destroy();
    env = juju.newEnvironment({conn: conn}, 'go');
    inspector = setUpInspector();
    var constraintsNode = container.all('.service-constraints');
    assert.equal(constraintsNode.size(), 1);

    var inputNodes = container.all('.service-constraints input');
    assert.equal(inputNodes.size(), 4);
  });

  it('deploys with constraints in python env', function() {
    env.destroy();
    env = juju.newEnvironment({conn: conn}, 'python');
    inspector = setUpInspector();
    env.connect();
    var vmContainer = inspector.viewletManager.get('container');

    vmContainer.one('input[name=cpu]').set('value', 2);
    // Called the deploy button, but the css if confirm.
    vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click');

    var message = env.ws.last_message();
    assert.deepEqual(message.constraints, ['cpu=2', 'mem=', 'arch=']);
  });

  it('deploys with constraints in go env', function() {
    env.destroy();
    env = juju.newEnvironment({conn: conn}, 'go');
    inspector = setUpInspector();
    env.connect();
    var vmContainer = inspector.viewletManager.get('container');

    vmContainer.one('input[name=cpu-power]').set('value', 2);
    // Called the deploy button, but the css if confirm.
    vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click');

    var message = env.ws.last_message();
    assert.deepEqual(message.Params.Constraints, { 'cpu-power': 2 });
  });

  it('disables and resets input fields when \'use default config\' is active',
      function() {
        function testDisabled(hasAttr) {
          var settings = vmContainer.one(
              '.service-configuration .ghost-config-content');
          var inputs = settings.all('textarea');
          inputs.each(function(input) {
            assert.equal(input.hasAttribute('disabled'), hasAttr,
                         'textarea missing disabled');
          });
          inputs = settings.all('input');
          inputs.each(function(input) {
            // The import config field is not disabled.
            var id = input.get('id');
            if (id !== 'config-file' || id !== 'use-default-toggle') {
              assert.equal(input.hasAttribute('disabled'), hasAttr,
                           'input missing disabled');
            }
          });
        }
        inspector = setUpInspector();
        var vmContainer = inspector.viewletManager.get('container');
        // 'use default config' should be on set by default
        var toggle = vmContainer.one('input#use-default-toggle');
        assert.equal(toggle.hasAttribute('checked'), true,
                     'toggle is not checked');
        // inputs should be disabled by default within the settings
        testDisabled(true);
        // clicking the toggle enables the input elements
        toggle.simulate('click');
        testDisabled(false);
        // set an input to a new value and then toggle the checkbox should
        // reset it to its previous value.
        var nameInput = vmContainer.one('textarea[name=name]'),
            oldVal = nameInput.get('value'),
            newVal = 'foo';

        assert.equal(nameInput.get('value'), oldVal);
        nameInput.set('value', newVal);
        assert.equal(nameInput.get('value'), newVal);
        toggle.simulate('click');
        testDisabled(true);
        assert.equal(nameInput.get('value'), oldVal);
      });

  it('Saves the config when closing the inspector via \'X\' or \'Save\'');

  it('renders into the dom when instantiated', function() {
    inspector = setUpInspector();
    assert.isObject(container.one('.ghost-config-wrapper'));
  });

  it('properly renders a service without charm options', function() {
    // Mutate charmData before the render.
    delete charmData.charm.options;
    inspector = setUpInspector();
    // Verify the viewlet rendered, previously it would raise.
    assert.isObject(container.one('.ghost-config-wrapper'));
    // Restore the test global
    charmData = utils.loadFixture('data/mediawiki-api-response.json', true);
  });

  it('hides configuration panel when a file is uploaded', function() {
    inspector = setUpInspector();
    var fileContents = 'yaml yaml yaml';

    inspector.onFileLoaded('a.yaml', {target: {result: fileContents}});
    inspector.viewletManager.configFileContent.should.equal(fileContents);
    var settings = container.all('.settings-wrapper');
    settings.each(function(node) {
      node.getStyle('display').should.equal('none');
    });
  });

  it('must restore file input when config is removed', function() {
    inspector = setUpInspector();
    var fileContents = 'yaml yaml yaml';

    inspector.onFileLoaded('a.yaml', {target: {result: fileContents}});
    inspector.viewletManager.configFileContent.should.equal(fileContents);
    var settings = container.all('.settings-wrapper');
    settings.each(function(node) {
      node.getStyle('display').should.equal('none');
    });
    // Load the file.
    var fileInput = container.one('.config-file input');
    inspector.onFileLoaded('a.yaml', {target: {result: fileContents}});

    // And then click to remove it.
    container.one('.config-file .fakebutton').simulate('click');

    // The content should be gone now.
    assert.equal(
        inspector.viewletManager.configFileContent,
        undefined);
    assert.equal(
        container.one('.config-file input').get('files').size(),
        0
    );
    assert.equal(
        container.one('.config-file .fakebutton').getContent(),
        'Import config file...'
    );
  });

  it('must be able to deploy with configuration from a file', function() {
    var received_config,
        received_config_raw;

    inspector = setUpInspector();
    var env = view.get('env');
    env.deploy = function(charm_url, service_name, config, config_raw) {
      received_config = config;
      received_config_raw = config_raw;
    };

    var config_raw = 'admins: \n user:pass';
    inspector.onFileLoaded('a.yaml', {target: {result: config_raw}});

    container.one('.confirm').simulate('click');
    // The config from the charm model should be emptied out.
    assert.equal(received_config, undefined);
    // The config from the file should take precedence.
    assert.equal(received_config_raw, config_raw);
  });

  describe('Service destroy UI', function() {
    it('has a button to destroy the service', function() {
      inspector = setUpInspector();
      assert.isObject(container.one('.destroy-service-trigger span'));
    });

    it('shows the destroy service prompt if the trigger is clicked',
        function() {
          inspector = setUpInspector();
          var promptBox = container.one('.destroy-service-prompt');
          assert.isTrue(promptBox.hasClass('closed'));
          container.one('.destroy-service-trigger span').simulate('click');
          assert.isFalse(promptBox.hasClass('closed'));
        });

    it('hides the destroy service prompt if cancel is clicked', function() {
      inspector = setUpInspector();
      var promptBox = container.one('.destroy-service-prompt');
      assert.isTrue(promptBox.hasClass('closed'));
      // First we have to open the prompt.
      container.one('.destroy-service-trigger span').simulate('click');
      assert.isFalse(promptBox.hasClass('closed'));
      // Now we can close it.
      container.one('.cancel-destroy').simulate('click');
      assert.isTrue(promptBox.hasClass('closed'));
    });

    it('initiates a destroy if the "Destroy" button is clicked',
        function(done) {
          inspector = setUpInspector();
          var promptBox = container.one('.destroy-service-prompt');
          // First we have to open the prompt.
          container.one('.destroy-service-trigger span').simulate('click');
          assert.isFalse(promptBox.hasClass('closed'));
          // If the test times out, it failed (because the expected
          // function call didn't happen).
          inspector.initiateServiceDestroy = function() {
            done();
          };
          container.one('.initiate-destroy').simulate('click');
        });

    it('wires up UI elements to handlers for destroy service', function() {
      // There are UI elements and they all have to be wired up to something.
      inspector = setUpInspector();
      var events = inspector.viewletManager.events;
      assert.equal(
          typeof events['.destroy-service-trigger span'].click, 'function');
      assert.equal(typeof events['.initiate-destroy'].click, 'function');
      assert.equal(typeof events['.cancel-destroy'].click, 'function');
    });
  });

});
<|endoftext|>


<commit_before>/**
 *
 */

var baseUrl = getRootPath();

$(document).ready(function(){
	/**
	 * 日期选择控件
	 */
	$('.form_date').datetimepicker({
	    language:  'zh-CN',
	    format: 'yyyy-mm-dd',
	    weekStart: 1,
	    todayBtn:  1,
		autoclose: 1,
		todayHighlight: 1,
		startView: 2,
		minView: 2,
		forceParse: 0,
		pickerPosition: "bottom-left"
	});

});<commit_msg>基础查询  日期初始化<commit_after>/**
 *
 */

var baseUrl = getRootPath();

$(document).ready(function(){
	/**
	 * 日期选择控件
	 */
	$('.form_date').datetimepicker({
	    language:  'zh-CN',
	    format: 'yyyy-mm-dd',
	    weekStart: 1,
	    todayBtn:  1,
		autoclose: 1,
		todayHighlight: 1,
		startView: 2,
		minView: 2,
		forceParse: 0,
		pickerPosition: "bottom-left"
	});

	//初始化日期
	var date = new Date();
	$("#startDate").val(date.Format("yyyy-MM")+"-01");
	$("#endDate").val(date.Format("yyyy-MM-dd"));
});<|endoftext|>


<commit_before>define(
['ext/backbone', 'ext/jquery', 'ext/underscore'],
function(Backbone, $, _) {

  var Router = Backbone.Router.extend({});

  /**
  * A base backbone model that has been extended to our needs.
  */
  var Model = Backbone.Model.extend({
    // TODO(mack): fix _oidFields so that it is scoped per model
    _oidFields: {},
    _dateFields: {},

    _cachedReferences: {},

    /**
     * Override toJSON to convert appropriate fields to their strict
     * BSON representation before sending to the server
     *
     * For example, convert fields representing ObjectId to: { $oid: string }
     *
     * See: http://www.mongodb.org/display/DOCS/Mongo+Extended+JSON
     */
    toJSON: function(serializeBson) {
      // TODO(mack): consider resolving referenceFields in here

      var obj = this._super('toJSON');
      if (!serializeBson) {
        return obj;
      }

      function serializeOid(value) {
        if (_.isArray(value)) {
          return _.map(value, function(v) {
            return { $oid: v };
          });
        } else if (value) {
          return { $oid: value };
        }
      }

      function serializeDate(value) {
        if (_.isArray(value)) {
          return _.map(value, function(v) {
            return { $date: v.getTime() };
          });
        } else if (value) {
          return { $date: value.getTime() };
        }
      }

      _.each(this._oidFields, function(__, key) {
        obj[key] = serializeOid(obj[key]);
      });

      _.each(this._dateFields, function(__, key) {
        obj[key] = serializeDate(obj[key]);
      });

      return obj;
    },

    getReferenceFields: function() {
      if (_.isFunction(this.referenceFields)) {
        return this.referenceFields();
      } else {
        return this.referenceFields || {};
      }
    },

    getCachedReferenceKey: function(attr) {
      // Since there is only one _cachedReferences per Model type,
      // we gotta key the cache on the model's cid...until we think
      // of a better way to do this.
      return this.cid + ':' + attr;
    },

    /**
     * TODO(mack): add field to each referenceField item to
     * specify if the reference is optional.
     * TODO(mack): support passing {$oid: "XXX...."} attr
     *
     * Override get to resolve id references to other models.
     * Models should define a referenceFields attribute that
     * that is a map (or a function that returns a map) with
     * the desired reference as the key * and the id field and
     * the collection that it's cached in as the value. For example:
     * referenceFields: function() {
     *   return {
     *    'user': ['user_id', _user.Users]
     *   };
     * }
     */
    get: function(attr) {
      if (attr in this.attributes) {
        return this._super('get', arguments);
      }

      var key = this.getCachedReferenceKey(attr);
      var val;
      if (key in this._cachedReferences) {
        val = this._cachedReferences[key];
      } else {
        var referenceFields = this.getReferenceFields();

        if (!(attr in referenceFields)) {
          return;
        }

        var arr = referenceFields[attr];
        var id = this.get(arr[0]);
        if (id) {
          val = arr[1].getFromCache(id);
          this._cachedReferences[key] = val;
        }
      }
      return val;
    },

    /**
    * Override set to convert ObjectId fields { $oid: string } to string fields
    */
    set: function(key, value, options) {
      var attrs, attr, val;

      // Handle both `"key", value` and `{key: value}` -style arguments.
      if (_.isObject(key) || key === null) {
        attrs = key;
        options = value;
      } else {
        attrs = {};
        attrs[key] = value;
      }

      // Extract attributes and options.
      if (!attrs) {
        return this;
      }
      if (attrs instanceof Model) {
        attrs = attrs.attributes;
      }
      //if (this.idAttribute in attrs && this.constructor._cacheName) {
      //  collectionCaches[this.constructor._cacheName].add(this);
      //}

      for (attr in attrs) {
        if (!attrs.hasOwnProperty(attr)) {
          val = attrs[attr];

          if (val) {
            if (_.isArray(val) && val.length && val[0].$oid) {
              // TODO(mack): This won't work if the field is supposed to objectid
              // field but starts out empty as an empty list. We might need a
              // more explicit way to state the type of each field.

              // Just gonna assume for now that if first item in array is an
              // ObjectId, entire array contains ObjectIds
              attrs[attr] = _.map(val, function(v) {
                return v.$oid;
              });
              this._oidFields[attr] = true;
            } else if (val.$oid) {
              attrs[attr] = val.$oid;
              this._oidFields[attr] = true;
            }

            if (_.isArray(val) && val.length && val[0].$date) {
              attrs[attr] = _.map(val, function(v) {
                return new Date(v.$oid);
              });
              this._dateFields[attr] = true;
            } else if (val.$date) {
              attrs[attr] = new Date(val.$date);
              this._dateFields[attr] = true;
            }
          }

          // During set, check if we are setting over an _id that is associated
          // with a reference field. If so, invalidate the key for the associated
          // reference field.
          // TODO(mack): optimize this
          var referenceFields = this.getReferenceFields();
          var cacheKey;
          _.each(referenceFields, function(arr, key) {
            if (attr === arr[0]) {
              cacheKey = this.getCachedReferenceKey(key);
              return false;
            }
          }, this);
          if (cacheKey && cacheKey in this._cachedReferences) {
            delete this._cachedReferences[cacheKey];
          }
        }
      }

      return this._super('set', [attrs, options]);
    }
  });

  var Collection = Backbone.Collection.extend({});

  var collectionCaches = {};

  Collection.registerCache = function(name) {
    if (!name) {
      console.warn('Cannot create cache because name is empty');
      return;
    }

    if (name in collectionCaches) {
      console.warn('Cannot create cache because a cache with name ' +
          name + ' already exists');
      return;
    }

    this._cacheName = name;
    collectionCaches[name] = {};
  };

  var normalizeId = function(id) {
    return id.$oid || id;
  };

  Collection.addToCache = function(objs) {
    // TODO(mack): enforce obj.id is set?

    if (!_.isArray(objs)) {
      objs = [objs];
    }

    var collection = collectionCaches[this._cacheName];
    _.each(objs, function(obj) {
      collection[normalizeId(obj.id)] = obj;
    });
  };

  Collection.removeFromCache = function(objs) {
    // TODO(mack): enforce obj.id is set?

    if (!_.isArray(objs)) {
      objs = [objs];
    }

    var collection = collectionCaches[this._cacheName];
    _.each(objs, function(obj) {
      delete collection[normalizeId(obj.id)];
    });
  };

  Collection._getOneFromCache = function(id) {
    var coll = collectionCaches[this._cacheName];
    if (!coll) {
      console.warn('Trying to fetch from non-existent cache ' + name);
      return undefined;
    }

    var model = coll[normalizeId(id)];
    if (!model) {
      console.warn('Did not find ' + id + ' in ' + this._cacheName);
      return undefined;
    }
    // model is already a Backbone Model
    if (model.cid) {
      return model;
    }
    return (coll[normalizeId(id)] = new this.prototype.model(model));
  };

  Collection.getFromCache = function(ids) {
    if (!this._cacheName) {
      console.warn('A cache does not exist for this collection type');
      return undefined;
    }

    if (!ids) {
      console.warn('No ids were passed');
      return undefined;
    }

    if (_.isArray(ids)) {
      return new this(_.map(ids, function(id) {
        return this._getOneFromCache(id);
      }, this));
    } else {
      return this._getOneFromCache(ids);
    }
  };

  var View = Backbone.View.extend({
    close: function() {
      this.remove();
      this.unbind();
    }
  });

  var ModalView = View.extend({
    events: {
      'click .close': 'close'
    },

    show: function() {
      // Close any existing modals first
      $('body > .modal-wrapper').each(function(idx, el) {
        var modalView = $(el).data('view');
        modalView.close();
      });

      // Show this modal
      this.render();
      this.$el
        .addClass('modal-wrapper')
        .data('view', this);
      // TODO(mack): maybe should have #modal-container rather than just
      // appending to body
      $('body').append(this.$el);
    }
  });

  /**
   * A base collection view that just renders a collection's contents into an
   * unordered list.
   *
   * Must override this.createItemView to return a view to render an item given
   * an item.
   *
   * Optionally override this.postRender() to do stuff after this.render().
   */
  var CollectionView = View.extend({
    tagName: 'article',

    initialize: function(options) {
      this.viewCallback = options.viewCallback;
      this.itemAttributes = options.itemAttributes;
    },

    /**
     * Along with the model, an additional map of attributes may be passed
     * to the individual items
     */
    createItemView: function(model, itemAttributes) {
      throw "Not implemented";
    },

    postRender: function() {
    },

    render: function() {
      this.$el.empty();
      this.collection.each(function(model) {
        var view = this.createItemView(model, this.itemAttributes);
        view.tagName = 'section';
        // TODO(david): Append all at once for faster DOM rendering
        this.$el.append(view.render().el);
      }, this);

      this.postRender();

      return this;
    }
  });

  // Add _super() helper to each function
  // From: http://pivotallabs.com/users/mbrunsfeld/blog/articles/1999-a-convenient-super-method-for-backbone-js
  (function(klasses) {

    // The super method takes two parameters: a method name
    // and an array of arguments to pass to the overridden method.
    // This is to optimize for the common case of passing 'arguments'.
    function _super(methodName, args) {

      // Keep track of how far up the prototype chain we have traversed,
      // in order to handle nested calls to _super.
      if (!this._superCallObjects) {
        this._superCallObjects = {};
      }
      var currentObject = this._superCallObjects[methodName] || this,
          parentObject  = findSuper(methodName, currentObject);
      this._superCallObjects[methodName] = parentObject;

      var result = parentObject[methodName].apply(this, args || []);
      delete this._superCallObjects[methodName];
      return result;
    }

    // Find the next object up the prototype chain that has a
    // different implementation of the method.
    function findSuper(methodName, childObject) {
      var object = childObject;
      while (object[methodName] === childObject[methodName]) {
        object = object.constructor.__super__;
      }
      return object;
    }

    _.each(klasses, function(klass) {
      klass.prototype._super = _super;
    });

  })([Model, Collection, View, Router]);

  return {
    Router: Router,
    Model: Model,
    Collection: Collection,
    View: View,
    ModalView: ModalView,
    CollectionView: CollectionView
  };
});
<commit_msg>Critical one character diff (I'm bad at things)<commit_after>define(
['ext/backbone', 'ext/jquery', 'ext/underscore'],
function(Backbone, $, _) {

  var Router = Backbone.Router.extend({});

  /**
  * A base backbone model that has been extended to our needs.
  */
  var Model = Backbone.Model.extend({
    // TODO(mack): fix _oidFields so that it is scoped per model
    _oidFields: {},
    _dateFields: {},

    _cachedReferences: {},

    /**
     * Override toJSON to convert appropriate fields to their strict
     * BSON representation before sending to the server
     *
     * For example, convert fields representing ObjectId to: { $oid: string }
     *
     * See: http://www.mongodb.org/display/DOCS/Mongo+Extended+JSON
     */
    toJSON: function(serializeBson) {
      // TODO(mack): consider resolving referenceFields in here

      var obj = this._super('toJSON');
      if (!serializeBson) {
        return obj;
      }

      function serializeOid(value) {
        if (_.isArray(value)) {
          return _.map(value, function(v) {
            return { $oid: v };
          });
        } else if (value) {
          return { $oid: value };
        }
      }

      function serializeDate(value) {
        if (_.isArray(value)) {
          return _.map(value, function(v) {
            return { $date: v.getTime() };
          });
        } else if (value) {
          return { $date: value.getTime() };
        }
      }

      _.each(this._oidFields, function(__, key) {
        obj[key] = serializeOid(obj[key]);
      });

      _.each(this._dateFields, function(__, key) {
        obj[key] = serializeDate(obj[key]);
      });

      return obj;
    },

    getReferenceFields: function() {
      if (_.isFunction(this.referenceFields)) {
        return this.referenceFields();
      } else {
        return this.referenceFields || {};
      }
    },

    getCachedReferenceKey: function(attr) {
      // Since there is only one _cachedReferences per Model type,
      // we gotta key the cache on the model's cid...until we think
      // of a better way to do this.
      return this.cid + ':' + attr;
    },

    /**
     * TODO(mack): add field to each referenceField item to
     * specify if the reference is optional.
     * TODO(mack): support passing {$oid: "XXX...."} attr
     *
     * Override get to resolve id references to other models.
     * Models should define a referenceFields attribute that
     * that is a map (or a function that returns a map) with
     * the desired reference as the key * and the id field and
     * the collection that it's cached in as the value. For example:
     * referenceFields: function() {
     *   return {
     *    'user': ['user_id', _user.Users]
     *   };
     * }
     */
    get: function(attr) {
      if (attr in this.attributes) {
        return this._super('get', arguments);
      }

      var key = this.getCachedReferenceKey(attr);
      var val;
      if (key in this._cachedReferences) {
        val = this._cachedReferences[key];
      } else {
        var referenceFields = this.getReferenceFields();

        if (!(attr in referenceFields)) {
          return;
        }

        var arr = referenceFields[attr];
        var id = this.get(arr[0]);
        if (id) {
          val = arr[1].getFromCache(id);
          this._cachedReferences[key] = val;
        }
      }
      return val;
    },

    /**
    * Override set to convert ObjectId fields { $oid: string } to string fields
    */
    set: function(key, value, options) {
      var attrs, attr, val;

      // Handle both `"key", value` and `{key: value}` -style arguments.
      if (_.isObject(key) || key === null) {
        attrs = key;
        options = value;
      } else {
        attrs = {};
        attrs[key] = value;
      }

      // Extract attributes and options.
      if (!attrs) {
        return this;
      }
      if (attrs instanceof Model) {
        attrs = attrs.attributes;
      }
      //if (this.idAttribute in attrs && this.constructor._cacheName) {
      //  collectionCaches[this.constructor._cacheName].add(this);
      //}

      for (attr in attrs) {
        if (attrs.hasOwnProperty(attr)) {
          val = attrs[attr];

          if (val) {
            if (_.isArray(val) && val.length && val[0].$oid) {
              // TODO(mack): This won't work if the field is supposed to objectid
              // field but starts out empty as an empty list. We might need a
              // more explicit way to state the type of each field.

              // Just gonna assume for now that if first item in array is an
              // ObjectId, entire array contains ObjectIds
              attrs[attr] = _.map(val, function(v) {
                return v.$oid;
              });
              this._oidFields[attr] = true;
            } else if (val.$oid) {
              attrs[attr] = val.$oid;
              this._oidFields[attr] = true;
            }

            if (_.isArray(val) && val.length && val[0].$date) {
              attrs[attr] = _.map(val, function(v) {
                return new Date(v.$oid);
              });
              this._dateFields[attr] = true;
            } else if (val.$date) {
              attrs[attr] = new Date(val.$date);
              this._dateFields[attr] = true;
            }
          }

          // During set, check if we are setting over an _id that is associated
          // with a reference field. If so, invalidate the key for the associated
          // reference field.
          // TODO(mack): optimize this
          var referenceFields = this.getReferenceFields();
          var cacheKey;
          _.each(referenceFields, function(arr, key) {
            if (attr === arr[0]) {
              cacheKey = this.getCachedReferenceKey(key);
              return false;
            }
          }, this);
          if (cacheKey && cacheKey in this._cachedReferences) {
            delete this._cachedReferences[cacheKey];
          }
        }
      }

      return this._super('set', [attrs, options]);
    }
  });

  var Collection = Backbone.Collection.extend({});

  var collectionCaches = {};

  Collection.registerCache = function(name) {
    if (!name) {
      console.warn('Cannot create cache because name is empty');
      return;
    }

    if (name in collectionCaches) {
      console.warn('Cannot create cache because a cache with name ' +
          name + ' already exists');
      return;
    }

    this._cacheName = name;
    collectionCaches[name] = {};
  };

  var normalizeId = function(id) {
    return id.$oid || id;
  };

  Collection.addToCache = function(objs) {
    // TODO(mack): enforce obj.id is set?

    if (!_.isArray(objs)) {
      objs = [objs];
    }

    var collection = collectionCaches[this._cacheName];
    _.each(objs, function(obj) {
      collection[normalizeId(obj.id)] = obj;
    });
  };

  Collection.removeFromCache = function(objs) {
    // TODO(mack): enforce obj.id is set?

    if (!_.isArray(objs)) {
      objs = [objs];
    }

    var collection = collectionCaches[this._cacheName];
    _.each(objs, function(obj) {
      delete collection[normalizeId(obj.id)];
    });
  };

  Collection._getOneFromCache = function(id) {
    var coll = collectionCaches[this._cacheName];
    if (!coll) {
      console.warn('Trying to fetch from non-existent cache ' + name);
      return undefined;
    }

    var model = coll[normalizeId(id)];
    if (!model) {
      console.warn('Did not find ' + id + ' in ' + this._cacheName);
      return undefined;
    }
    // model is already a Backbone Model
    if (model.cid) {
      return model;
    }
    return (coll[normalizeId(id)] = new this.prototype.model(model));
  };

  Collection.getFromCache = function(ids) {
    if (!this._cacheName) {
      console.warn('A cache does not exist for this collection type');
      return undefined;
    }

    if (!ids) {
      console.warn('No ids were passed');
      return undefined;
    }

    if (_.isArray(ids)) {
      return new this(_.map(ids, function(id) {
        return this._getOneFromCache(id);
      }, this));
    } else {
      return this._getOneFromCache(ids);
    }
  };

  var View = Backbone.View.extend({
    close: function() {
      this.remove();
      this.unbind();
    }
  });

  var ModalView = View.extend({
    events: {
      'click .close': 'close'
    },

    show: function() {
      // Close any existing modals first
      $('body > .modal-wrapper').each(function(idx, el) {
        var modalView = $(el).data('view');
        modalView.close();
      });

      // Show this modal
      this.render();
      this.$el
        .addClass('modal-wrapper')
        .data('view', this);
      // TODO(mack): maybe should have #modal-container rather than just
      // appending to body
      $('body').append(this.$el);
    }
  });

  /**
   * A base collection view that just renders a collection's contents into an
   * unordered list.
   *
   * Must override this.createItemView to return a view to render an item given
   * an item.
   *
   * Optionally override this.postRender() to do stuff after this.render().
   */
  var CollectionView = View.extend({
    tagName: 'article',

    initialize: function(options) {
      this.viewCallback = options.viewCallback;
      this.itemAttributes = options.itemAttributes;
    },

    /**
     * Along with the model, an additional map of attributes may be passed
     * to the individual items
     */
    createItemView: function(model, itemAttributes) {
      throw "Not implemented";
    },

    postRender: function() {
    },

    render: function() {
      this.$el.empty();
      this.collection.each(function(model) {
        var view = this.createItemView(model, this.itemAttributes);
        view.tagName = 'section';
        // TODO(david): Append all at once for faster DOM rendering
        this.$el.append(view.render().el);
      }, this);

      this.postRender();

      return this;
    }
  });

  // Add _super() helper to each function
  // From: http://pivotallabs.com/users/mbrunsfeld/blog/articles/1999-a-convenient-super-method-for-backbone-js
  (function(klasses) {

    // The super method takes two parameters: a method name
    // and an array of arguments to pass to the overridden method.
    // This is to optimize for the common case of passing 'arguments'.
    function _super(methodName, args) {

      // Keep track of how far up the prototype chain we have traversed,
      // in order to handle nested calls to _super.
      if (!this._superCallObjects) {
        this._superCallObjects = {};
      }
      var currentObject = this._superCallObjects[methodName] || this,
          parentObject  = findSuper(methodName, currentObject);
      this._superCallObjects[methodName] = parentObject;

      var result = parentObject[methodName].apply(this, args || []);
      delete this._superCallObjects[methodName];
      return result;
    }

    // Find the next object up the prototype chain that has a
    // different implementation of the method.
    function findSuper(methodName, childObject) {
      var object = childObject;
      while (object[methodName] === childObject[methodName]) {
        object = object.constructor.__super__;
      }
      return object;
    }

    _.each(klasses, function(klass) {
      klass.prototype._super = _super;
    });

  })([Model, Collection, View, Router]);

  return {
    Router: Router,
    Model: Model,
    Collection: Collection,
    View: View,
    ModalView: ModalView,
    CollectionView: CollectionView
  };
});
<|endoftext|>