<commit_before>/**
 * Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
 * Copyright (c) 2012, Thomas Tanghus <thomas@tanghus.net>
 * This file is licensed under the Affero General Public License version 3 or later.
 * See the COPYING-README file.
 */

OC.Settings = OC.Settings || {};
OC.Settings.Apps = OC.Settings.Apps || {
	loadApp:function(app) {
		var page = $('#rightcontent');
		page.find('p.license').show();
		page.find('span.name').text(app.name);
		page.find('small.externalapp').text(app.internallabel);
		if (app.version) {
			page.find('span.version').text(app.version);
		} else {
			page.find('span.version').text('');
		}
		page.find('span.score').html(app.score);
		page.find('p.description').text(app.description);
		page.find('img.preview').attr('src', app.preview);
		if (app.preview && app.preview.length) {
			page.find('img.preview').show();
		} else {
			page.find('img.preview').hide();
		}
		page.find('small.externalapp').attr('style', 'visibility:visible');
		page.find('span.author').text(app.author);
		page.find('span.licence').text(app.licence);

		if (app.update !== false) {
			page.find('input.update').show();
			page.find('input.update').data('appid', app.id);
			page.find('input.update').attr('value',t('settings', 'Update to {appversion}', {appversion:app.update}));
		} else {
			page.find('input.update').hide();
		}

		page.find('input.enable').show();
		page.find('input.enable').val((app.active) ? t('settings', 'Disable') : t('settings', 'Enable'));
		page.find('input.enable').data('appid', app.id);
		page.find('input.enable').data('active', app.active);
		if (app.internal === false) {
			page.find('span.score').show();
			page.find('p.appslink').show();
			page.find('a').attr('href', 'http://apps.owncloud.com/content/show.php?content=' + app.id);
			page.find('small.externalapp').hide();
		} else {
			page.find('p.appslink').hide();
			page.find('span.score').hide();
		}
	},
	enableApp:function(appid, active, element) {
		console.log('enableApp:', appid, active, element);
		var appitem=$('#leftcontent li[data-id="'+appid+'"]');
		appData = appitem.data('app');
		appData.active = !active;
		appitem.data('app', appData);
		element.val(t('settings','Please wait....'));
		if(active) {
			$.post(OC.filePath('settings','ajax','disableapp.php'),{appid:appid},function(result) {
				if(!result || result.status !== 'success') {
					OC.dialogs.alert('Error while disabling app', t('core', 'Error'));
				}
				else {
					element.data('active',false);
					OC.Settings.Apps.removeNavigation(appid);
					element.val(t('settings','Enable'));
				}
			},'json');
			$('#leftcontent li[data-id="'+appid+'"]').removeClass('active');
		} else {
			$.post(OC.filePath('settings','ajax','enableapp.php'),{appid:appid},function(result) {
				if(!result || result.status !== 'success') {
					OC.dialogs.alert('Error while enabling app', t('core', 'Error'));
				}
				else {
					OC.Settings.Apps.addNavigation(appid);
					element.data('active',true);
					element.val(t('settings','Disable'));
				}
			},'json')
			.fail(function() {
				OC.dialogs.alert('Error while enabling app', t('core', 'Error'));
				element.data('active',false);
				OC.Settings.Apps.removeNavigation(appid);
				element.val(t('settings','Enable'));
			});
			$('#leftcontent li[data-id="'+appid+'"]').addClass('active');
		}
	},
	updateApp:function(appid, element) {
		console.log('updateApp:', appid, element);
		element.val(t('settings','Updating....'));
		$.post(OC.filePath('settings','ajax','updateapp.php'),{appid:appid},function(result) {
			if(!result || result.status !== 'success') {
				OC.dialogs.alert(t('settings','Error while updating app'),t('settings','Error'));
			}
			else {
				element.val(t('settings','Updated'));
				element.hide();
			}
		},'json');
	},

	insertApp:function(appdata) {
		var applist = $('#leftcontent li');
		var app =
				$('
'
				+ ''
				+ appdata.name+'3rd party
');
		app.data('app', appdata);
		var added = false;
		applist.each(function() {
			if(!parseInt($(this).data('installed')) && $(this).find('a').text().toLowerCase() > appdata.name.toLowerCase()) {
				$(this).before(app);
				added = true;
				return false; // dang, remember this to get out of loop
			}
		});
		if(!added) {
			applist.last().after(app);
		}
		return app;
	},
	removeNavigation: function(appid){
		$.getJSON(OC.filePath('settings', 'ajax', 'navigationdetect.php'), {app: appid}).done(function(response){
			if(response.status === 'success'){
				var navIds=response.nav_ids;
				for(var i=0; i< navIds.length; i++){
					$('#apps').children('li[data-id="'+navIds[i]+'"]').remove();
				}
			}
		});
	},
	addNavigation: function(appid){
		$.getJSON(OC.filePath('settings', 'ajax', 'navigationdetect.php'), {app: appid}).done(function(response){
			if(response.status === 'success'){
				var navEntries=response.nav_entries;
				for(var i=0; i< navEntries.length; i++){
					var entry = navEntries[i];
					var container = $('#apps');

					if(container.children('li[data-id="'+entry.id+'"]').length === 0){
						var li=$('

');
						li.attr('data-id', entry.id);
						var img= $('').attr({ src: entry.icon});
						var a=$('').attr('href', entry.href);
						var filename=$('');
						filename.text(entry.name);
						a.prepend(filename);
						a.prepend(img);
						li.append(a);
						// prepend the new app before the 'More apps' function
						$('#apps-management').before(li);
						if (!SVGSupport() && entry.icon.match(/\.svg$/i)) {
							$(img).addClass('svg');
							replaceSVG();
						}
					}
				}
			}
		});
	}
};

$(document).ready(function(){
	$('#leftcontent li').each(function(index,li){
		var app = OC.get('appData_'+$(li).data('id'));
		$(li).data('app',app);
		$(this).find('span.hidden').remove();
	});
	$('#leftcontent li').keydown(function(event) {
		if (event.which === 13 || event.which === 32) {
			$(event.target).click();
		}
		return false;
	});

	$(document).on('click', '#leftcontent', function(event){
		var tgt = $(event.target);
		if (tgt.is('li') || tgt.is('a')) {
			var item = tgt.is('li') ? $(tgt) : $(tgt).parent();
			var app = item.data('app');
			OC.Settings.Apps.loadApp(app);
		}
		return false;
	});
	$('#rightcontent input.enable').click(function(){
		var element = $(this);
		var appid=$(this).data('appid');
		var active=$(this).data('active');
		if(appid) {
			OC.Settings.Apps.enableApp(appid, active, element);
		}
	});
	$('#rightcontent input.update').click(function(){
		var element = $(this);
		var appid=$(this).data('appid');
		if(appid) {
			OC.Settings.Apps.updateApp(appid, element);
		}
	});

	if(appid) {
		var item = $('#leftcontent li[data-id="'+appid+'"]');
		if(item) {
			item.trigger('click');
			item.addClass('active');
			$('#leftcontent').animate({scrollTop: $(item).offset().top-70}, 'slow','swing');
		}
	}
});
<commit_msg>scroll the app navigation down so the newly added app is seen<commit_after>/**
 * Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
 * Copyright (c) 2012, Thomas Tanghus <thomas@tanghus.net>
 * This file is licensed under the Affero General Public License version 3 or later.
 * See the COPYING-README file.
 */

OC.Settings = OC.Settings || {};
OC.Settings.Apps = OC.Settings.Apps || {
	loadApp:function(app) {
		var page = $('#rightcontent');
		page.find('p.license').show();
		page.find('span.name').text(app.name);
		page.find('small.externalapp').text(app.internallabel);
		if (app.version) {
			page.find('span.version').text(app.version);
		} else {
			page.find('span.version').text('');
		}
		page.find('span.score').html(app.score);
		page.find('p.description').text(app.description);
		page.find('img.preview').attr('src', app.preview);
		if (app.preview && app.preview.length) {
			page.find('img.preview').show();
		} else {
			page.find('img.preview').hide();
		}
		page.find('small.externalapp').attr('style', 'visibility:visible');
		page.find('span.author').text(app.author);
		page.find('span.licence').text(app.licence);

		if (app.update !== false) {
			page.find('input.update').show();
			page.find('input.update').data('appid', app.id);
			page.find('input.update').attr('value',t('settings', 'Update to {appversion}', {appversion:app.update}));
		} else {
			page.find('input.update').hide();
		}

		page.find('input.enable').show();
		page.find('input.enable').val((app.active) ? t('settings', 'Disable') : t('settings', 'Enable'));
		page.find('input.enable').data('appid', app.id);
		page.find('input.enable').data('active', app.active);
		if (app.internal === false) {
			page.find('span.score').show();
			page.find('p.appslink').show();
			page.find('a').attr('href', 'http://apps.owncloud.com/content/show.php?content=' + app.id);
			page.find('small.externalapp').hide();
		} else {
			page.find('p.appslink').hide();
			page.find('span.score').hide();
		}
	},
	enableApp:function(appid, active, element) {
		console.log('enableApp:', appid, active, element);
		var appitem=$('#leftcontent li[data-id="'+appid+'"]');
		appData = appitem.data('app');
		appData.active = !active;
		appitem.data('app', appData);
		element.val(t('settings','Please wait....'));
		if(active) {
			$.post(OC.filePath('settings','ajax','disableapp.php'),{appid:appid},function(result) {
				if(!result || result.status !== 'success') {
					OC.dialogs.alert('Error while disabling app', t('core', 'Error'));
				}
				else {
					element.data('active',false);
					OC.Settings.Apps.removeNavigation(appid);
					element.val(t('settings','Enable'));
				}
			},'json');
			$('#leftcontent li[data-id="'+appid+'"]').removeClass('active');
		} else {
			$.post(OC.filePath('settings','ajax','enableapp.php'),{appid:appid},function(result) {
				if(!result || result.status !== 'success') {
					OC.dialogs.alert('Error while enabling app', t('core', 'Error'));
				}
				else {
					OC.Settings.Apps.addNavigation(appid);
					element.data('active',true);
					element.val(t('settings','Disable'));
				}
			},'json')
			.fail(function() {
				OC.dialogs.alert('Error while enabling app', t('core', 'Error'));
				element.data('active',false);
				OC.Settings.Apps.removeNavigation(appid);
				element.val(t('settings','Enable'));
			});
			$('#leftcontent li[data-id="'+appid+'"]').addClass('active');
		}
	},
	updateApp:function(appid, element) {
		console.log('updateApp:', appid, element);
		element.val(t('settings','Updating....'));
		$.post(OC.filePath('settings','ajax','updateapp.php'),{appid:appid},function(result) {
			if(!result || result.status !== 'success') {
				OC.dialogs.alert(t('settings','Error while updating app'),t('settings','Error'));
			}
			else {
				element.val(t('settings','Updated'));
				element.hide();
			}
		},'json');
	},

	insertApp:function(appdata) {
		var applist = $('#leftcontent li');
		var app =
				$('
'
				+ ''
				+ appdata.name+'3rd party
');
		app.data('app', appdata);
		var added = false;
		applist.each(function() {
			if(!parseInt($(this).data('installed')) && $(this).find('a').text().toLowerCase() > appdata.name.toLowerCase()) {
				$(this).before(app);
				added = true;
				return false; // dang, remember this to get out of loop
			}
		});
		if(!added) {
			applist.last().after(app);
		}
		return app;
	},
	removeNavigation: function(appid){
		$.getJSON(OC.filePath('settings', 'ajax', 'navigationdetect.php'), {app: appid}).done(function(response){
			if(response.status === 'success'){
				var navIds=response.nav_ids;
				for(var i=0; i< navIds.length; i++){
					$('#apps').children('li[data-id="'+navIds[i]+'"]').remove();
				}
			}
		});
	},
	addNavigation: function(appid){
		$.getJSON(OC.filePath('settings', 'ajax', 'navigationdetect.php'), {app: appid}).done(function(response){
			if(response.status === 'success'){
				var navEntries=response.nav_entries;
				for(var i=0; i< navEntries.length; i++){
					var entry = navEntries[i];
					var container = $('#apps');

					if(container.children('li[data-id="'+entry.id+'"]').length === 0){
						var li=$('

');
						li.attr('data-id', entry.id);
						var img= $('').attr({ src: entry.icon});
						var a=$('').attr('href', entry.href);
						var filename=$('');
						filename.text(entry.name);
						a.prepend(filename);
						a.prepend(img);
						li.append(a);
						// prepend the new app before the 'More apps' function
						$('#apps-management').before(li);
						// scroll the app navigation down so the newly added app is seen
						$('#navigation').animate({ scrollTop: $('#apps').height() }, 'slow');
						if (!SVGSupport() && entry.icon.match(/\.svg$/i)) {
							$(img).addClass('svg');
							replaceSVG();
						}
					}
				}
			}
		});
	}
};

$(document).ready(function(){
	$('#leftcontent li').each(function(index,li){
		var app = OC.get('appData_'+$(li).data('id'));
		$(li).data('app',app);
		$(this).find('span.hidden').remove();
	});
	$('#leftcontent li').keydown(function(event) {
		if (event.which === 13 || event.which === 32) {
			$(event.target).click();
		}
		return false;
	});

	$(document).on('click', '#leftcontent', function(event){
		var tgt = $(event.target);
		if (tgt.is('li') || tgt.is('a')) {
			var item = tgt.is('li') ? $(tgt) : $(tgt).parent();
			var app = item.data('app');
			OC.Settings.Apps.loadApp(app);
		}
		return false;
	});
	$('#rightcontent input.enable').click(function(){
		var element = $(this);
		var appid=$(this).data('appid');
		var active=$(this).data('active');
		if(appid) {
			OC.Settings.Apps.enableApp(appid, active, element);
		}
	});
	$('#rightcontent input.update').click(function(){
		var element = $(this);
		var appid=$(this).data('appid');
		if(appid) {
			OC.Settings.Apps.updateApp(appid, element);
		}
	});

	if(appid) {
		var item = $('#leftcontent li[data-id="'+appid+'"]');
		if(item) {
			item.trigger('click');
			item.addClass('active');
			$('#leftcontent').animate({scrollTop: $(item).offset().top-70}, 'slow','swing');
		}
	}
});
<|endoftext|>


<commit_before>/**
 * Copyright 2014 Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

	var SetAssignmentsPageController = ['$scope', 'auth', 'api', 'gameBoardTitles', 'boardSearchOptions', 'boardProcessor', '$rootScope', '$window', '$timeout', '$location', function($scope, auth, api, gameBoardTitles, boardSearchOptions, boardProcessor, $rootScope, $window, $timeout, $location) {
		$rootScope.pageTitle = "Assign Boards";

		$scope.generateGameBoardTitle = gameBoardTitles.generate;
		$scope.boardSearchOptions = boardSearchOptions;

		$scope.myGroups = api.groupManagementEndpoint.get({"archived_groups_only":false}); // get a list of all known unarchived groups for this user.

		$scope.openedAssignPanels = []; // shows those panels currently opened
		$scope.pendingAssignment = {}; // boardId to group id mapping - allows us to know which group is being assigned which board.

		$scope.groupAssignmentInfo = {}; // map of boardId to group list - shows groups currently assigned.

		// allows the view to change which panels are open.
		$scope.toggleAssignPanel = function(board) {
			var indexOfBoard = $scope.openedAssignPanels.indexOf(board.id);
			if ($scope.openedAssignPanels.indexOf(board.id) == -1) {
				$scope.openedAssignPanels.push(board.id);
			} else {
				$scope.openedAssignPanels.splice(indexOfBoard, 1);
			}

			if($scope.myGroups.length == 0)
           		$scope.modals.groupsWarning.show();
		}

		var setDefaultBoardSearchOptions = function(deviceSpecificDefaultField) {
			for (boardSearchParameter in $scope.boardSearchOptions) {
				var boardSearchOption = boardSearchOptions[boardSearchParameter];
				var selectedOptionVariableName = 'selected' + boardSearchParameter.charAt(0).toUpperCase() + boardSearchParameter.slice(1) + 'Option';
				var defaultValueKey = boardSearchOption[deviceSpecificDefaultField];
				$scope[selectedOptionVariableName] = boardSearchOption.values[defaultValueKey];
			}
		};

		var updateBoards = function(limit) {
			var limit = limit || $scope.selectedNoBoardsOption.value;
			$scope.setLoading(true);
			api.userGameBoards($scope.selectedFilterOption.value, $scope.selectedSortOption.value, 0, limit).$promise.then(function(boards) {
				$scope.boards = boards;
				boardProcessor.augmentBoards(boards.results, $scope.user._id);
				updateGroupAssignmentMap($scope.boards.results);
				if ($location.hash()) {
					$scope.toggleAssignPanel({id: $location.hash()});
				}

				$scope.setLoading(false);
			})
		};

		// update boards when filters have been selected
		$scope.$watch("selectedNoBoardsOption", function(newVal, oldVal) {
			if (newVal !== oldVal) {
				updateBoards();
			}
		});
		$scope.$watch("selectedSortOption", function(newVal, oldVal) {
			if (newVal !== oldVal) {
				updateBoards($scope.boards.results.length);
			}
		});

		// update tooltips when this changes.
		$scope.$watch("boards.results", function(newVal, oldVal){
			$timeout(function(){
				Opentip.findElements();
			}, 0);
		}, true);

		// Perform initial load
		setDefaultBoardSearchOptions('cardDefault');
		updateBoards();

		var mergeInProgress = false;
		$scope.loadMore = function() {
			if (mergeInProgress) return;
			mergeInProgress = true;
			$scope.setLoading(true);
			api.userGameBoards($scope.selectedFilterOption.value, $scope.selectedSortOption.value, $scope.boards.results.length).$promise.then(function(newBoards){
				// Augment new boards and merge them into results:
				boardProcessor.augmentBoards(newBoards.results, $scope.user._id);
				updateGroupAssignmentMap(newBoards.results);
				// Remove duplicate boards caused by changing board list in another tab. Test uniqueness on board ID.
				$scope.boards.results = _.unionWith($scope.boards.results, newBoards.results, function(a,b) {return a.id == b.id});
				// Avoid issues if boards have been deleted in another tab:
				if ($scope.boards.totalResults > newBoards.totalResults) {
					$scope.boards.totalResults = newBoards.totalResults;
				}
				$scope.setLoading(false);
				mergeInProgress = false;
			});
		};

		$scope.deleteBoard = function(board){
			lookupAssignedGroups(board).$promise.then(function(groupsAssigned) {
				if (groupsAssigned != null && groupsAssigned.length != 0){
					if ($scope.user.role == "ADMIN" || $scope.user.role == "EVENT_MANAGER") {
						alert("Warning: You currently have groups assigned to this board. If you delete this your groups will still be assigned but you won't be able to unassign them or see the board in your Assigned Boards or My boards page.");
					} else {
						$scope.showToast($scope.toastTypes.Failure, "Board Deletion Not Allowed", "You have groups assigned to this board. To delete this board, you must unassign all groups.");
						return;
					}
				}

				var boardTitle = board.title ? board.title : $scope.generateGameBoardTitle(board);
				// Warn user before deleting
				var confirmation = confirm("You are about to delete "+ boardTitle + " board?");
				if (confirmation){
	       			// TODO: This needs to be reviewed
	       			// Currently reloading boards after delete
					$scope.setLoading(true);
	       			api.deleteGameBoard(board.id).$promise.then(function(){
				        updateBoards($scope.boards.results.length);
				        $scope.setLoading(false);
				        $scope.showToast($scope.toastTypes.Success, "Board Deleted", "You have successfully deleted the board: " + boardTitle);
	       			}).catch(function(e){
						$scope.showToast($scope.toastTypes.Failure, "Board Deletion Failed", "With error message: (" + e.status + ") "+ e.status + ") "+ e.data.errorMessage != undefined ? e.data.errorMessage : "");
	       			});
				}
			})
		}

		var lookupAssignedGroups = function(board) {
			var groups = api.assignments.getAssignedGroups({gameId: board.id});
			return groups;
		}

		var updateGroupAssignmentMap = function(boardsToChange) {
			angular.forEach(boardsToChange, function(board, key){
				$scope.groupAssignmentInfo[board.id] = lookupAssignedGroups(board);
			});
		}

		$scope.getListOfGroups = function(listOfGroups) {
			if (listOfGroups && listOfGroups.length == 0) {
				return "No groups have been assigned."
			}

			var listOfGroupsString = "Board assigned to: ";

			angular.forEach(listOfGroups, function(group, key){
				listOfGroupsString = listOfGroupsString + group.groupName + ", ";
			});

			listOfGroupsString = listOfGroupsString.replace(/,\s*$/, "");

			return listOfGroupsString;
		}

		$scope.assignBoard = function(board) {
			if ($scope.pendingAssignment[board.id]) {
				var dueDate = $scope.pendingAssignment[board.id].dueDate;

				if (dueDate != null) {
					dueDate = Date.UTC(dueDate.getFullYear(), dueDate.getMonth(), dueDate.getDate());
					var today = new Date();
					today.setUTCHours(0, 0, 0, 0);
					if ((dueDate - today) < 0) {
						$scope.showToast($scope.toastTypes.Failure, "Board Assignment Failed", "Error: Due date cannot be in the past.");
						return;
					}
				}

				var groupToAssign = $scope.pendingAssignment[board.id]._id;
				var assignmentToPost = {"gameboardId" : board.id, "groupId": groupToAssign, "dueDate": dueDate}

				api.assignments.assignBoard(assignmentToPost).$promise.then(function(){
					updateGroupAssignmentMap([board]);
					delete $scope.pendingAssignment[board.id]; // remove from pending list.
					$scope.showToast($scope.toastTypes.Success, "Assignment Saved", "This assignment has been saved successfully.");
					$scope.groupAssignmentInfo[board.id].$promise.then(function() {
						$timeout(Opentip.findElements, 0);
					});
				}).catch(function(e){
	        		$scope.showToast($scope.toastTypes.Failure, "Board Assignment Failed", e.data.errorMessage || ("Error " + e.status));
				})
			} else {
	        	$scope.showToast($scope.toastTypes.Failure, "Board Assignment Failed", "Error: Please choose a group.");
	        }
		}

		$scope.unassignBoard = function(group, board) {
			var unassignGroup = $window.confirm('Are you sure you want to unassign this board from this group?');

			if (unassignGroup){
				api.assignments.unassignBoard({gameId: board.id, groupId: group._id}).$promise.then(function(){
					updateGroupAssignmentMap([board]);
					$scope.showToast($scope.toastTypes.Success, "Assignment Deleted", "This assignment has been unset successfully.");
					$scope.groupAssignmentInfo[board.id].$promise.then(function() {
						$timeout(Opentip.findElements, 0);
					});
				}).catch(function(e){
        			$scope.showToast($scope.toastTypes.Failure, "Board Unassignment Failed", e.data.errorMessage || ("Error " + e.status));
				});
			}
		}
	}];

	var MyAssignmentsPageController = ['$scope', 'auth', 'api', 'gameBoardTitles', 'boardProcessor', '$rootScope', '$timeout', '$location', function($scope, auth, api, gameBoardTitles, boardProcessor, $rootScope, $timeout, $location) {

		$scope.setLoading(true);

		$rootScope.pageTitle = "My Assignments";

		$scope.generateGameBoardTitle = gameBoardTitles.generate;

		$scope.myAssignments = {};

		$scope.myAssignments.completed = [];
		$scope.myAssignments.inProgressRecent = [];
		$scope.myAssignments.inProgressOld = [];
		$scope.sortPredicate = null;

		$scope.now = new Date();
		var fourWeeksAgo = new Date($scope.now - (4 * 7 * 24 * 60 * 60 * 1000));
		// Midnight five days ago:
		var fiveDaysAgo = new Date($scope.now);
		fiveDaysAgo.setDate($scope.now.getDate() - 5);
		fiveDaysAgo.setHours(0, 0, 0, 0);

		var extractBoardsFrom = function(assignments) {
			var boards = []
			for (var i = 0; i < assignments.length; i++) {
				var assignment = assignments[i];
				boards.push(assignment.gameboard);
			}
			return boards;
		}

		api.assignments.getMyAssignments().$promise.then(function(assignments) {
			boardsForProcessing = extractBoardsFrom(assignments);
			boardProcessor.augmentBoards(boardsForProcessing, $scope.user._id);
			angular.forEach(assignments, function(assignment, index) {
				if (assignment.gameboard.percentageCompleted < 100) {
					var noDueDateButRecent = !assignment.dueDate && (assignment.creationDate > fourWeeksAgo);
					assignment.overdue = assignment.dueDate && (assignment.dueDate < $scope.now);
					var onlyJustOverdue = assignment.overdue && (assignment.dueDate >= fiveDaysAgo);
					if (noDueDateButRecent || onlyJustOverdue) {
						// Assignment only just overdue, or within last month but no due date.
						$scope.myAssignments.inProgressRecent.push(assignment);
					} else {
						$scope.myAssignments.inProgressOld.push(assignment);
					}
				} else {
					$scope.myAssignments.completed.push(assignment);
				}
			})
			$scope.setLoading(false);
			// Log this in the front end because the count is used in the global nav, which incorrectly caused a log event.
            api.logger.log({
                type : "VIEW_MY_ASSIGNMENTS"
            });
		});

		$scope.setVisibleBoard = function(state){
			if (state === 'IN_PROGRESS_RECENT') {
				$scope.sortPredicate = ['!dueDate', 'dueDate', '-creationDate'];
				$scope.assignmentsVisible = $scope.myAssignments.inProgressRecent;
			} else if (state === 'IN_PROGRESS_OLD') {
				$scope.sortPredicate = ['!dueDate', 'dueDate', '-creationDate'];
				$scope.assignmentsVisible = $scope.myAssignments.inProgressOld;
			} else {
				$scope.sortPredicate = '-creationDate';
				$scope.assignmentsVisible = $scope.myAssignments.completed;
			}
		}

		// Update the currently shown assignments based on URL hash:
		switch ($location.hash()) {
			case "inprogress":
				$scope.setVisibleBoard('IN_PROGRESS_RECENT');
				break;
			case "older":
				$scope.setVisibleBoard('IN_PROGRESS_OLD');
				break;
			case "completed":
				$scope.setVisibleBoard('COMPLETED');
				break;
			default:
				$scope.setVisibleBoard('IN_PROGRESS_RECENT');
		}
	}];

	return {
		SetAssignmentsPageController: SetAssignmentsPageController,
		MyAssignmentsPageController: MyAssignmentsPageController
	};
})<commit_msg>Fix bug in assignment categorisation code<commit_after>/**
 * Copyright 2014 Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

	var SetAssignmentsPageController = ['$scope', 'auth', 'api', 'gameBoardTitles', 'boardSearchOptions', 'boardProcessor', '$rootScope', '$window', '$timeout', '$location', function($scope, auth, api, gameBoardTitles, boardSearchOptions, boardProcessor, $rootScope, $window, $timeout, $location) {
		$rootScope.pageTitle = "Assign Boards";

		$scope.generateGameBoardTitle = gameBoardTitles.generate;
		$scope.boardSearchOptions = boardSearchOptions;

		$scope.myGroups = api.groupManagementEndpoint.get({"archived_groups_only":false}); // get a list of all known unarchived groups for this user.

		$scope.openedAssignPanels = []; // shows those panels currently opened
		$scope.pendingAssignment = {}; // boardId to group id mapping - allows us to know which group is being assigned which board.

		$scope.groupAssignmentInfo = {}; // map of boardId to group list - shows groups currently assigned.

		// allows the view to change which panels are open.
		$scope.toggleAssignPanel = function(board) {
			var indexOfBoard = $scope.openedAssignPanels.indexOf(board.id);
			if ($scope.openedAssignPanels.indexOf(board.id) == -1) {
				$scope.openedAssignPanels.push(board.id);
			} else {
				$scope.openedAssignPanels.splice(indexOfBoard, 1);
			}

			if($scope.myGroups.length == 0)
           		$scope.modals.groupsWarning.show();
		}

		var setDefaultBoardSearchOptions = function(deviceSpecificDefaultField) {
			for (boardSearchParameter in $scope.boardSearchOptions) {
				var boardSearchOption = boardSearchOptions[boardSearchParameter];
				var selectedOptionVariableName = 'selected' + boardSearchParameter.charAt(0).toUpperCase() + boardSearchParameter.slice(1) + 'Option';
				var defaultValueKey = boardSearchOption[deviceSpecificDefaultField];
				$scope[selectedOptionVariableName] = boardSearchOption.values[defaultValueKey];
			}
		};

		var updateBoards = function(limit) {
			var limit = limit || $scope.selectedNoBoardsOption.value;
			$scope.setLoading(true);
			api.userGameBoards($scope.selectedFilterOption.value, $scope.selectedSortOption.value, 0, limit).$promise.then(function(boards) {
				$scope.boards = boards;
				boardProcessor.augmentBoards(boards.results, $scope.user._id);
				updateGroupAssignmentMap($scope.boards.results);
				if ($location.hash()) {
					$scope.toggleAssignPanel({id: $location.hash()});
				}

				$scope.setLoading(false);
			})
		};

		// update boards when filters have been selected
		$scope.$watch("selectedNoBoardsOption", function(newVal, oldVal) {
			if (newVal !== oldVal) {
				updateBoards();
			}
		});
		$scope.$watch("selectedSortOption", function(newVal, oldVal) {
			if (newVal !== oldVal) {
				updateBoards($scope.boards.results.length);
			}
		});

		// update tooltips when this changes.
		$scope.$watch("boards.results", function(newVal, oldVal){
			$timeout(function(){
				Opentip.findElements();
			}, 0);
		}, true);

		// Perform initial load
		setDefaultBoardSearchOptions('cardDefault');
		updateBoards();

		var mergeInProgress = false;
		$scope.loadMore = function() {
			if (mergeInProgress) return;
			mergeInProgress = true;
			$scope.setLoading(true);
			api.userGameBoards($scope.selectedFilterOption.value, $scope.selectedSortOption.value, $scope.boards.results.length).$promise.then(function(newBoards){
				// Augment new boards and merge them into results:
				boardProcessor.augmentBoards(newBoards.results, $scope.user._id);
				updateGroupAssignmentMap(newBoards.results);
				// Remove duplicate boards caused by changing board list in another tab. Test uniqueness on board ID.
				$scope.boards.results = _.unionWith($scope.boards.results, newBoards.results, function(a,b) {return a.id == b.id});
				// Avoid issues if boards have been deleted in another tab:
				if ($scope.boards.totalResults > newBoards.totalResults) {
					$scope.boards.totalResults = newBoards.totalResults;
				}
				$scope.setLoading(false);
				mergeInProgress = false;
			});
		};

		$scope.deleteBoard = function(board){
			lookupAssignedGroups(board).$promise.then(function(groupsAssigned) {
				if (groupsAssigned != null && groupsAssigned.length != 0){
					if ($scope.user.role == "ADMIN" || $scope.user.role == "EVENT_MANAGER") {
						alert("Warning: You currently have groups assigned to this board. If you delete this your groups will still be assigned but you won't be able to unassign them or see the board in your Assigned Boards or My boards page.");
					} else {
						$scope.showToast($scope.toastTypes.Failure, "Board Deletion Not Allowed", "You have groups assigned to this board. To delete this board, you must unassign all groups.");
						return;
					}
				}

				var boardTitle = board.title ? board.title : $scope.generateGameBoardTitle(board);
				// Warn user before deleting
				var confirmation = confirm("You are about to delete "+ boardTitle + " board?");
				if (confirmation){
	       			// TODO: This needs to be reviewed
	       			// Currently reloading boards after delete
					$scope.setLoading(true);
	       			api.deleteGameBoard(board.id).$promise.then(function(){
				        updateBoards($scope.boards.results.length);
				        $scope.setLoading(false);
				        $scope.showToast($scope.toastTypes.Success, "Board Deleted", "You have successfully deleted the board: " + boardTitle);
	       			}).catch(function(e){
						$scope.showToast($scope.toastTypes.Failure, "Board Deletion Failed", "With error message: (" + e.status + ") "+ e.status + ") "+ e.data.errorMessage != undefined ? e.data.errorMessage : "");
	       			});
				}
			})
		}

		var lookupAssignedGroups = function(board) {
			var groups = api.assignments.getAssignedGroups({gameId: board.id});
			return groups;
		}

		var updateGroupAssignmentMap = function(boardsToChange) {
			angular.forEach(boardsToChange, function(board, key){
				$scope.groupAssignmentInfo[board.id] = lookupAssignedGroups(board);
			});
		}

		$scope.getListOfGroups = function(listOfGroups) {
			if (listOfGroups && listOfGroups.length == 0) {
				return "No groups have been assigned."
			}

			var listOfGroupsString = "Board assigned to: ";

			angular.forEach(listOfGroups, function(group, key){
				listOfGroupsString = listOfGroupsString + group.groupName + ", ";
			});

			listOfGroupsString = listOfGroupsString.replace(/,\s*$/, "");

			return listOfGroupsString;
		}

		$scope.assignBoard = function(board) {
			if ($scope.pendingAssignment[board.id]) {
				var dueDate = $scope.pendingAssignment[board.id].dueDate;

				if (dueDate != null) {
					dueDate = Date.UTC(dueDate.getFullYear(), dueDate.getMonth(), dueDate.getDate());
					var today = new Date();
					today.setUTCHours(0, 0, 0, 0);
					if ((dueDate - today) < 0) {
						$scope.showToast($scope.toastTypes.Failure, "Board Assignment Failed", "Error: Due date cannot be in the past.");
						return;
					}
				}

				var groupToAssign = $scope.pendingAssignment[board.id]._id;
				var assignmentToPost = {"gameboardId" : board.id, "groupId": groupToAssign, "dueDate": dueDate}

				api.assignments.assignBoard(assignmentToPost).$promise.then(function(){
					updateGroupAssignmentMap([board]);
					delete $scope.pendingAssignment[board.id]; // remove from pending list.
					$scope.showToast($scope.toastTypes.Success, "Assignment Saved", "This assignment has been saved successfully.");
					$scope.groupAssignmentInfo[board.id].$promise.then(function() {
						$timeout(Opentip.findElements, 0);
					});
				}).catch(function(e){
	        		$scope.showToast($scope.toastTypes.Failure, "Board Assignment Failed", e.data.errorMessage || ("Error " + e.status));
				})
			} else {
	        	$scope.showToast($scope.toastTypes.Failure, "Board Assignment Failed", "Error: Please choose a group.");
	        }
		}

		$scope.unassignBoard = function(group, board) {
			var unassignGroup = $window.confirm('Are you sure you want to unassign this board from this group?');

			if (unassignGroup){
				api.assignments.unassignBoard({gameId: board.id, groupId: group._id}).$promise.then(function(){
					updateGroupAssignmentMap([board]);
					$scope.showToast($scope.toastTypes.Success, "Assignment Deleted", "This assignment has been unset successfully.");
					$scope.groupAssignmentInfo[board.id].$promise.then(function() {
						$timeout(Opentip.findElements, 0);
					});
				}).catch(function(e){
        			$scope.showToast($scope.toastTypes.Failure, "Board Unassignment Failed", e.data.errorMessage || ("Error " + e.status));
				});
			}
		}
	}];

	var MyAssignmentsPageController = ['$scope', 'auth', 'api', 'gameBoardTitles', 'boardProcessor', '$rootScope', '$timeout', '$location', function($scope, auth, api, gameBoardTitles, boardProcessor, $rootScope, $timeout, $location) {

		$scope.setLoading(true);

		$rootScope.pageTitle = "My Assignments";

		$scope.generateGameBoardTitle = gameBoardTitles.generate;

		$scope.myAssignments = {};

		$scope.myAssignments.completed = [];
		$scope.myAssignments.inProgressRecent = [];
		$scope.myAssignments.inProgressOld = [];
		$scope.sortPredicate = null;

		$scope.now = new Date();
		var fourWeeksAgo = new Date($scope.now - (4 * 7 * 24 * 60 * 60 * 1000));
		// Midnight five days ago:
		var fiveDaysAgo = new Date($scope.now);
		fiveDaysAgo.setDate($scope.now.getDate() - 5);
		fiveDaysAgo.setHours(0, 0, 0, 0);

		var extractBoardsFrom = function(assignments) {
			var boards = []
			for (var i = 0; i < assignments.length; i++) {
				var assignment = assignments[i];
				boards.push(assignment.gameboard);
			}
			return boards;
		}

		api.assignments.getMyAssignments().$promise.then(function(assignments) {
			boardsForProcessing = extractBoardsFrom(assignments);
			boardProcessor.augmentBoards(boardsForProcessing, $scope.user._id);
			angular.forEach(assignments, function(assignment, index) {
				if (assignment.gameboard.percentageCompleted < 100) {
					var noDueDateButRecent = !assignment.dueDate && (assignment.creationDate > fourWeeksAgo);
					var dueDateAndCurrent = assignment.dueDate && (assignment.dueDate >= fiveDaysAgo);
					if (noDueDateButRecent || dueDateAndCurrent) {
						// Assignment either not/only just overdue, or else set within last month but no due date.
						$scope.myAssignments.inProgressRecent.push(assignment);
					} else {
						$scope.myAssignments.inProgressOld.push(assignment);
					}
				} else {
					$scope.myAssignments.completed.push(assignment);
				}
			})
			$scope.setLoading(false);
			// Log this in the front end because the count is used in the global nav, which incorrectly caused a log event.
            api.logger.log({
                type : "VIEW_MY_ASSIGNMENTS"
            });
		});

		$scope.setVisibleBoard = function(state){
			if (state === 'IN_PROGRESS_RECENT') {
				$scope.sortPredicate = ['!dueDate', 'dueDate', '-creationDate'];
				$scope.assignmentsVisible = $scope.myAssignments.inProgressRecent;
			} else if (state === 'IN_PROGRESS_OLD') {
				$scope.sortPredicate = ['!dueDate', 'dueDate', '-creationDate'];
				$scope.assignmentsVisible = $scope.myAssignments.inProgressOld;
			} else {
				$scope.sortPredicate = '-creationDate';
				$scope.assignmentsVisible = $scope.myAssignments.completed;
			}
		}

		// Update the currently shown assignments based on URL hash:
		switch ($location.hash()) {
			case "inprogress":
				$scope.setVisibleBoard('IN_PROGRESS_RECENT');
				break;
			case "older":
				$scope.setVisibleBoard('IN_PROGRESS_OLD');
				break;
			case "completed":
				$scope.setVisibleBoard('COMPLETED');
				break;
			default:
				$scope.setVisibleBoard('IN_PROGRESS_RECENT');
		}
	}];

	return {
		SetAssignmentsPageController: SetAssignmentsPageController,
		MyAssignmentsPageController: MyAssignmentsPageController
	};
})<|endoftext|>


<commit_before>import 'source-map-support/register';

import '../olm-loader';

import Crypto from '../../lib/crypto';
import expect from 'expect';

import WebStorageSessionStore from '../../lib/store/session/webstorage';
import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../MockStorageApi';
import TestClient from '../TestClient';
import {MatrixEvent} from '../../lib/models/event';
import Room from '../../lib/models/room';
import olmlib from '../../lib/crypto/olmlib';

const EventEmitter = require("events").EventEmitter;

const sdk = require("../..");

const Olm = global.Olm;

describe("Crypto", function() {
    if (!sdk.CRYPTO_ENABLED) {
        return;
    }

    beforeEach(function(done) {
        Olm.init().then(done);
    });

    it("Crypto exposes the correct olm library version", function() {
        expect(Crypto.getOlmVersion()[0]).toEqual(3);
    });

    describe('Session management', function() {
        const otkResponse = {
            one_time_keys: {
                '@alice:home.server': {
                    aliceDevice: {
                        'signed_curve25519:FLIBBLE': {
                            key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
                            signatures: {
                                '@alice:home.server': {
                                    'ed25519:aliceDevice': 'totally a valid signature',
                                },
                            },
                        },
                    },
                },
            },
        };
        let crypto;
        let mockBaseApis;
        let mockRoomList;

        let fakeEmitter;

        beforeEach(async function() {
            const mockStorage = new MockStorageApi();
            const sessionStore = new WebStorageSessionStore(mockStorage);
            const cryptoStore = new MemoryCryptoStore(mockStorage);

            cryptoStore.storeEndToEndDeviceData({
                devices: {
                    '@bob:home.server': {
                        'BOBDEVICE': {
                            keys: {
                                'curve25519:BOBDEVICE': 'this is a key',
                            },
                        },
                    },
                },
                trackingStatus: {},
            });

            mockBaseApis = {
                sendToDevice: expect.createSpy(),
                getKeyBackupVersion: expect.createSpy(),
                isGuest: expect.createSpy(),
            };
            mockRoomList = {};

            fakeEmitter = new EventEmitter();

            crypto = new Crypto(
                mockBaseApis,
                sessionStore,
                "@alice:home.server",
                "FLIBBLE",
                sessionStore,
                cryptoStore,
                mockRoomList,
            );
            crypto.registerEventHandlers(fakeEmitter);
            await crypto.init();
        });

        afterEach(async function() {
            await crypto.stop();
        });

        it("restarts wedged Olm sessions", async function() {
            const prom = new Promise((resolve) => {
                mockBaseApis.claimOneTimeKeys = function() {
                    resolve();
                    return otkResponse;
                };
            });

            fakeEmitter.emit('toDeviceEvent', {
                getType: expect.createSpy().andReturn('m.room.message'),
                getContent: expect.createSpy().andReturn({
                    msgtype: 'm.bad.encrypted',
                }),
                getWireContent: expect.createSpy().andReturn({
                    algorithm: 'm.olm.v1.curve25519-aes-sha2',
                    sender_key: 'this is a key',
                }),
                getSender: expect.createSpy().andReturn('@bob:home.server'),
            });

            await prom;
        });
    });

    describe('Key requests', function() {
        let aliceClient;
        let bobClient;

        beforeEach(async function() {
            aliceClient = (new TestClient(
                "@alice:example.com", "alicedevice",
            )).client;
            bobClient = (new TestClient(
                "@bob:example.com", "bobdevice",
            )).client;
            await aliceClient.initCrypto();
            await bobClient.initCrypto();
        });

        it(
            "does not cancel keyshare requests if some messages are not decrypted",
            async function() {
                function awaitEvent(emitter, event) {
                    return new Promise((resolve, reject) => {
                        emitter.once(event, (result) => {
                            resolve(result);
                        });
                    });
                }

                async function keyshareEventForEvent(event, index) {
                    const eventContent = event.getWireContent();
                    const key = await aliceClient._crypto._olmDevice
                        .getInboundGroupSessionKey(
                            roomId, eventContent.sender_key, eventContent.session_id,
                            index,
                        );
                    const ksEvent = new MatrixEvent({
                        type: "m.forwarded_room_key",
                        sender: "@alice:example.com",
                        content: {
                            algorithm: olmlib.MEGOLM_ALGORITHM,
                            room_id: roomId,
                            sender_key: eventContent.sender_key,
                            sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
                            session_id: eventContent.session_id,
                            session_key: key.key,
                            chain_index: key.chain_index,
                            forwarding_curve25519_key_chain:
                            key.forwarding_curve_key_chain,
                        },
                    });
                    // make onRoomKeyEvent think this was an encrypted event
                    ksEvent._senderCurve25519Key = "akey";
                    return ksEvent;
                }

                const encryptionCfg = {
                    "algorithm": "m.megolm.v1.aes-sha2",
                };
                const roomId = "!someroom";
                const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
                const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
                aliceClient.store.storeRoom(aliceRoom);
                bobClient.store.storeRoom(bobRoom);
                await aliceClient.setRoomEncryption(roomId, encryptionCfg);
                await bobClient.setRoomEncryption(roomId, encryptionCfg);
                const events = [
                    new MatrixEvent({
                        type: "m.room.message",
                        sender: "@alice:example.com",
                        room_id: roomId,
                        event_id: "$1",
                        content: {
                            msgtype: "m.text",
                            body: "1",
                        },
                    }),
                    new MatrixEvent({
                        type: "m.room.message",
                        sender: "@alice:example.com",
                        room_id: roomId,
                        event_id: "$2",
                        content: {
                            msgtype: "m.text",
                            body: "2",
                        },
                    }),
                ];
                await Promise.all(events.map(async (event) => {
                    // alice encrypts each event, and then bob tries to decrypt
                    // them without any keys, so that they'll be in pending
                    await aliceClient._crypto.encryptEvent(event, aliceRoom);
                    event._clearEvent = {};
                    event._senderCurve25519Key = null;
                    event._claimedEd25519Key = null;
                    try {
                        await bobClient._crypto.decryptEvent(event);
                    } catch (e) {
                        // we expect this to fail because we don't have the
                        // decryption keys yet
                    }
                }));

                const bobDecryptor = bobClient._crypto._getRoomDecryptor(
                    roomId, olmlib.MEGOLM_ALGORITHM,
                );

                let eventPromise = Promise.all(events.map((ev) => {
                    return awaitEvent(ev, "Event.decrypted");
                }));

                // keyshare the session key starting at the second message, so
                // the first message can't be decrypted yet, but the second one
                // can
                let ksEvent = await keyshareEventForEvent(events[1], 1);
                await bobDecryptor.onRoomKeyEvent(ksEvent);
                await eventPromise;
                expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
                expect(events[1].getContent().msgtype).toNotBe("m.bad.encrypted");

                const cryptoStore = bobClient._cryptoStore;
                const eventContent = events[0].getWireContent();
                const senderKey = eventContent.sender_key;
                const sessionId = eventContent.session_id;
                const roomKeyRequestBody = {
                    algorithm: olmlib.MEGOLM_ALGORITHM,
                    room_id: roomId,
                    sender_key: senderKey,
                    session_id: sessionId,
                };
                // the room key request should still be there, since we haven't
                // decrypted everything
                expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
                    .toExist();

                // keyshare the session key starting at the first message, so
                // that it can now be decrypted
                eventPromise = awaitEvent(events[0], "Event.decrypted");
                ksEvent = await keyshareEventForEvent(events[0], 0);
                await bobDecryptor.onRoomKeyEvent(ksEvent);
                await eventPromise;
                expect(events[0].getContent().msgtype).toNotBe("m.bad.encrypted");
                // the room key request should be gone since we've now decypted everything
                expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
                    .toNotExist();

                aliceClient.stopClient();
                bobClient.stopClient();
            },
        );
    });
});
<commit_msg>stop client after each test<commit_after>import 'source-map-support/register';

import '../olm-loader';

import Crypto from '../../lib/crypto';
import expect from 'expect';

import WebStorageSessionStore from '../../lib/store/session/webstorage';
import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../MockStorageApi';
import TestClient from '../TestClient';
import {MatrixEvent} from '../../lib/models/event';
import Room from '../../lib/models/room';
import olmlib from '../../lib/crypto/olmlib';

const EventEmitter = require("events").EventEmitter;

const sdk = require("../..");

const Olm = global.Olm;

describe("Crypto", function() {
    if (!sdk.CRYPTO_ENABLED) {
        return;
    }

    beforeEach(function(done) {
        Olm.init().then(done);
    });

    it("Crypto exposes the correct olm library version", function() {
        expect(Crypto.getOlmVersion()[0]).toEqual(3);
    });

    describe('Session management', function() {
        const otkResponse = {
            one_time_keys: {
                '@alice:home.server': {
                    aliceDevice: {
                        'signed_curve25519:FLIBBLE': {
                            key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
                            signatures: {
                                '@alice:home.server': {
                                    'ed25519:aliceDevice': 'totally a valid signature',
                                },
                            },
                        },
                    },
                },
            },
        };
        let crypto;
        let mockBaseApis;
        let mockRoomList;

        let fakeEmitter;

        beforeEach(async function() {
            const mockStorage = new MockStorageApi();
            const sessionStore = new WebStorageSessionStore(mockStorage);
            const cryptoStore = new MemoryCryptoStore(mockStorage);

            cryptoStore.storeEndToEndDeviceData({
                devices: {
                    '@bob:home.server': {
                        'BOBDEVICE': {
                            keys: {
                                'curve25519:BOBDEVICE': 'this is a key',
                            },
                        },
                    },
                },
                trackingStatus: {},
            });

            mockBaseApis = {
                sendToDevice: expect.createSpy(),
                getKeyBackupVersion: expect.createSpy(),
                isGuest: expect.createSpy(),
            };
            mockRoomList = {};

            fakeEmitter = new EventEmitter();

            crypto = new Crypto(
                mockBaseApis,
                sessionStore,
                "@alice:home.server",
                "FLIBBLE",
                sessionStore,
                cryptoStore,
                mockRoomList,
            );
            crypto.registerEventHandlers(fakeEmitter);
            await crypto.init();
        });

        afterEach(async function() {
            await crypto.stop();
        });

        it("restarts wedged Olm sessions", async function() {
            const prom = new Promise((resolve) => {
                mockBaseApis.claimOneTimeKeys = function() {
                    resolve();
                    return otkResponse;
                };
            });

            fakeEmitter.emit('toDeviceEvent', {
                getType: expect.createSpy().andReturn('m.room.message'),
                getContent: expect.createSpy().andReturn({
                    msgtype: 'm.bad.encrypted',
                }),
                getWireContent: expect.createSpy().andReturn({
                    algorithm: 'm.olm.v1.curve25519-aes-sha2',
                    sender_key: 'this is a key',
                }),
                getSender: expect.createSpy().andReturn('@bob:home.server'),
            });

            await prom;
        });
    });

    describe('Key requests', function() {
        let aliceClient;
        let bobClient;

        beforeEach(async function() {
            aliceClient = (new TestClient(
                "@alice:example.com", "alicedevice",
            )).client;
            bobClient = (new TestClient(
                "@bob:example.com", "bobdevice",
            )).client;
            await aliceClient.initCrypto();
            await bobClient.initCrypto();
        });

        afterEach(async function() {
            aliceClient.stopClient();
            bobClient.stopClient();
        });

        it(
            "does not cancel keyshare requests if some messages are not decrypted",
            async function() {
                function awaitEvent(emitter, event) {
                    return new Promise((resolve, reject) => {
                        emitter.once(event, (result) => {
                            resolve(result);
                        });
                    });
                }

                async function keyshareEventForEvent(event, index) {
                    const eventContent = event.getWireContent();
                    const key = await aliceClient._crypto._olmDevice
                        .getInboundGroupSessionKey(
                            roomId, eventContent.sender_key, eventContent.session_id,
                            index,
                        );
                    const ksEvent = new MatrixEvent({
                        type: "m.forwarded_room_key",
                        sender: "@alice:example.com",
                        content: {
                            algorithm: olmlib.MEGOLM_ALGORITHM,
                            room_id: roomId,
                            sender_key: eventContent.sender_key,
                            sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
                            session_id: eventContent.session_id,
                            session_key: key.key,
                            chain_index: key.chain_index,
                            forwarding_curve25519_key_chain:
                            key.forwarding_curve_key_chain,
                        },
                    });
                    // make onRoomKeyEvent think this was an encrypted event
                    ksEvent._senderCurve25519Key = "akey";
                    return ksEvent;
                }

                const encryptionCfg = {
                    "algorithm": "m.megolm.v1.aes-sha2",
                };
                const roomId = "!someroom";
                const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
                const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
                aliceClient.store.storeRoom(aliceRoom);
                bobClient.store.storeRoom(bobRoom);
                await aliceClient.setRoomEncryption(roomId, encryptionCfg);
                await bobClient.setRoomEncryption(roomId, encryptionCfg);
                const events = [
                    new MatrixEvent({
                        type: "m.room.message",
                        sender: "@alice:example.com",
                        room_id: roomId,
                        event_id: "$1",
                        content: {
                            msgtype: "m.text",
                            body: "1",
                        },
                    }),
                    new MatrixEvent({
                        type: "m.room.message",
                        sender: "@alice:example.com",
                        room_id: roomId,
                        event_id: "$2",
                        content: {
                            msgtype: "m.text",
                            body: "2",
                        },
                    }),
                ];
                await Promise.all(events.map(async (event) => {
                    // alice encrypts each event, and then bob tries to decrypt
                    // them without any keys, so that they'll be in pending
                    await aliceClient._crypto.encryptEvent(event, aliceRoom);
                    event._clearEvent = {};
                    event._senderCurve25519Key = null;
                    event._claimedEd25519Key = null;
                    try {
                        await bobClient._crypto.decryptEvent(event);
                    } catch (e) {
                        // we expect this to fail because we don't have the
                        // decryption keys yet
                    }
                }));

                const bobDecryptor = bobClient._crypto._getRoomDecryptor(
                    roomId, olmlib.MEGOLM_ALGORITHM,
                );

                let eventPromise = Promise.all(events.map((ev) => {
                    return awaitEvent(ev, "Event.decrypted");
                }));

                // keyshare the session key starting at the second message, so
                // the first message can't be decrypted yet, but the second one
                // can
                let ksEvent = await keyshareEventForEvent(events[1], 1);
                await bobDecryptor.onRoomKeyEvent(ksEvent);
                await eventPromise;
                expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
                expect(events[1].getContent().msgtype).toNotBe("m.bad.encrypted");

                const cryptoStore = bobClient._cryptoStore;
                const eventContent = events[0].getWireContent();
                const senderKey = eventContent.sender_key;
                const sessionId = eventContent.session_id;
                const roomKeyRequestBody = {
                    algorithm: olmlib.MEGOLM_ALGORITHM,
                    room_id: roomId,
                    sender_key: senderKey,
                    session_id: sessionId,
                };
                // the room key request should still be there, since we haven't
                // decrypted everything
                expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
                    .toExist();

                // keyshare the session key starting at the first message, so
                // that it can now be decrypted
                eventPromise = awaitEvent(events[0], "Event.decrypted");
                ksEvent = await keyshareEventForEvent(events[0], 0);
                await bobDecryptor.onRoomKeyEvent(ksEvent);
                await eventPromise;
                expect(events[0].getContent().msgtype).toNotBe("m.bad.encrypted");
                // the room key request should be gone since we've now decypted everything
                expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
                    .toNotExist();
            },
        );
    });
});
<|endoftext|>


<commit_before>// @flow

const path = require(`path`)
const { store } = require(`../redux`)
const fs = require(`fs`)
const pageDataUtil = require(`../utils/page-data`)
const normalizePagePath = require(`../utils/normalize-page-path`)
const telemetry = require(`gatsby-telemetry`)
const url = require(`url`)
const { createHash } = require(`crypto`)

type QueryResult = {
  id: string,
  result: object,
}

type QueryResultsMap = Map<string, QueryResult>

/**
 * Get cached page query result for given page path.
 * @param {string} pagePath Path to a page.
 * @param {string} directory Root directory of current project.
 */
const getCachedPageData = async (
  pagePath: string,
  directory: string
): QueryResult => {
  const { program } = store.getState()
  const publicDir = path.join(program.directory, `public`)
  try {
    const pageData = await pageDataUtil.read({ publicDir }, pagePath)
    return {
      result: pageData.result,
      id: pagePath,
    }
  } catch (err) {
    console.log(
      `Error loading a result for the page query in "${pagePath}". Query was not run and no cached result was found.`
    )
    return undefined
  }
}

const hashPaths = paths => {
  if (!paths) {
    return undefined
  }
  return paths.map(path => {
    if (!path) {
      return undefined
    }
    return createHash(`sha256`)
      .update(path)
      .digest(`hex`)
  })
}

/**
 * Get cached StaticQuery results for components that Gatsby didn't run query yet.
 * @param {QueryResultsMap} resultsMap Already stored results for queries that don't need to be read from files.
 * @param {string} directory Root directory of current project.
 */
const getCachedStaticQueryResults = (
  resultsMap: QueryResultsMap,
  directory: string
): QueryResultsMap => {
  const cachedStaticQueryResults = new Map()
  const { staticQueryComponents } = store.getState()
  staticQueryComponents.forEach(staticQueryComponent => {
    // Don't read from file if results were already passed from query runner
    if (resultsMap.has(staticQueryComponent.hash)) return
    const filePath = path.join(
      directory,
      `public`,
      `static`,
      `d`,
      `${staticQueryComponent.hash}.json`
    )
    const fileResult = fs.readFileSync(filePath, `utf-8`)
    if (fileResult === `undefined`) {
      console.log(
        `Error loading a result for the StaticQuery in "${
          staticQueryComponent.componentPath
        }". Query was not run and no cached result was found.`
      )
      return
    }
    cachedStaticQueryResults.set(staticQueryComponent.hash, {
      result: JSON.parse(fileResult),
      id: staticQueryComponent.hash,
    })
  })
  return cachedStaticQueryResults
}

const getRoomNameFromPath = (path: string): string => `path-${path}`

class WebsocketManager {
  pageResults: QueryResultsMap
  staticQueryResults: QueryResultsMap
  errors: Map<string, QueryResult>
  isInitialised: boolean
  activePaths: Set<string>
  programDir: string

  constructor() {
    this.isInitialised = false
    this.activePaths = new Set()
    this.pageResults = new Map()
    this.staticQueryResults = new Map()
    this.errors = new Map()
    // this.websocket
    // this.programDir

    this.init = this.init.bind(this)
    this.getSocket = this.getSocket.bind(this)
    this.emitPageData = this.emitPageData.bind(this)
    this.emitStaticQueryData = this.emitStaticQueryData.bind(this)
    this.emitError = this.emitError.bind(this)
    this.connectedClients = 0
  }

  init({ server, directory }) {
    this.programDir = directory

    const cachedStaticQueryResults = getCachedStaticQueryResults(
      this.staticQueryResults,
      this.programDir
    )
    this.staticQueryResults = new Map([
      ...this.staticQueryResults,
      ...cachedStaticQueryResults,
    ])

    this.websocket = require(`socket.io`)(server)

    this.websocket.on(`connection`, s => {
      let activePath = null
      if (
        s &&
        s.handshake &&
        s.handshake.headers &&
        s.handshake.headers.referer
      ) {
        const path = url.parse(s.handshake.headers.referer).path
        if (path) {
          activePath = path
          this.activePaths.add(path)
        }
      }

      this.connectedClients += 1
      // Send already existing static query results
      this.staticQueryResults.forEach(result => {
        this.websocket.send({
          type: `staticQueryResult`,
          payload: result,
        })
      })
      this.errors.forEach((message, errorID) => {
        this.websocket.send({
          type: `overlayError`,
          payload: {
            id: errorID,
            message,
          },
        })
      })

      const leaveRoom = path => {
        s.leave(getRoomNameFromPath(path))
        const leftRoom = this.websocket.sockets.adapter.rooms[
          getRoomNameFromPath(path)
        ]
        if (!leftRoom || leftRoom.length === 0) {
          this.activePaths.delete(path)
        }
      }

      const getDataForPath = async path => {
        if (!this.pageResults.has(path)) {
          const result = await getCachedPageData(path, this.programDir)
          if (result) {
            this.pageResults.set(path, result)
          } else {
            console.log(`Page not found`, path)
            return
          }
        }

        this.websocket.send({
          type: `pageQueryResult`,
          why: `getDataForPath`,
          payload: this.pageResults.get(path),
        })

        telemetry.trackCli(
          `WEBSOCKET_PAGE_DATA_UPDATE`,
          {
            siteMeasurements: {
              clientsCount: this.connectedClients,
              paths: hashPaths(Array.from(this.activePaths)),
            },
          },
          { debounce: true }
        )
      }

      s.on(`getDataForPath`, getDataForPath)

      s.on(`registerPath`, path => {
        s.join(getRoomNameFromPath(path))
        activePath = path
        this.activePaths.add(path)
      })

      s.on(`disconnect`, s => {
        leaveRoom(activePath)
        this.connectedClients -= 1
      })

      s.on(`unregisterPath`, path => {
        leaveRoom(path)
      })
    })

    this.isInitialised = true
  }

  getSocket() {
    return this.isInitialised && this.websocket
  }

  emitStaticQueryData(data: QueryResult) {
    this.staticQueryResults.set(data.id, data)
    if (this.isInitialised) {
      this.websocket.send({ type: `staticQueryResult`, payload: data })
      telemetry.trackCli(
        `WEBSOCKET_EMIT_STATIC_PAGE_DATA_UPDATE`,
        {
          siteMeasurements: {
            clientsCount: this.connectedClients,
            paths: hashPaths(Array.from(this.activePaths)),
          },
        },
        { debounce: true }
      )
    }
  }

  emitPageData(data: QueryResult) {
    data.id = normalizePagePath(data.id)
    this.pageResults.set(data.id, data)
    if (this.isInitialised) {
      this.websocket.send({ type: `pageQueryResult`, payload: data })
      telemetry.trackCli(
        `WEBSOCKET_EMIT_PAGE_DATA_UPDATE`,
        {
          siteMeasurements: {
            clientsCount: this.connectedClients,
            paths: hashPaths(Array.from(this.activePaths)),
          },
        },
        { debounce: true }
      )
    }
  }
  emitError(id: string, message?: string) {
    if (message) {
      this.errors.set(id, message)
    } else {
      this.errors.delete(id)
    }

    if (this.isInitialised) {
      this.websocket.send({ type: `overlayError`, payload: { id, message } })
    }
  }
}

const manager = new WebsocketManager()

module.exports = manager
<commit_msg>fix(gatsby): Check if page exists before trying to get page data (#16220)<commit_after>// @flow

const path = require(`path`)
const { store } = require(`../redux`)
const fs = require(`fs`)
const pageDataUtil = require(`../utils/page-data`)
const normalizePagePath = require(`../utils/normalize-page-path`)
const telemetry = require(`gatsby-telemetry`)
const url = require(`url`)
const { createHash } = require(`crypto`)

type QueryResult = {
  id: string,
  result: object,
}

type QueryResultsMap = Map<string, QueryResult>

const denormalize = path => {
  if (path === undefined) {
    return path
  }
  if (path === `/`) {
    return `/`
  }
  if (path.charAt(path.length - 1) !== `/`) {
    return path + `/`
  }
  return path
}

/**
 * Get cached page query result for given page path.
 * @param {string} pagePath Path to a page.
 * @param {string} directory Root directory of current project.
 */
const getCachedPageData = async (
  pagePath: string,
  directory: string
): QueryResult => {
  const { program, pages } = store.getState()
  const publicDir = path.join(program.directory, `public`)
  if (pages.has(denormalize(pagePath)) || pages.has(pagePath)) {
    try {
      const pageData = await pageDataUtil.read({ publicDir }, pagePath)
      return {
        result: pageData.result,
        id: pagePath,
      }
    } catch (err) {
      console.log(
        `Error loading a result for the page query in "${pagePath}". Query was not run and no cached result was found.`
      )
      return undefined
    }
  }
  return undefined
}

const hashPaths = paths => {
  if (!paths) {
    return undefined
  }
  return paths.map(path => {
    if (!path) {
      return undefined
    }
    return createHash(`sha256`)
      .update(path)
      .digest(`hex`)
  })
}

/**
 * Get cached StaticQuery results for components that Gatsby didn't run query yet.
 * @param {QueryResultsMap} resultsMap Already stored results for queries that don't need to be read from files.
 * @param {string} directory Root directory of current project.
 */
const getCachedStaticQueryResults = (
  resultsMap: QueryResultsMap,
  directory: string
): QueryResultsMap => {
  const cachedStaticQueryResults = new Map()
  const { staticQueryComponents } = store.getState()
  staticQueryComponents.forEach(staticQueryComponent => {
    // Don't read from file if results were already passed from query runner
    if (resultsMap.has(staticQueryComponent.hash)) return
    const filePath = path.join(
      directory,
      `public`,
      `static`,
      `d`,
      `${staticQueryComponent.hash}.json`
    )
    const fileResult = fs.readFileSync(filePath, `utf-8`)
    if (fileResult === `undefined`) {
      console.log(
        `Error loading a result for the StaticQuery in "${
          staticQueryComponent.componentPath
        }". Query was not run and no cached result was found.`
      )
      return
    }
    cachedStaticQueryResults.set(staticQueryComponent.hash, {
      result: JSON.parse(fileResult),
      id: staticQueryComponent.hash,
    })
  })
  return cachedStaticQueryResults
}

const getRoomNameFromPath = (path: string): string => `path-${path}`

class WebsocketManager {
  pageResults: QueryResultsMap
  staticQueryResults: QueryResultsMap
  errors: Map<string, QueryResult>
  isInitialised: boolean
  activePaths: Set<string>
  programDir: string

  constructor() {
    this.isInitialised = false
    this.activePaths = new Set()
    this.pageResults = new Map()
    this.staticQueryResults = new Map()
    this.errors = new Map()
    // this.websocket
    // this.programDir

    this.init = this.init.bind(this)
    this.getSocket = this.getSocket.bind(this)
    this.emitPageData = this.emitPageData.bind(this)
    this.emitStaticQueryData = this.emitStaticQueryData.bind(this)
    this.emitError = this.emitError.bind(this)
    this.connectedClients = 0
  }

  init({ server, directory }) {
    this.programDir = directory

    const cachedStaticQueryResults = getCachedStaticQueryResults(
      this.staticQueryResults,
      this.programDir
    )
    this.staticQueryResults = new Map([
      ...this.staticQueryResults,
      ...cachedStaticQueryResults,
    ])

    this.websocket = require(`socket.io`)(server)

    this.websocket.on(`connection`, s => {
      let activePath = null
      if (
        s &&
        s.handshake &&
        s.handshake.headers &&
        s.handshake.headers.referer
      ) {
        const path = url.parse(s.handshake.headers.referer).path
        if (path) {
          activePath = path
          this.activePaths.add(path)
        }
      }

      this.connectedClients += 1
      // Send already existing static query results
      this.staticQueryResults.forEach(result => {
        this.websocket.send({
          type: `staticQueryResult`,
          payload: result,
        })
      })
      this.errors.forEach((message, errorID) => {
        this.websocket.send({
          type: `overlayError`,
          payload: {
            id: errorID,
            message,
          },
        })
      })

      const leaveRoom = path => {
        s.leave(getRoomNameFromPath(path))
        const leftRoom = this.websocket.sockets.adapter.rooms[
          getRoomNameFromPath(path)
        ]
        if (!leftRoom || leftRoom.length === 0) {
          this.activePaths.delete(path)
        }
      }

      const getDataForPath = async path => {
        if (!this.pageResults.has(path)) {
          const result = await getCachedPageData(path, this.programDir)
          if (result) {
            this.pageResults.set(path, result)
          } else {
            console.log(`Page not found`, path)
            return
          }
        }

        this.websocket.send({
          type: `pageQueryResult`,
          why: `getDataForPath`,
          payload: this.pageResults.get(path),
        })

        telemetry.trackCli(
          `WEBSOCKET_PAGE_DATA_UPDATE`,
          {
            siteMeasurements: {
              clientsCount: this.connectedClients,
              paths: hashPaths(Array.from(this.activePaths)),
            },
          },
          { debounce: true }
        )
      }

      s.on(`getDataForPath`, getDataForPath)

      s.on(`registerPath`, path => {
        s.join(getRoomNameFromPath(path))
        activePath = path
        this.activePaths.add(path)
      })

      s.on(`disconnect`, s => {
        leaveRoom(activePath)
        this.connectedClients -= 1
      })

      s.on(`unregisterPath`, path => {
        leaveRoom(path)
      })
    })

    this.isInitialised = true
  }

  getSocket() {
    return this.isInitialised && this.websocket
  }

  emitStaticQueryData(data: QueryResult) {
    this.staticQueryResults.set(data.id, data)
    if (this.isInitialised) {
      this.websocket.send({ type: `staticQueryResult`, payload: data })
      telemetry.trackCli(
        `WEBSOCKET_EMIT_STATIC_PAGE_DATA_UPDATE`,
        {
          siteMeasurements: {
            clientsCount: this.connectedClients,
            paths: hashPaths(Array.from(this.activePaths)),
          },
        },
        { debounce: true }
      )
    }
  }

  emitPageData(data: QueryResult) {
    data.id = normalizePagePath(data.id)
    this.pageResults.set(data.id, data)
    if (this.isInitialised) {
      this.websocket.send({ type: `pageQueryResult`, payload: data })
      telemetry.trackCli(
        `WEBSOCKET_EMIT_PAGE_DATA_UPDATE`,
        {
          siteMeasurements: {
            clientsCount: this.connectedClients,
            paths: hashPaths(Array.from(this.activePaths)),
          },
        },
        { debounce: true }
      )
    }
  }
  emitError(id: string, message?: string) {
    if (message) {
      this.errors.set(id, message)
    } else {
      this.errors.delete(id)
    }

    if (this.isInitialised) {
      this.websocket.send({ type: `overlayError`, payload: { id, message } })
    }
  }
}

const manager = new WebsocketManager()

module.exports = manager
<|endoftext|>


<commit_before>var mongodb = require('mongodb'),
    MongoClient = require('mongodb').MongoClient,
    Db = require('mongodb').Db,
    Connection = require('mongodb').Connection,
    Server = require('mongodb').Server,
    BSON = require('mongodb').BSON,
    ObjectID = require('mongodb').ObjectID;

BookProvider = function(uri) {
  mongodb.MongoClient.connect(uri, { server: { auto_reconnect: true } }, function (error, database) {
    console.log("mongo db: " + JSON.stringify(database));
  });
  //this.db= new Db('books', new Server(host, port, {safe: false}, {auto_reconnect: true}, {}));
  //this.db.open(function(){});
};

//initialize info values
BookProvider.prototype.init = function(info, callback) {
  this.getCollection(function(error, book_collection) {
    if ( error ) callback(error);
    else book_collection.insert({info:info});
  });
};

BookProvider.prototype.getCollection= function(callback) {
  this.db.collection('bks', function(error, book_collection) {
    if( error ) callback(error);
    else callback(null, book_collection);
  });
};

//find all books
BookProvider.prototype.findAll = function(callback) {
    this.getCollection(function(error, book_collection) {
      if( error ) callback(error);
      else {
        book_collection.find().toArray(function(error, results) {
          if( error ) callback(error);
          else callback(null, results);
        });
      }
    });
};

//find recently edited books
BookProvider.prototype.findRecentEdits = function(callback) {
    this.getCollection(function(error, book_collection) {
      if( error ) callback(error);
      else {
        book_collection.find().sort({last_edit:-1}).limit(8).toArray(function(error, results) {
          if( error ) callback(error);
          else callback(null, results);
        });
      }
    });
};
//find recently edited books
BookProvider.prototype.findRecentLogs = function(callback) {
    this.getCollection(function(error, book_collection) {
      if( error ) callback(error);
      else {
        book_collection.find().sort({created_at:-1}).limit(8).toArray(function(error, results) {
          if( error ) callback(error);
          else callback(null, results);
        });
      }
    });
};

//find subset of books with tag
BookProvider.prototype.findTag = function(tag, callback) {
  this.getCollection(function(error, book_collection) {
    if(error) callback(error);
    else {
      book_collection.find({tags:tag}).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
    }
  });
};

//search all books
BookProvider.prototype.search = function(query, callback) {
  var name = query._field;
  var value = query._query;
  var dbQuery = {};
  dbQuery[name] = {$regex:value, $options:"-i"};
  this.getCollection(function(error, book_collection) {
      book_collection.find(dbQuery).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};

//search books and return subset based on a feild and query (_ref)
BookProvider.prototype.searchRefs = function(query, callback) {
  var name = query._field;
  var value = query._ref;
  var dbQuery = {};
  if (query._refs != undefined) {
    var exclude = query._refs;
    if (exclude.constructor == Array) {
      for (var i = 0; i < exclude.length; i++) {
        var obj = new ObjectID(exclude[i]);
        exclude[i] = obj;
      }
      dbQuery = {_id:{$nin:exclude}};
    } else {
      exclude = new ObjectID(exclude);
      dbQuery = {_id:{$ne:exclude}};
    }
  }
  dbQuery[name] = {$regex:value, $options:"-i"};
  console.log(JSON.stringify(dbQuery));
  this.getCollection(function(error, book_collection) {
      book_collection.find(dbQuery).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};

//search books and return subset based on a feild and query (_ref)
BookProvider.prototype.browseRefs = function(query, callback) {
  var dbQuery = {};
  var f = query._field;
  var q = query[query._field];
  if (f == "_fields") {
      dbQuery[query[query._field]] = {$exists:true};
  } else if (query._field == "_types") {
    dbQuery = {type:q};
  } else if (f == "_tags") {
    dbQuery = {tags:{$in:[q]}};
  }
  if (query._refs != undefined) {
    var exclude = query._refs;
    if (exclude.constructor == Array) {
      for (var i = 0; i < exclude.length; i++) {
        var obj = new ObjectID(exclude[i]);
        exclude[i] = obj;
      }
      dbQuery = {_id:{$nin:exclude}};
    } else {
      exclude = new ObjectID(exclude);
      dbQuery = {_id:{$ne:exclude}};
    }
  }
  console.log(dbQuery);
  this.getCollection(function(error, book_collection) {
      book_collection.find(dbQuery).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};
//search books and return subset based on a feild and query (_ref)
BookProvider.prototype.browse = function(query, callback) {
  var dbQuery = {};
  var f = query._field;
  var q = query[query._field];
  if (f == "_fields") {
      dbQuery[query[query._field]] = {$exists:true};
  } else if (query._field == "_types") {
    dbQuery = {type:q};
  } else if (f == "_tags") {
    dbQuery = {tags:{$in:[q]}};
  }
  console.log(dbQuery);
  this.getCollection(function(error, book_collection) {
      book_collection.find(dbQuery).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};

// get references from single book to display query from id
BookProvider.prototype.getRefs = function(refs, callback) {
  var query = {_id:{$in:[]}};
  for (var i = 0; i < refs.length; i++) {
    var objId = new ObjectID(refs[i].refId);
    query._id["$in"].push(objId);
  }
  this.getCollection(function(error, book_collection) {
      book_collection.find(query).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};

//save new book
BookProvider.prototype.save = function(books, callback) {
    this.getCollection(function(error, book_collection) {
      if( error ) callback(error)
      else {
        if( typeof(books.length)=="undefined")
          books = [books];

        for( var i =0;i< books.length;i++ ) {
          book = books[i];
          book.created_at = new Date();
        }

        book_collection.insert(books, function() {
          callback(null, books);
        });
      }
    });
};

BookProvider.prototype.addType = function(type, callback) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else {
      book_collection.update({info:{$exists:true}}, {$addToSet:{"info._types":type}});
    }
  });
};

// find a book by id
BookProvider.prototype.findById = function(id, callback) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else {
      book_collection.findOne({info:{$exists:true}}, function(error, info) {
          book_collection.findOne({_id:book_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(error, result) {
            if (error) callback(error);
            else callback(null, result, info);
        });
      });
    }
  });
};

// get info
BookProvider.prototype.getInfo = function(callback) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else {
      book_collection.findOne({info:{$exists:true}}, function(error, info) {
          if (error) callback(error);
          else callback(null, info);
      });
    }
  });
};

// add a tag
BookProvider.prototype.addTag = function(query, set) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else book_collection.update(query, set);
  });
};

// referenced by add note
BookProvider.prototype.addReferencedBy = function(refId, id, title, note) {
  var query = {_id:new ObjectID(refId)};
  var set = { referencedBy: {}};
  set["referencedBy"] = {id:id, note:note, title:title};
  this.getCollection(function(error, book_collection){
    if (error) callback(error);
    else {
      book_collection.update(query, {$addToSet:set});
    }
  });
};
// edit referenced by
BookProvider.prototype.editReferencedBy = function(refId, id, title, note) {
  var query = {_id:new ObjectID(refId), referencedBy:{$elemMatch:{id:id}}};
  var set = {};
  set["referencedBy.$.note"] = note;
  this.getCollection(function(error, book_collection){
    if (error) callback(error);
    else {
      book_collection.update(query, {$set:set});
    }
  });
};
// edit reference
BookProvider.prototype.editRef = function(refId, id, title, note) {
  var query = {_id:new ObjectID(refId), refs:{$elemMatch:{id:id}}};
  var set = {};
  set["refs.$.note"] = note;
  this.getCollection(function(error, book_collection){
    if (error) callback(error);
    else {
      book_collection.update(query, {$set:set});
    }
  });
};

// update a book
BookProvider.prototype.update = function(bookId, books, callback) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else {
       book_collection.update(
          {_id:book_collection.db.bson_serializer.ObjectID.createFromHexString(bookId)},
          books,
          function(error, books) {
            if (error) callback(error);
            else callback(null, books);
          });
    }
  });
};

//delete book
BookProvider.prototype.delete = function(bookId, callback) {
        this.getCollection(function(error, book_collection) {
                if(error) callback(error);
                else {
                        book_collection.remove(
                                {_id: book_collection.db.bson_serializer.ObjectID.createFromHexString(bookId)},
                                function(error, book){
                                        if(error) callback(error);
                                        else callback(null, book)
                                });
                        }
        });
};

exports.BookProvider = BookProvider;<commit_msg>log database<commit_after>var mongodb = require('mongodb'),
    MongoClient = require('mongodb').MongoClient,
    Db = require('mongodb').Db,
    Connection = require('mongodb').Connection,
    Server = require('mongodb').Server,
    BSON = require('mongodb').BSON,
    ObjectID = require('mongodb').ObjectID;

BookProvider = function(uri) {
  var db = null;
  mongodb.MongoClient.connect(uri, { server: { auto_reconnect: true } }, function (error, database) {
    if (error) console.log("Error connecting to MongoLab");
    else {
      console.log("mongo db: " + database);
      db = database;
    }
  });
  //this.db= new Db('books', new Server(host, port, {safe: false}, {auto_reconnect: true}, {}));
  //this.db.open(function(){});
};

//initialize info values
BookProvider.prototype.init = function(info, callback) {
  this.getCollection(function(error, book_collection) {
    if ( error ) callback(error);
    else book_collection.insert({info:info});
  });
};

BookProvider.prototype.getCollection= function(callback) {
  this.db.collection('bks', function(error, book_collection) {
    if( error ) callback(error);
    else callback(null, book_collection);
  });
};

//find all books
BookProvider.prototype.findAll = function(callback) {
    this.getCollection(function(error, book_collection) {
      if( error ) callback(error);
      else {
        book_collection.find().toArray(function(error, results) {
          if( error ) callback(error);
          else callback(null, results);
        });
      }
    });
};

//find recently edited books
BookProvider.prototype.findRecentEdits = function(callback) {
    this.getCollection(function(error, book_collection) {
      if( error ) callback(error);
      else {
        book_collection.find().sort({last_edit:-1}).limit(8).toArray(function(error, results) {
          if( error ) callback(error);
          else callback(null, results);
        });
      }
    });
};
//find recently edited books
BookProvider.prototype.findRecentLogs = function(callback) {
    this.getCollection(function(error, book_collection) {
      if( error ) callback(error);
      else {
        book_collection.find().sort({created_at:-1}).limit(8).toArray(function(error, results) {
          if( error ) callback(error);
          else callback(null, results);
        });
      }
    });
};

//find subset of books with tag
BookProvider.prototype.findTag = function(tag, callback) {
  this.getCollection(function(error, book_collection) {
    if(error) callback(error);
    else {
      book_collection.find({tags:tag}).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
    }
  });
};

//search all books
BookProvider.prototype.search = function(query, callback) {
  var name = query._field;
  var value = query._query;
  var dbQuery = {};
  dbQuery[name] = {$regex:value, $options:"-i"};
  this.getCollection(function(error, book_collection) {
      book_collection.find(dbQuery).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};

//search books and return subset based on a feild and query (_ref)
BookProvider.prototype.searchRefs = function(query, callback) {
  var name = query._field;
  var value = query._ref;
  var dbQuery = {};
  if (query._refs != undefined) {
    var exclude = query._refs;
    if (exclude.constructor == Array) {
      for (var i = 0; i < exclude.length; i++) {
        var obj = new ObjectID(exclude[i]);
        exclude[i] = obj;
      }
      dbQuery = {_id:{$nin:exclude}};
    } else {
      exclude = new ObjectID(exclude);
      dbQuery = {_id:{$ne:exclude}};
    }
  }
  dbQuery[name] = {$regex:value, $options:"-i"};
  console.log(JSON.stringify(dbQuery));
  this.getCollection(function(error, book_collection) {
      book_collection.find(dbQuery).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};

//search books and return subset based on a feild and query (_ref)
BookProvider.prototype.browseRefs = function(query, callback) {
  var dbQuery = {};
  var f = query._field;
  var q = query[query._field];
  if (f == "_fields") {
      dbQuery[query[query._field]] = {$exists:true};
  } else if (query._field == "_types") {
    dbQuery = {type:q};
  } else if (f == "_tags") {
    dbQuery = {tags:{$in:[q]}};
  }
  if (query._refs != undefined) {
    var exclude = query._refs;
    if (exclude.constructor == Array) {
      for (var i = 0; i < exclude.length; i++) {
        var obj = new ObjectID(exclude[i]);
        exclude[i] = obj;
      }
      dbQuery = {_id:{$nin:exclude}};
    } else {
      exclude = new ObjectID(exclude);
      dbQuery = {_id:{$ne:exclude}};
    }
  }
  console.log(dbQuery);
  this.getCollection(function(error, book_collection) {
      book_collection.find(dbQuery).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};
//search books and return subset based on a feild and query (_ref)
BookProvider.prototype.browse = function(query, callback) {
  var dbQuery = {};
  var f = query._field;
  var q = query[query._field];
  if (f == "_fields") {
      dbQuery[query[query._field]] = {$exists:true};
  } else if (query._field == "_types") {
    dbQuery = {type:q};
  } else if (f == "_tags") {
    dbQuery = {tags:{$in:[q]}};
  }
  console.log(dbQuery);
  this.getCollection(function(error, book_collection) {
      book_collection.find(dbQuery).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};

// get references from single book to display query from id
BookProvider.prototype.getRefs = function(refs, callback) {
  var query = {_id:{$in:[]}};
  for (var i = 0; i < refs.length; i++) {
    var objId = new ObjectID(refs[i].refId);
    query._id["$in"].push(objId);
  }
  this.getCollection(function(error, book_collection) {
      book_collection.find(query).toArray(function(error, results){
        if (error) callback(error);
        else callback(null, results);
      });
  });
};

//save new book
BookProvider.prototype.save = function(books, callback) {
    this.getCollection(function(error, book_collection) {
      if( error ) callback(error)
      else {
        if( typeof(books.length)=="undefined")
          books = [books];

        for( var i =0;i< books.length;i++ ) {
          book = books[i];
          book.created_at = new Date();
        }

        book_collection.insert(books, function() {
          callback(null, books);
        });
      }
    });
};

BookProvider.prototype.addType = function(type, callback) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else {
      book_collection.update({info:{$exists:true}}, {$addToSet:{"info._types":type}});
    }
  });
};

// find a book by id
BookProvider.prototype.findById = function(id, callback) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else {
      book_collection.findOne({info:{$exists:true}}, function(error, info) {
          book_collection.findOne({_id:book_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(error, result) {
            if (error) callback(error);
            else callback(null, result, info);
        });
      });
    }
  });
};

// get info
BookProvider.prototype.getInfo = function(callback) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else {
      book_collection.findOne({info:{$exists:true}}, function(error, info) {
          if (error) callback(error);
          else callback(null, info);
      });
    }
  });
};

// add a tag
BookProvider.prototype.addTag = function(query, set) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else book_collection.update(query, set);
  });
};

// referenced by add note
BookProvider.prototype.addReferencedBy = function(refId, id, title, note) {
  var query = {_id:new ObjectID(refId)};
  var set = { referencedBy: {}};
  set["referencedBy"] = {id:id, note:note, title:title};
  this.getCollection(function(error, book_collection){
    if (error) callback(error);
    else {
      book_collection.update(query, {$addToSet:set});
    }
  });
};
// edit referenced by
BookProvider.prototype.editReferencedBy = function(refId, id, title, note) {
  var query = {_id:new ObjectID(refId), referencedBy:{$elemMatch:{id:id}}};
  var set = {};
  set["referencedBy.$.note"] = note;
  this.getCollection(function(error, book_collection){
    if (error) callback(error);
    else {
      book_collection.update(query, {$set:set});
    }
  });
};
// edit reference
BookProvider.prototype.editRef = function(refId, id, title, note) {
  var query = {_id:new ObjectID(refId), refs:{$elemMatch:{id:id}}};
  var set = {};
  set["refs.$.note"] = note;
  this.getCollection(function(error, book_collection){
    if (error) callback(error);
    else {
      book_collection.update(query, {$set:set});
    }
  });
};

// update a book
BookProvider.prototype.update = function(bookId, books, callback) {
  this.getCollection(function(error, book_collection) {
    if (error) callback(error);
    else {
       book_collection.update(
          {_id:book_collection.db.bson_serializer.ObjectID.createFromHexString(bookId)},
          books,
          function(error, books) {
            if (error) callback(error);
            else callback(null, books);
          });
    }
  });
};

//delete book
BookProvider.prototype.delete = function(bookId, callback) {
        this.getCollection(function(error, book_collection) {
                if(error) callback(error);
                else {
                        book_collection.remove(
                                {_id: book_collection.db.bson_serializer.ObjectID.createFromHexString(bookId)},
                                function(error, book){
                                        if(error) callback(error);
                                        else callback(null, book)
                                });
                        }
        });
};

exports.BookProvider = BookProvider;<|endoftext|>