<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 *
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */
conceptsService.$inject = ['openmrsRest' ,'$q']

export default function conceptsService(openmrsRest, $q){

	var service = {
			getEmptyConceptObject: getEmptyConceptObject,
			getEmptyLocaleConceptObject: getEmptyLocaleConceptObject,
			postConcept: postConcept,
			getLocales: getLocales,
			getLocaleNames: getLocaleNames,
			getLocaleDescr: getLocaleDescr,
			getEmptyLocalizedConcepts : getEmptyLocalizedConcepts,
			getLocalizedConcepts : getLocalizedConcepts,
			parseNames : parseNames,
			parseDescriptions : parseDescriptions,
	}
	return service

	/**
	 * @returns empty concept object with defined non-localized fields
	 */
	function getEmptyConceptObject(){
		return {
				version : "",
				conceptClass : "",
				datatype : "",
				set : false,
				//numeric data
				hiAbsolute : "",
				hiCritical : "",
				hiNormal : "",
				lowAbsolute : "",
				lowCritical : "",
				lowNormal : "",
				units : "",
				allowDecimal : false,
				displayPrecision : "",
				//arrays of concept data
				answers : [],
				setMembers: [],
				handler : ""
		};
	}
	/**
	 * sends proper POST request to create concept
	 * @concept - concept object, it must contain datatype and conceptClass properties
	 * @localizedConcepts = array of concept localized data objects
	 * @returns result object, contains boolean property success and proper message
	 */
	function postConcept(concept, localizedConcepts){
		//will be serialized to JSON request body
		var conceptRequest = {};
		//contain data about result of postConcept operation
		var result= {};
		var deferred = $q.defer();
		//parse names to array, return error message if no fully specified name
		try{
			conceptRequest.names = parseNames(localizedConcepts);
			conceptRequest.descriptions = parseDescriptions(localizedConcepts);
		}catch(err){
			result.success = false;
			result.message = err;
			deferred.resolve(result);
			return deferred.promise;
		}
		//return error message if conceptClass or datatype is undefined
		if(angular.isDefined(concept.datatype)&&angular.isDefined(concept.conceptClass)){
			conceptRequest.datatype = concept.datatype.uuid;
			conceptRequest.conceptClass = concept.conceptClass.uuid;
		}
		else{
			result.success = false;
			result.message = "concept's datatype and class must be defined!";
			deferred.resolve(result);
			return deferred.promise;
		}

		if(concept.version !== ""){
			conceptRequest.version = concept.version;
		}
		if(concept.set){
			conceptRequest.set = concept.set;
			conceptRequest.setMembers = []
			for(var i =0; i<concept.setMembers.length;i++){
				conceptRequest.setMembers.push(concept.setMembers[i].uuid);
			}
		}
		//if coded
		if(concept.datatype.uuid === "8d4a48b6-c2cc-11de-8d13-0010c6dffd0f"){
			conceptRequest.answers = [];
			for(var i =0; i<concept.answers.length;i++){
				conceptRequest.answers.push(concept.answers[i].uuid);
			}
		}
		//if complex TODO implement
		if(concept.datatype.uuid === "8d4a6242-c2cc-11de-8d13-0010c6dffd0f"){

		}
		//if numeric, add numeric data fields if they are defined
		if(concept.datatype.uuid === "8d4a4488-c2cc-11de-8d13-0010c6dffd0f"){
			if(concept.hiAbsolute)conceptRequest.hiAbsolute = concept.hiAbsolute;
			if(concept.hiCritical)conceptRequest.hiCritical = concept.hiCritical;
			if(concept.hiNormal)conceptRequest.hiNormal = concept.hiNormal;
			if(concept.lowAbsolute)conceptRequest.lowAbsolute = concept.lowAbsolute;
			if(concept.lowCritical)conceptRequest.lowCritical = concept.lowCritical;
			if(concept.lowNormal)conceptRequest.lowNormal = concept.lowNormal;
			if(concept.displayPrecision)conceptRequest.displayPrecision = concept.displayPrecision;
			if(concept.units)conceptRequest.units = concept.units;
			if(concept.allowDecimal !== null)conceptRequest.allowDecimal = concept.allowDecimal;
		}
		//return in then clause to avoid returning undefined
		var conceptJson = angular.toJson(conceptRequest);

        function handleSuccess(success){
        	if(angular.isDefined(success)){
            	result.requestBody = conceptJson;
                result.success = true;
                result.message = success.display + " had been saved";
        	}
        	else{
        		result.success = false;
        		result.message = "Server Error"
        	}
        	deferred.resolve(result);
        };
        function handleException(exception){
        	result.requestBody = conceptJson;
        	result.success = false;
        	result.message = exception.statusText;
            deferred.resolve(result);
        }

		if(angular.isDefined(concept.uuid)){
	        openmrsRest.update('concept', {uuid: concept.uuid}, conceptJson)
				        .then(function(success){handleSuccess(success)},
			  				  function(exception){handleException(exception)});
		}
		else{
	        openmrsRest.create('concept', conceptJson)
	        			.then(function(success){handleSuccess(success)},
	        				  function(exception){handleException(exception)});
		}
        return deferred.promise;

	}
	/**parse names to array of objects, which can be easily serialized to json to make request
	 * @localizedNames array of concept localized data objects
	 * @returns array of names
	 */
	function parseNames(localizedNames){
		var isFullnamePresent = false;
		var names = [];
		angular.forEach(localizedNames, function(key, value){
			var locale = localizedNames[value].locale;
			//add fullname
			if(angular.isDefined(localizedNames[value].fullname)
				&&angular.isDefined(localizedNames[value].fullname.name)
				&&localizedNames[value].fullname.name != ""){

				var isPreferred =
					(localizedNames[value].preferredName.name === localizedNames[value].fullname.name);

				var name = {"name" : localizedNames[value].fullname.name,
							"locale" : locale,
							"conceptNameType" : "FULLY_SPECIFIED",
							"localePreferred" : isPreferred};
				setUuidIfDefined(name, localizedNames[value].fullname);
				names.push(name);
				isFullnamePresent = true;
			}
			if(angular.isDefined(localizedNames[value].shortname)
				&&localizedNames[value].shortname.name != ""){

					var name = {"name" : localizedNames[value].shortname.name,
						"locale" : locale,
						"conceptNameType" : "SHORT"};

					setUuidIfDefined(name, localizedNames[value].shortname);

					names.push(name);

			}
			if(angular.isDefined(localizedNames[value].searchTerms)
					&&localizedNames[value].searchTerms.length > 0){

				for(var j=0; j<localizedNames[value].searchTerms.length;j++){
					if(localizedNames[value].searchTerms[j].name != ""){
						var name = {"name" : localizedNames[value].searchTerms[j].name,
								"locale" : locale,
								"conceptNameType" : "INDEX_TERM"};
						setUuidIfDefined(name, localizedNames[value].searchTerms[j]);
						names.push(name);
					}
				}
			}
			if(angular.isDefined(localizedNames[value].synonyms)
					&&localizedNames[value].synonyms.length > 0){

				for(var j=0; j<localizedNames[value].synonyms.length;j++){
					if(localizedNames[value].synonyms[j].name != ""){

						var isPreferred =
							(localizedNames[value].preferredName.name === localizedNames[value].synonyms[j].name);

						var name = {"name" : localizedNames[value].synonyms[j].name,
									"locale" : locale,
									"localePreferred" : isPreferred};

						setUuidIfDefined(name, localizedNames[value].synonyms[j]);
						names.push(name);
					}
				}
			}
		});
		if(isFullnamePresent) return names;
		else throw "No fully specified name is present!"
	}
	function setUuidIfDefined(requestObj, formObj){
		if(angular.isDefined(formObj.uuid)){
			requestObj.uuid = formObj.uuid;
		}
	}
	/**parse descriptions to array of objects, which can be easily serialized to json to make request
	 * @localizedNames array of concept localized data objects
	 * @returns array of descriptions
	 */
	function parseDescriptions(localizedConcepts){
		var descriptions = [];
		angular.forEach(localizedConcepts, function(key, value){
			//add fullname
			if(angular.isDefined(localizedConcepts[value].description)
					&&localizedConcepts[value].description.description != ""){
				var description = {"description" : localizedConcepts[value].description.description,
									"locale" : localizedConcepts[value].locale};
				setUuidIfDefined(description, localizedConcepts[value].description)
				descriptions.push(description);
			}
		})
		return descriptions
	}
	/**
	 * @names array of names objects of concept
	 * @descriptions array of descriptions objects of concept
	 * @serverLocales array of avalaible locales
	 * @returns array of locales which are contained in any name or description
	 */
	function getLocales (names, descriptions, serverLocales){
		var locales = {};
		//check names
		for(var index=0;index<names.length;index++){
			checkLocale(names[index]);
		}
		//check descriptions
		for(var index=0;index<descriptions.length;index++){
			checkLocale(descriptions[index]);
		}
		//check if input locale is contained by serverLocales
		function checkLocale (input){
			for(var index=0; index<serverLocales.length; index++){
				if(input.locale === serverLocales[index]) locales[serverLocales[index]] = true;
			}
		}
		return Object.keys(locales).sort();
	}

		/**
		 * @names array of names objects of concept
		 * @locale string of locale abbreviation, e.g. "en"
		 * @returns object, which holds full, short name and synonyms for specified locale
		 */
		function getLocaleNames (names, locale){
			//flags for types of locale prefered names
			var shortFlag = "SHORT";
			var fullFlag = "FULLY_SPECIFIED";
			var searchFlag = "INDEX_TERM";
			//holds full, short names and synonyms array
			var localeNames = {};
			localeNames.synonyms = [];
			localeNames.searchTerms = [];
			localeNames.locale = locale;

			for (var index=0;index<names.length;index++){
				if(names[index].locale === locale){
					if(names[index].localePreferred === true) localeNames.preferredName = names[index].name;
					if(names[index].conceptNameType === shortFlag) localeNames.shortname = names[index];
					else if(names[index].conceptNameType === fullFlag){
						localeNames.fullname = names[index];
						localeNames.fullname.valid = true;
					}
					else if(names[index].conceptNameType === searchFlag) localeNames.searchTerms.push(names[index]);
					else localeNames.synonyms.push(names[index]);
				}
			}
		return localeNames;
	};
	/**
	 * @descriptions array of descriptions objects of concept
	 * @locale string of locale abbreviation
	 * @returns string - description of concept for specified locale
	 */
	function getLocaleDescr(descriptions, locale){
		for (var index=0;index<descriptions.length;index++){
			if(descriptions[index].locale === locale){
				return descriptions[index];
			}
		}
	};

	function getLocalizedConcepts(names, descriptions, serverLocales){
		var locales = getLocales(names, descriptions, serverLocales);
		var localizedConcepts = {};
		for (var index=0; index < locales.length; index++){
			localizedConcepts[locales[index]] = getLocaleNames(names, locales[index]);
			localizedConcepts[locales[index]].description = getLocaleDescr(descriptions, locales[index]);
			localizedConcepts[locales[index]].locale = locales[index];
			if(angular.isUndefined(localizedConcepts[locales[index]].fullname)){
				localizedConcepts[locales[index]].fullname = { name : "" };
			}
		}
		//if there's no data for given locale, create empty locale concept object
		for (var index=0; index < serverLocales.length; index++){
			if(angular.isUndefined(localizedConcepts[serverLocales[index]])){
				localizedConcepts[serverLocales[index]] = getEmptyLocaleConceptObject(serverLocales[index]);
			}
		}
		return localizedConcepts;
	}

	function getEmptyLocalizedConcepts(serverLocales){
		var localizedConcepts = {};
		for (var index=0; index < serverLocales.length; index++){
			localizedConcepts[serverLocales[index]] = getEmptyLocaleConceptObject(serverLocales[index]);
		}
		return localizedConcepts;
	}

	/**
	 * function used to delete concept
	 * @param concept to delete
     */
	function deleteConcept(concept) {
		openmrsRest.remove('concept', {uuid : concept.uuid, purge : true});
	}
	/**
	 * @returns empty localized concept data object for one locale
	 */
	function getEmptyLocaleConceptObject(locale){
		var empty = {};
		empty.locale = locale;
		empty.fullname = {};
		empty.fullname.name = "";
		empty.preferredName = "";
		empty.shortname;
		empty.searchTerms = [];
		empty.synonyms = [];
		empty.description = {};
		empty.description.description = ""
		return empty;
	}
}
		<commit_msg>Fixed concept delete forever issue<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 *
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */
conceptsService.$inject = ['openmrsRest' ,'$q']

export default function conceptsService(openmrsRest, $q){

	var service = {
			getEmptyConceptObject: getEmptyConceptObject,
			getEmptyLocaleConceptObject: getEmptyLocaleConceptObject,
			postConcept: postConcept,
			deleteConcept: deleteConcept,
			getLocales: getLocales,
			getLocaleNames: getLocaleNames,
			getLocaleDescr: getLocaleDescr,
			getEmptyLocalizedConcepts : getEmptyLocalizedConcepts,
			getLocalizedConcepts : getLocalizedConcepts,
			parseNames : parseNames,
			parseDescriptions : parseDescriptions,
	}
	return service

	/**
	 * @returns empty concept object with defined non-localized fields
	 */
	function getEmptyConceptObject(){
		return {
				version : "",
				conceptClass : "",
				datatype : "",
				set : false,
				//numeric data
				hiAbsolute : "",
				hiCritical : "",
				hiNormal : "",
				lowAbsolute : "",
				lowCritical : "",
				lowNormal : "",
				units : "",
				allowDecimal : false,
				displayPrecision : "",
				//arrays of concept data
				answers : [],
				setMembers: [],
				handler : ""
		};
	}
	/**
	 * sends proper POST request to create concept
	 * @concept - concept object, it must contain datatype and conceptClass properties
	 * @localizedConcepts = array of concept localized data objects
	 * @returns result object, contains boolean property success and proper message
	 */
	function postConcept(concept, localizedConcepts){
		//will be serialized to JSON request body
		var conceptRequest = {};
		//contain data about result of postConcept operation
		var result= {};
		var deferred = $q.defer();
		//parse names to array, return error message if no fully specified name
		try{
			conceptRequest.names = parseNames(localizedConcepts);
			conceptRequest.descriptions = parseDescriptions(localizedConcepts);
		}catch(err){
			result.success = false;
			result.message = err;
			deferred.resolve(result);
			return deferred.promise;
		}
		//return error message if conceptClass or datatype is undefined
		if(angular.isDefined(concept.datatype)&&angular.isDefined(concept.conceptClass)){
			conceptRequest.datatype = concept.datatype.uuid;
			conceptRequest.conceptClass = concept.conceptClass.uuid;
		}
		else{
			result.success = false;
			result.message = "concept's datatype and class must be defined!";
			deferred.resolve(result);
			return deferred.promise;
		}

		if(concept.version !== ""){
			conceptRequest.version = concept.version;
		}
		if(concept.set){
			conceptRequest.set = concept.set;
			conceptRequest.setMembers = []
			for(var i =0; i<concept.setMembers.length;i++){
				conceptRequest.setMembers.push(concept.setMembers[i].uuid);
			}
		}
		//if coded
		if(concept.datatype.uuid === "8d4a48b6-c2cc-11de-8d13-0010c6dffd0f"){
			conceptRequest.answers = [];
			for(var i =0; i<concept.answers.length;i++){
				conceptRequest.answers.push(concept.answers[i].uuid);
			}
		}
		//if complex TODO implement
		if(concept.datatype.uuid === "8d4a6242-c2cc-11de-8d13-0010c6dffd0f"){

		}
		//if numeric, add numeric data fields if they are defined
		if(concept.datatype.uuid === "8d4a4488-c2cc-11de-8d13-0010c6dffd0f"){
			if(concept.hiAbsolute)conceptRequest.hiAbsolute = concept.hiAbsolute;
			if(concept.hiCritical)conceptRequest.hiCritical = concept.hiCritical;
			if(concept.hiNormal)conceptRequest.hiNormal = concept.hiNormal;
			if(concept.lowAbsolute)conceptRequest.lowAbsolute = concept.lowAbsolute;
			if(concept.lowCritical)conceptRequest.lowCritical = concept.lowCritical;
			if(concept.lowNormal)conceptRequest.lowNormal = concept.lowNormal;
			if(concept.displayPrecision)conceptRequest.displayPrecision = concept.displayPrecision;
			if(concept.units)conceptRequest.units = concept.units;
			if(concept.allowDecimal !== null)conceptRequest.allowDecimal = concept.allowDecimal;
		}
		//return in then clause to avoid returning undefined
		var conceptJson = angular.toJson(conceptRequest);

        function handleSuccess(success){
        	if(angular.isDefined(success)){
            	result.requestBody = conceptJson;
                result.success = true;
                result.message = success.display + " had been saved";
        	}
        	else{
        		result.success = false;
        		result.message = "Server Error"
        	}
        	deferred.resolve(result);
        };
        function handleException(exception){
        	result.requestBody = conceptJson;
        	result.success = false;
        	result.message = exception.statusText;
            deferred.resolve(result);
        }

		if(angular.isDefined(concept.uuid)){
	        openmrsRest.update('concept', {uuid: concept.uuid}, conceptJson)
				        .then(function(success){handleSuccess(success)},
			  				  function(exception){handleException(exception)});
		}
		else{
	        openmrsRest.create('concept', conceptJson)
	        			.then(function(success){handleSuccess(success)},
	        				  function(exception){handleException(exception)});
		}
        return deferred.promise;

	}
	/**parse names to array of objects, which can be easily serialized to json to make request
	 * @localizedNames array of concept localized data objects
	 * @returns array of names
	 */
	function parseNames(localizedNames){
		var isFullnamePresent = false;
		var names = [];
		angular.forEach(localizedNames, function(key, value){
			var locale = localizedNames[value].locale;
			//add fullname
			if(angular.isDefined(localizedNames[value].fullname)
				&&angular.isDefined(localizedNames[value].fullname.name)
				&&localizedNames[value].fullname.name != ""){

				var isPreferred =
					(localizedNames[value].preferredName.name === localizedNames[value].fullname.name);

				var name = {"name" : localizedNames[value].fullname.name,
							"locale" : locale,
							"conceptNameType" : "FULLY_SPECIFIED",
							"localePreferred" : isPreferred};
				setUuidIfDefined(name, localizedNames[value].fullname);
				names.push(name);
				isFullnamePresent = true;
			}
			if(angular.isDefined(localizedNames[value].shortname)
				&&localizedNames[value].shortname.name != ""){

					var name = {"name" : localizedNames[value].shortname.name,
						"locale" : locale,
						"conceptNameType" : "SHORT"};

					setUuidIfDefined(name, localizedNames[value].shortname);

					names.push(name);

			}
			if(angular.isDefined(localizedNames[value].searchTerms)
					&&localizedNames[value].searchTerms.length > 0){

				for(var j=0; j<localizedNames[value].searchTerms.length;j++){
					if(localizedNames[value].searchTerms[j].name != ""){
						var name = {"name" : localizedNames[value].searchTerms[j].name,
								"locale" : locale,
								"conceptNameType" : "INDEX_TERM"};
						setUuidIfDefined(name, localizedNames[value].searchTerms[j]);
						names.push(name);
					}
				}
			}
			if(angular.isDefined(localizedNames[value].synonyms)
					&&localizedNames[value].synonyms.length > 0){

				for(var j=0; j<localizedNames[value].synonyms.length;j++){
					if(localizedNames[value].synonyms[j].name != ""){

						var isPreferred =
							(localizedNames[value].preferredName.name === localizedNames[value].synonyms[j].name);

						var name = {"name" : localizedNames[value].synonyms[j].name,
									"locale" : locale,
									"localePreferred" : isPreferred};

						setUuidIfDefined(name, localizedNames[value].synonyms[j]);
						names.push(name);
					}
				}
			}
		});
		if(isFullnamePresent) return names;
		else throw "No fully specified name is present!"
	}
	function setUuidIfDefined(requestObj, formObj){
		if(angular.isDefined(formObj.uuid)){
			requestObj.uuid = formObj.uuid;
		}
	}
	/**parse descriptions to array of objects, which can be easily serialized to json to make request
	 * @localizedNames array of concept localized data objects
	 * @returns array of descriptions
	 */
	function parseDescriptions(localizedConcepts){
		var descriptions = [];
		angular.forEach(localizedConcepts, function(key, value){
			//add fullname
			if(angular.isDefined(localizedConcepts[value].description)
					&&localizedConcepts[value].description.description != ""){
				var description = {"description" : localizedConcepts[value].description.description,
									"locale" : localizedConcepts[value].locale};
				setUuidIfDefined(description, localizedConcepts[value].description)
				descriptions.push(description);
			}
		})
		return descriptions
	}
	/**
	 * @names array of names objects of concept
	 * @descriptions array of descriptions objects of concept
	 * @serverLocales array of avalaible locales
	 * @returns array of locales which are contained in any name or description
	 */
	function getLocales (names, descriptions, serverLocales){
		var locales = {};
		//check names
		for(var index=0;index<names.length;index++){
			checkLocale(names[index]);
		}
		//check descriptions
		for(var index=0;index<descriptions.length;index++){
			checkLocale(descriptions[index]);
		}
		//check if input locale is contained by serverLocales
		function checkLocale (input){
			for(var index=0; index<serverLocales.length; index++){
				if(input.locale === serverLocales[index]) locales[serverLocales[index]] = true;
			}
		}
		return Object.keys(locales).sort();
	}

		/**
		 * @names array of names objects of concept
		 * @locale string of locale abbreviation, e.g. "en"
		 * @returns object, which holds full, short name and synonyms for specified locale
		 */
		function getLocaleNames (names, locale){
			//flags for types of locale prefered names
			var shortFlag = "SHORT";
			var fullFlag = "FULLY_SPECIFIED";
			var searchFlag = "INDEX_TERM";
			//holds full, short names and synonyms array
			var localeNames = {};
			localeNames.synonyms = [];
			localeNames.searchTerms = [];
			localeNames.locale = locale;

			for (var index=0;index<names.length;index++){
				if(names[index].locale === locale){
					if(names[index].localePreferred === true) localeNames.preferredName = names[index].name;
					if(names[index].conceptNameType === shortFlag) localeNames.shortname = names[index];
					else if(names[index].conceptNameType === fullFlag){
						localeNames.fullname = names[index];
						localeNames.fullname.valid = true;
					}
					else if(names[index].conceptNameType === searchFlag) localeNames.searchTerms.push(names[index]);
					else localeNames.synonyms.push(names[index]);
				}
			}
		return localeNames;
	};
	/**
	 * @descriptions array of descriptions objects of concept
	 * @locale string of locale abbreviation
	 * @returns string - description of concept for specified locale
	 */
	function getLocaleDescr(descriptions, locale){
		for (var index=0;index<descriptions.length;index++){
			if(descriptions[index].locale === locale){
				return descriptions[index];
			}
		}
	};

	function getLocalizedConcepts(names, descriptions, serverLocales){
		var locales = getLocales(names, descriptions, serverLocales);
		var localizedConcepts = {};
		for (var index=0; index < locales.length; index++){
			localizedConcepts[locales[index]] = getLocaleNames(names, locales[index]);
			localizedConcepts[locales[index]].description = getLocaleDescr(descriptions, locales[index]);
			localizedConcepts[locales[index]].locale = locales[index];
			if(angular.isUndefined(localizedConcepts[locales[index]].fullname)){
				localizedConcepts[locales[index]].fullname = { name : "" };
			}
		}
		//if there's no data for given locale, create empty locale concept object
		for (var index=0; index < serverLocales.length; index++){
			if(angular.isUndefined(localizedConcepts[serverLocales[index]])){
				localizedConcepts[serverLocales[index]] = getEmptyLocaleConceptObject(serverLocales[index]);
			}
		}
		return localizedConcepts;
	}

	function getEmptyLocalizedConcepts(serverLocales){
		var localizedConcepts = {};
		for (var index=0; index < serverLocales.length; index++){
			localizedConcepts[serverLocales[index]] = getEmptyLocaleConceptObject(serverLocales[index]);
		}
		return localizedConcepts;
	}

	/**
	 * function used to delete concept
	 * @param concept to delete
     */
	function deleteConcept(concept) {
		openmrsRest.remove('concept', {uuid : concept.uuid, purge : true});
	}
	/**
	 * @returns empty localized concept data object for one locale
	 */
	function getEmptyLocaleConceptObject(locale){
		var empty = {};
		empty.locale = locale;
		empty.fullname = {};
		empty.fullname.name = "";
		empty.preferredName = "";
		empty.shortname;
		empty.searchTerms = [];
		empty.synonyms = [];
		empty.description = {};
		empty.description.description = ""
		return empty;
	}
}
		<|endoftext|>


<commit_before>var App = Ember.Application.create({
  rootElement: '#builds-application'
});

App.Router.map(function() {
  this.route('release');
  this.route('beta');
  this.route('canary');
  this.route('tagged');
});

App.Router.reopen({
  notifyGoogleAnalytics: function() {
    var url = this.get('url');

    // Add a slash if neccesary
    if (!/^\//.test(url)){ url = '/' + url; }

    _gaq.push(['_trackPageview', '/builds' + url]);
  }.on('didTransition')
});

App.CopyClipboardComponent = Ember.Component.extend({
  tagName: 'span',
  hasFlash: ZeroClipboard.detectFlashSupport(),

  didInsertElement: function () {
    var clip = new ZeroClipboard(this.$('button'), {
      // This would normally be a relative path
      moviePath: "/images/ZeroClipboard.swf",

      trustedDomains: ["*"],
      allowScriptAccess: "always"
    });

    clip.on('mousedown', function(client, e) {
      Em.run.later(this, function() {
        $(this).removeClass('loading');
        $(this).removeAttr('disabled');
      }, 1000);
      Em.run.next(this, function() {
        $(this).addClass('loading');
        $(this).attr('disabled', 'disabled');
      });
    });

    this.$('input').on('click', function() {
      $(this).select();
    });
  }
});

App.S3Bucket = Ember.Object.extend({
  files: [],
  isLoading: false,

  // Setup these as default values.
  // They can be overridden on create.
  queryUseSSL: true,
  objectUseSSL: false,
  delimiter: '/',
  bucket: 'builds.emberjs.com',
  endpoint: 's3.amazonaws.com',

  delimiterParameter: function(){
    var delimiter = this.getWithDefault('delimiter','').toString();
    return (delimiter) ? 'delimiter=' + delimiter : '';
  }.property('delimiter'),

  markerParameter: function(){
    return 'marker=' + this.getWithDefault('marker','').toString();
  }.property('marker'),

  maxKeysParameter: function(){
    return 'max-keys=' + this.getWithDefault('maxKeys','').toString();
  }.property('maxKeys'),

  prefixParameter: function(){
    return 'prefix=' + this.getWithDefault('prefix','').toString();
  }.property('prefix'),

  queryProtocol: function() {
    return this.get('queryUseSSL') ? 'https://' : 'http://';
  }.property('queryUseSSL'),

  queryBaseUrl: function(){
    return this.get('queryProtocol') + this.get('endpoint') + '/' + this.get('bucket')
  }.property('queryProtocol', 'endpoint', 'bucket'),

  objectProtocol: function() {
    return this.get('objectUseSSL') ? 'https://' : 'http://';
  }.property('objectUseSSL'),

  objectBaseUrl: function(){
    return this.get('objectProtocol') + this.get('bucket');
  }.property('objectProtocol', 'bucket'),

  queryParams: function(){
    return this.get('delimiterParameter')  + '&' +
      this.get('markerParameter')     + '&' +
      this.get('maxKeysParameter')    + '&' +
      this.get('prefixParameter');
  }.property('delimiterParameter','markerParameter','maxKeysParameter','prefixParameter'),

  queryUrl: function(){
    return this.get('queryBaseUrl') + '?' + this.get('queryParams');
  }.property('queryBaseUrl','queryParams'),

  filesPresent: function(){
    return this.get('files').length;
  }.property('files.@each'),

  filterFiles: function(filter){
    var files = this.get('files');

    return files.filter(function(e) {
      return e.get('name').indexOf(filter + '.') !== -1;
    });
  },

  load: function(){
    var self = this,
    baseUrl = this.get('objectBaseUrl');

    this.set('isLoading', true);
    Ember.$.get(this.get('queryUrl'), function(data){
      self.set('isLoading', false);
      self.set('response', data);

      var contents = data.getElementsByTagName('Contents'),
      length   = contents.length,
      files    = [];

      for(var i = 0; i < length; i++) {
        var size = contents[i].getElementsByTagName('Size')[0].firstChild.data,
        name = contents[i].getElementsByTagName('Key')[0].firstChild.data,
        lastModified = new Date(contents[i].getElementsByTagName('LastModified')[0].firstChild.data);

        files.push(
          App.S3File.create({
          name: name,
          size: size,
          lastModified: lastModified,
          relativePath: name,
          baseUrl: baseUrl
        })
        );
      }

      self.set('files', files.sort(function(a,b){
        return b.lastModified - a.lastModified;
      }));
    });
  }.observes('queryUrl').on('init')
});

App.S3File = Ember.Object.extend({
  scriptTag: function(){
    var escapedURL = Handlebars.Utils.escapeExpression(this.get('url'));

    return new Handlebars.SafeString('<script src="' + escapedURL + '"></script>').toString();
  }.property('url'),

  url: function(){
    return this.get('baseUrl') + '/' + this.get('relativePath');
  }.property('baseUrl', 'relativePath')
});

App.Project = Ember.Object.extend();

App.Project.reopenClass({
  FIXTURES:
    [ {
      projectName: 'Ember',
      projectFilter: 'ember',
      projectRepo: 'emberjs/ember.js',
      channel: "tagged"
    }, {
      projectName: 'Ember Data',
      projectFilter: 'ember-data',
      projectRepo: 'emberjs/data',
      channel: "tagged"
    }, {
      projectName: "Ember",
      projectFilter: "ember",
      projectRepo: 'emberjs/ember.js',
      initialVersion: "1.5.0",
      initialReleaseDate: "2014-03-29",
      lastRelease: "1.5.0",
      futureVersion: "1.5.1",
      channel: "release",
      date: "2014-03-29",
      changelogPath: "CHANGELOG.md"
    }, {
      projectName: "Ember",
      projectFilter: "ember",
      projectRepo: 'emberjs/ember.js',
      lastRelease: "1.6.0-beta.1",
      futureVersion: "1.6.0-beta.2",
      finalVersion: '1.6.0',
      channel: "beta",
      cycleEstimatedFinishDate: '2014-05-06',
      date: "2014-03-31",
      nextDate: "2014-04-07",
      changelogPath: "CHANGELOG.md"
    }, {
      projectName: "Ember Data",
      projectFilter: "ember-data",
      projectRepo: 'emberjs/data',
      lastRelease: "1.0.0-beta.7",
      futureVersion: "1.0.0-beta.8",
      channel: "beta",
      date: "2014-02-20",
      changelogPath: "CHANGELOG.md"
    }, {
      projectName: "Ember",
      projectFilter: "ember",
      projectRepo: 'emberjs/ember.js',
      channel: "canary",
    }, {
      projectName: "Ember Data",
      projectFilter: "ember-data",
      projectRepo: 'emberjs/data',
      channel: "canary",
    }],

  all: function(channel){
    var projects;

    if (channel)
      projects = this.FIXTURES.filterBy('channel', channel);
    else
      projects = this.FIXTURES;

    return projects.map(function(obj) {
      return App.Project.create(obj);
    });
  },

  find: function(channel, name) {
    var allProjects = this.all(channel);

    if (!name)
      return allProjects;
    else
      return allProjects.filterBy('projectName', name);
  }
});

App.BuildCategoryMixin = Ember.Mixin.create({
  renderTemplate: function() {
    this.render('build-list');
  }
});

App.ApplicationController = Ember.ObjectController.extend({
  isIndexActive: function(){
    return this.isActiveChannel('index');
  }.property('currentRouteName'),

  isTaggedActive: function(){
    return this.isActiveChannel('tagged');
  }.property('currentRouteName'),

  isChannelsActive: function(){
    var self = this;
    return !['index','tagged'].some(function(name){ return name === self.get('currentRouteName'); })
  }.property('currentRouteName'),

  isReleaseActive: function(){
    return this.isActiveChannel('release');
  }.property('currentRouteName'),

  isBetaActive: function(){
    return this.isActiveChannel('beta');
  }.property('currentRouteName'),

  isCanaryActive: function(){
    return this.isActiveChannel('canary');
  }.property('currentRouteName'),

  isActiveChannel: function(channel){
    return this.get('currentRouteName').indexOf(channel) !== -1;
  }
});

App.ProjectsMixin = Ember.Mixin.create({
  projects: function(){
    var projects = App.Project.find(this.get('channel')),
        bucket   = this.get('model'),
        self = this;

    projects.forEach(function(project){
      if (project.channel === 'beta'){
        project.isEmberBeta = project.projectName === 'Ember';

        [1,2,3,4,5].forEach(function(increment){
          var versionParts = project.lastRelease.split('.');
          var currentBetaNumber = parseInt(versionParts[versionParts.length - 1], 10);
          project['beta' + increment + 'Completed'] = increment <= currentBetaNumber;
          project['isBeta' + increment] = increment === currentBetaNumber;
        });

        var release = App.Project.find('release', project.projectName)[0];

        // no releases exist for ember-data (yet)
        if (release) {
          project.lastStableVersion = release.initialVersion;
          project.lastStableDate = release.initialReleaseDate;
        }
      }

      project.files = bucket.filterFiles(project.projectFilter);
      project.description = self.description(project);
      project.lastReleaseDebugUrl = self.lastReleaseUrl(project.projectFilter, project.channel, project.lastRelease, '.js');
      project.lastReleaseProdUrl  = self.lastReleaseUrl(project.projectFilter, project.channel, project.lastRelease, '.prod.js');
      project.lastReleaseMinUrl   = self.lastReleaseUrl(project.projectFilter, project.channel, project.lastRelease, '.min.js');

      if (project.channel === 'canary')
        project.lastRelease = 'latest';
      else if (project.changelog !== 'false')
        project.lastReleaseChangelogUrl   = 'https://github.com/' + project.projectRepo + '/blob/v' + project.lastRelease + '/' + project.changelogPath;
    });

    return projects;
  }.property('channel', 'model'),

  description: function(project){
    var lastRelease = project.lastRelease,
        futureVersion = project.futureVersion,
        value;

    if (this.get('channel') === 'tagged') {
      value = '';
    } else if (lastRelease) {
      value = 'The builds listed below are incremental improvements made since ' + lastRelease + ' and may become ' + futureVersion + '.'
    } else if (futureVersion) {
      value = 'The builds listed below are not based on a tagged release. Upon the next release cycle they will become ' + futureVersion + '.';
    } else {
      value = 'The builds listed below are based on the most recent development.';
    }

    return new Handlebars.SafeString(value);
  },

  lastReleaseUrl: function(project, channel, lastRelease, extension){
    if (channel === 'canary')
      return 'http://builds.emberjs.com/canary/' + project + extension;
    else
      return 'http://builds.emberjs.com/tags/v' + lastRelease + '/' + project + extension;
  }

});

App.CanaryRoute = Ember.Route.extend(App.BuildCategoryMixin, {
  model: function() {
    return App.S3Bucket.create({title: 'Canary Builds', prefix: 'canary/' });
  }
});

App.CanaryController = Ember.ObjectController.extend(App.ProjectsMixin, {
  templateName: 'buildList',
  channel: 'canary'
});

App.BetaRoute = Ember.Route.extend(App.BuildCategoryMixin, {
  model: function() {
    return App.S3Bucket.create({title: 'Beta Builds', prefix: 'beta/'});
  }
});

App.BetaController = Ember.ObjectController.extend(App.ProjectsMixin, {
  templateName: 'buildList',
  channel: 'beta'
});

App.ReleaseRoute = Ember.Route.extend(App.BuildCategoryMixin, {
  model: function() {
    return App.S3Bucket.create({title: 'Release Builds', prefix: 'release/'});
  }
});

App.ReleaseController = Ember.ObjectController.extend(App.ProjectsMixin, {
  templateName: 'buildList',
  channel: 'release'
});

App.TaggedRoute = Ember.Route.extend(App.BuildCategoryMixin, {
  model: function() {
    var bucket = App.S3Bucket.create({
      title: 'Tagged Release Builds',
      prefix: 'tags/',
      delimiter: '',
    });
    return bucket;
  }
});

App.TaggedController = Ember.ObjectController.extend(App.ProjectsMixin, {
  channel: 'tagged'
});
/*
 * Handlebars Helpers
 */
Ember.Handlebars.helper('format-bytes', function(bytes){
  return (bytes / 1024).toFixed(2) + ' KB';
});

Ember.Handlebars.helper('format-date-time', function(date, format, options) {
  if (!options) {
    options = format;
    format = null;
  }

  if (format){
    return moment(date).format(format);
  } else {
    return moment(date).fromNow();
  }
});

Ember.Handlebars.helper('isHiDPIScreen', function() {
  return window.getDevicePixelRatio() > 1;
});
<commit_msg>Update /builds fixtures.<commit_after>var App = Ember.Application.create({
  rootElement: '#builds-application'
});

App.Router.map(function() {
  this.route('release');
  this.route('beta');
  this.route('canary');
  this.route('tagged');
});

App.Router.reopen({
  notifyGoogleAnalytics: function() {
    var url = this.get('url');

    // Add a slash if neccesary
    if (!/^\//.test(url)){ url = '/' + url; }

    _gaq.push(['_trackPageview', '/builds' + url]);
  }.on('didTransition')
});

App.CopyClipboardComponent = Ember.Component.extend({
  tagName: 'span',
  hasFlash: ZeroClipboard.detectFlashSupport(),

  didInsertElement: function () {
    var clip = new ZeroClipboard(this.$('button'), {
      // This would normally be a relative path
      moviePath: "/images/ZeroClipboard.swf",

      trustedDomains: ["*"],
      allowScriptAccess: "always"
    });

    clip.on('mousedown', function(client, e) {
      Em.run.later(this, function() {
        $(this).removeClass('loading');
        $(this).removeAttr('disabled');
      }, 1000);
      Em.run.next(this, function() {
        $(this).addClass('loading');
        $(this).attr('disabled', 'disabled');
      });
    });

    this.$('input').on('click', function() {
      $(this).select();
    });
  }
});

App.S3Bucket = Ember.Object.extend({
  files: [],
  isLoading: false,

  // Setup these as default values.
  // They can be overridden on create.
  queryUseSSL: true,
  objectUseSSL: false,
  delimiter: '/',
  bucket: 'builds.emberjs.com',
  endpoint: 's3.amazonaws.com',

  delimiterParameter: function(){
    var delimiter = this.getWithDefault('delimiter','').toString();
    return (delimiter) ? 'delimiter=' + delimiter : '';
  }.property('delimiter'),

  markerParameter: function(){
    return 'marker=' + this.getWithDefault('marker','').toString();
  }.property('marker'),

  maxKeysParameter: function(){
    return 'max-keys=' + this.getWithDefault('maxKeys','').toString();
  }.property('maxKeys'),

  prefixParameter: function(){
    return 'prefix=' + this.getWithDefault('prefix','').toString();
  }.property('prefix'),

  queryProtocol: function() {
    return this.get('queryUseSSL') ? 'https://' : 'http://';
  }.property('queryUseSSL'),

  queryBaseUrl: function(){
    return this.get('queryProtocol') + this.get('endpoint') + '/' + this.get('bucket')
  }.property('queryProtocol', 'endpoint', 'bucket'),

  objectProtocol: function() {
    return this.get('objectUseSSL') ? 'https://' : 'http://';
  }.property('objectUseSSL'),

  objectBaseUrl: function(){
    return this.get('objectProtocol') + this.get('bucket');
  }.property('objectProtocol', 'bucket'),

  queryParams: function(){
    return this.get('delimiterParameter')  + '&' +
      this.get('markerParameter')     + '&' +
      this.get('maxKeysParameter')    + '&' +
      this.get('prefixParameter');
  }.property('delimiterParameter','markerParameter','maxKeysParameter','prefixParameter'),

  queryUrl: function(){
    return this.get('queryBaseUrl') + '?' + this.get('queryParams');
  }.property('queryBaseUrl','queryParams'),

  filesPresent: function(){
    return this.get('files').length;
  }.property('files.@each'),

  filterFiles: function(filter){
    var files = this.get('files');

    return files.filter(function(e) {
      return e.get('name').indexOf(filter + '.') !== -1;
    });
  },

  load: function(){
    var self = this,
    baseUrl = this.get('objectBaseUrl');

    this.set('isLoading', true);
    Ember.$.get(this.get('queryUrl'), function(data){
      self.set('isLoading', false);
      self.set('response', data);

      var contents = data.getElementsByTagName('Contents'),
      length   = contents.length,
      files    = [];

      for(var i = 0; i < length; i++) {
        var size = contents[i].getElementsByTagName('Size')[0].firstChild.data,
        name = contents[i].getElementsByTagName('Key')[0].firstChild.data,
        lastModified = new Date(contents[i].getElementsByTagName('LastModified')[0].firstChild.data);

        files.push(
          App.S3File.create({
          name: name,
          size: size,
          lastModified: lastModified,
          relativePath: name,
          baseUrl: baseUrl
        })
        );
      }

      self.set('files', files.sort(function(a,b){
        return b.lastModified - a.lastModified;
      }));
    });
  }.observes('queryUrl').on('init')
});

App.S3File = Ember.Object.extend({
  scriptTag: function(){
    var escapedURL = Handlebars.Utils.escapeExpression(this.get('url'));

    return new Handlebars.SafeString('<script src="' + escapedURL + '"></script>').toString();
  }.property('url'),

  url: function(){
    return this.get('baseUrl') + '/' + this.get('relativePath');
  }.property('baseUrl', 'relativePath')
});

App.Project = Ember.Object.extend();

App.Project.reopenClass({
  FIXTURES:
    [ {
      projectName: 'Ember',
      projectFilter: 'ember',
      projectRepo: 'emberjs/ember.js',
      channel: "tagged"
    }, {
      projectName: 'Ember Data',
      projectFilter: 'ember-data',
      projectRepo: 'emberjs/data',
      channel: "tagged"
    }, {
      projectName: "Ember",
      projectFilter: "ember",
      projectRepo: 'emberjs/ember.js',
      initialVersion: "1.5.0",
      initialReleaseDate: "2014-03-29",
      lastRelease: "1.5.0",
      futureVersion: "1.5.1",
      channel: "release",
      date: "2014-03-29",
      changelogPath: "CHANGELOG.md"
    }, {
      projectName: "Ember",
      projectFilter: "ember",
      projectRepo: 'emberjs/ember.js',
      lastRelease: "1.6.0-beta.2",
      futureVersion: "1.6.0-beta.3",
      finalVersion: '1.6.0',
      channel: "beta",
      cycleEstimatedFinishDate: '2014-05-06',
      date: "2014-04-08",
      nextDate: "2014-04-14",
      changelogPath: "CHANGELOG.md"
    }, {
      projectName: "Ember Data",
      projectFilter: "ember-data",
      projectRepo: 'emberjs/data',
      lastRelease: "1.0.0-beta.7",
      futureVersion: "1.0.0-beta.8",
      channel: "beta",
      date: "2014-02-20",
      changelogPath: "CHANGELOG.md"
    }, {
      projectName: "Ember",
      projectFilter: "ember",
      projectRepo: 'emberjs/ember.js',
      channel: "canary",
    }, {
      projectName: "Ember Data",
      projectFilter: "ember-data",
      projectRepo: 'emberjs/data',
      channel: "canary",
    }],

  all: function(channel){
    var projects;

    if (channel)
      projects = this.FIXTURES.filterBy('channel', channel);
    else
      projects = this.FIXTURES;

    return projects.map(function(obj) {
      return App.Project.create(obj);
    });
  },

  find: function(channel, name) {
    var allProjects = this.all(channel);

    if (!name)
      return allProjects;
    else
      return allProjects.filterBy('projectName', name);
  }
});

App.BuildCategoryMixin = Ember.Mixin.create({
  renderTemplate: function() {
    this.render('build-list');
  }
});

App.ApplicationController = Ember.ObjectController.extend({
  isIndexActive: function(){
    return this.isActiveChannel('index');
  }.property('currentRouteName'),

  isTaggedActive: function(){
    return this.isActiveChannel('tagged');
  }.property('currentRouteName'),

  isChannelsActive: function(){
    var self = this;
    return !['index','tagged'].some(function(name){ return name === self.get('currentRouteName'); })
  }.property('currentRouteName'),

  isReleaseActive: function(){
    return this.isActiveChannel('release');
  }.property('currentRouteName'),

  isBetaActive: function(){
    return this.isActiveChannel('beta');
  }.property('currentRouteName'),

  isCanaryActive: function(){
    return this.isActiveChannel('canary');
  }.property('currentRouteName'),

  isActiveChannel: function(channel){
    return this.get('currentRouteName').indexOf(channel) !== -1;
  }
});

App.ProjectsMixin = Ember.Mixin.create({
  projects: function(){
    var projects = App.Project.find(this.get('channel')),
        bucket   = this.get('model'),
        self = this;

    projects.forEach(function(project){
      if (project.channel === 'beta'){
        project.isEmberBeta = project.projectName === 'Ember';

        [1,2,3,4,5].forEach(function(increment){
          var versionParts = project.lastRelease.split('.');
          var currentBetaNumber = parseInt(versionParts[versionParts.length - 1], 10);
          project['beta' + increment + 'Completed'] = increment <= currentBetaNumber;
          project['isBeta' + increment] = increment === currentBetaNumber;
        });

        var release = App.Project.find('release', project.projectName)[0];

        // no releases exist for ember-data (yet)
        if (release) {
          project.lastStableVersion = release.initialVersion;
          project.lastStableDate = release.initialReleaseDate;
        }
      }

      project.files = bucket.filterFiles(project.projectFilter);
      project.description = self.description(project);
      project.lastReleaseDebugUrl = self.lastReleaseUrl(project.projectFilter, project.channel, project.lastRelease, '.js');
      project.lastReleaseProdUrl  = self.lastReleaseUrl(project.projectFilter, project.channel, project.lastRelease, '.prod.js');
      project.lastReleaseMinUrl   = self.lastReleaseUrl(project.projectFilter, project.channel, project.lastRelease, '.min.js');

      if (project.channel === 'canary')
        project.lastRelease = 'latest';
      else if (project.changelog !== 'false')
        project.lastReleaseChangelogUrl   = 'https://github.com/' + project.projectRepo + '/blob/v' + project.lastRelease + '/' + project.changelogPath;
    });

    return projects;
  }.property('channel', 'model'),

  description: function(project){
    var lastRelease = project.lastRelease,
        futureVersion = project.futureVersion,
        value;

    if (this.get('channel') === 'tagged') {
      value = '';
    } else if (lastRelease) {
      value = 'The builds listed below are incremental improvements made since ' + lastRelease + ' and may become ' + futureVersion + '.'
    } else if (futureVersion) {
      value = 'The builds listed below are not based on a tagged release. Upon the next release cycle they will become ' + futureVersion + '.';
    } else {
      value = 'The builds listed below are based on the most recent development.';
    }

    return new Handlebars.SafeString(value);
  },

  lastReleaseUrl: function(project, channel, lastRelease, extension){
    if (channel === 'canary')
      return 'http://builds.emberjs.com/canary/' + project + extension;
    else
      return 'http://builds.emberjs.com/tags/v' + lastRelease + '/' + project + extension;
  }

});

App.CanaryRoute = Ember.Route.extend(App.BuildCategoryMixin, {
  model: function() {
    return App.S3Bucket.create({title: 'Canary Builds', prefix: 'canary/' });
  }
});

App.CanaryController = Ember.ObjectController.extend(App.ProjectsMixin, {
  templateName: 'buildList',
  channel: 'canary'
});

App.BetaRoute = Ember.Route.extend(App.BuildCategoryMixin, {
  model: function() {
    return App.S3Bucket.create({title: 'Beta Builds', prefix: 'beta/'});
  }
});

App.BetaController = Ember.ObjectController.extend(App.ProjectsMixin, {
  templateName: 'buildList',
  channel: 'beta'
});

App.ReleaseRoute = Ember.Route.extend(App.BuildCategoryMixin, {
  model: function() {
    return App.S3Bucket.create({title: 'Release Builds', prefix: 'release/'});
  }
});

App.ReleaseController = Ember.ObjectController.extend(App.ProjectsMixin, {
  templateName: 'buildList',
  channel: 'release'
});

App.TaggedRoute = Ember.Route.extend(App.BuildCategoryMixin, {
  model: function() {
    var bucket = App.S3Bucket.create({
      title: 'Tagged Release Builds',
      prefix: 'tags/',
      delimiter: '',
    });
    return bucket;
  }
});

App.TaggedController = Ember.ObjectController.extend(App.ProjectsMixin, {
  channel: 'tagged'
});
/*
 * Handlebars Helpers
 */
Ember.Handlebars.helper('format-bytes', function(bytes){
  return (bytes / 1024).toFixed(2) + ' KB';
});

Ember.Handlebars.helper('format-date-time', function(date, format, options) {
  if (!options) {
    options = format;
    format = null;
  }

  if (format){
    return moment(date).format(format);
  } else {
    return moment(date).fromNow();
  }
});

Ember.Handlebars.helper('isHiDPIScreen', function() {
  return window.getDevicePixelRatio() > 1;
});
<|endoftext|>


<commit_before>import { _ } from 'azk';
import { UIProxy } from 'azk/cli/ui';

var glob = require('glob');
var path = require('path');

export class SugestionChooser extends UIProxy {
  constructor(sugestions_folder, ui) {
    super(ui);
    this.__sugestions = [];
    this.load(sugestions_folder);
  }

  get suggestions() {
    return this.__sugestions;
  }

  load(dir) {
    _.each(glob.sync(path.join(dir, '**/*.js')), (file) => {
      var Suggestion = require(file).Suggestion;
      if (Suggestion) {
        var suggestion = new Suggestion(this);
        this.__sugestions.push(suggestion);
      }
    });
  }

  suggest(evidences) {

    return _.map(evidences, (evidence) => {
      var suggestionChoosen = _.find(this.suggestions, (suggestion) => {
        var diff = _.difference([ evidence.ruleName ], suggestion.ruleNamesList);
        return diff.length === 0;
      });
      evidence.suggestionChoosen = suggestionChoosen;
      return evidence;
    });
  }

}
<commit_msg>Fixing bug that does not allow two (or more) systems with sames rules to be loaded into the same `Azkfile.js`, fix #46<commit_after>import { _ } from 'azk';
import { UIProxy } from 'azk/cli/ui';

var glob = require('glob');
var path = require('path');

export class SugestionChooser extends UIProxy {
  constructor(sugestions_folder, ui) {
    super(ui);
    this.__sugestions = [];
    this.load(sugestions_folder);
  }

  get suggestions() {
    return this.__sugestions;
  }

  load(dir) {
    _.each(glob.sync(path.join(dir, '**/*.js')), (file) => {
      var Suggestion = require(file).Suggestion;
      if (Suggestion) {
        var suggestion = new Suggestion(this);
        this.__sugestions.push(suggestion);
      }
    });
  }

  suggest(evidences) {

    return _.map(evidences, (evidence) => {
      var suggestionChoosen = _.find(this.suggestions, (suggestion) => {
        var diff = _.difference([ evidence.ruleName ], suggestion.ruleNamesList);
        return diff.length === 0;
      });
      evidence.suggestionChoosen = _.cloneDeep(suggestionChoosen);
      return evidence;
    });
  }

}
<|endoftext|>


<commit_before>#! /usr/bin/env node

/**
 * Copyright 2015 Mozilla
 *
 * 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.
 */

'use strict';

// Import this first so we can use it to wrap other modules we import.
var promisify = require("promisify-node");

var packageJson = require('./package.json');
var program = require('commander');
var rimraf = promisify(require('rimraf'));
var promptly = promisify(require('promptly'));
var fs = promisify(require('fs'));

// The scripts that implement the various commands/tasks we expose.
var configure = require('./lib/configure');
var deploy = require('./lib/deploy');
var offline = require('./lib/offline');
var bootstrap = require('./lib/bootstrap');
var integrate = require('./lib/integrate');

program
  .version(packageJson.version);

program
  .command('configure')
  .description('configure repository to auto-deploy to GitHub Pages using Travis CI')
  .action(function(env, options) {
    configure()
    .catch(function(err) {
      console.error(err);
    });
  });

program
  .command('deploy [dir]')
  .description('deploy directory to GitHub Pages')
  .option('-m, --message <message>', 'commit message')
  .action(function(dir, options) {
    deploy({
      rootDir: dir,
      cloneDir: '.gh-pages-cache',
      message: options.message,
    })
    .then(function() {
      // For perf, don't delete the repository.  This means users will have to
      // add .gh-pages-cache to their .gitignore file to hide its `git status`.
      // return rimraf('.gh-pages-cache');

      fs.access('.gitignore').then(function() {
        var gitignore = fs.readFileSync('.gitignore', 'utf8');

        if (gitignore.indexOf('.gh-pages.cache') === -1) {
          promptly.prompt('.gh-pages-cache is a temporary repository that we use to push changes to your gh-pages branch. Do you want to add it to .gitignore (Y/N, suggested Y): ', {
            default: 'Y',
            retry: true,
            validator: function(val) {
              val = val.toUpperCase();
              if (val !== 'N' && val !== 'Y') {
                throw new Error('Value should be Y or N');
              }
              return val;
            },
          }).then(function(answer) {
            if (answer === 'Y') {
              gitignore += '\n.gh-pages.cache\n';
              fs.writeFileSync('.gitignore', gitignore);
            }
          });
        }
      }).catch(function() {
        console.log('.gh-pages-cache is a temporary repository that we use to push changes to your gh-pages branch. We suggest you add it to your .gitignore.');
      });
    })
    .catch(function(err) {
      console.error(err);
    });
  });

program
  .command('offline [dir]')
  .description('offline the files in the directory by generating offline-worker.js script')
  .option('--file-globs [fileGlobs]', 'a comma-separated list of file globs to offline (default: \'**/*\')', '**/*')
  .option('--import-scripts <importScripts>', 'a comma-separated list of additional scripts to import into offline-worker.js')
  .action(function(dir, options) {
    offline({
      rootDir: dir,
      fileGlobs: options.fileGlobs ? options.fileGlobs.split(',') : null,
      importScripts: options.importScripts ? options.importScripts.split(',') : null,
    })
    .catch(function(err) {
      console.error(err);
    });
  });

program
  .command('bootstrap [dir]')
  .description('bootstrap the directory with a template app')
  .action(function(dir) {
    bootstrap({
      rootDir: dir,
    })
    .catch(function(err) {
      console.error(err);
    });
  });

  program
    .command('integrate [dir]')
    .description('integrate the offline-manager.js script into your app')
    .action(function(dir) {
      integrate({
        dir: dir,
      })
      .catch(function(err) {
        console.error(err);
      });
    });

program.parse(process.argv);

if (!process.argv.slice(2).length) {
  program.outputHelp();
}
<commit_msg>Replace .gh-pages.cache with .gh-pages-cache<commit_after>#! /usr/bin/env node

/**
 * Copyright 2015 Mozilla
 *
 * 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.
 */

'use strict';

// Import this first so we can use it to wrap other modules we import.
var promisify = require("promisify-node");

var packageJson = require('./package.json');
var program = require('commander');
var rimraf = promisify(require('rimraf'));
var promptly = promisify(require('promptly'));
var fs = promisify(require('fs'));

// The scripts that implement the various commands/tasks we expose.
var configure = require('./lib/configure');
var deploy = require('./lib/deploy');
var offline = require('./lib/offline');
var bootstrap = require('./lib/bootstrap');
var integrate = require('./lib/integrate');

program
  .version(packageJson.version);

program
  .command('configure')
  .description('configure repository to auto-deploy to GitHub Pages using Travis CI')
  .action(function(env, options) {
    configure()
    .catch(function(err) {
      console.error(err);
    });
  });

program
  .command('deploy [dir]')
  .description('deploy directory to GitHub Pages')
  .option('-m, --message <message>', 'commit message')
  .action(function(dir, options) {
    deploy({
      rootDir: dir,
      cloneDir: '.gh-pages-cache',
      message: options.message,
    })
    .then(function() {
      // For perf, don't delete the repository.  This means users will have to
      // add .gh-pages-cache to their .gitignore file to hide its `git status`.
      // return rimraf('.gh-pages-cache');

      fs.access('.gitignore').then(function() {
        var gitignore = fs.readFileSync('.gitignore', 'utf8');

        if (gitignore.indexOf('.gh-pages-cache') === -1) {
          promptly.prompt('.gh-pages-cache is a temporary repository that we use to push changes to your gh-pages branch. Do you want to add it to .gitignore (Y/N, suggested Y): ', {
            default: 'Y',
            retry: true,
            validator: function(val) {
              val = val.toUpperCase();
              if (val !== 'N' && val !== 'Y') {
                throw new Error('Value should be Y or N');
              }
              return val;
            },
          }).then(function(answer) {
            if (answer === 'Y') {
              gitignore += '\n.gh-pages-cache\n';
              fs.writeFileSync('.gitignore', gitignore);
            }
          });
        }
      }).catch(function() {
        console.log('.gh-pages-cache is a temporary repository that we use to push changes to your gh-pages branch. We suggest you add it to your .gitignore.');
      });
    })
    .catch(function(err) {
      console.error(err);
    });
  });

program
  .command('offline [dir]')
  .description('offline the files in the directory by generating offline-worker.js script')
  .option('--file-globs [fileGlobs]', 'a comma-separated list of file globs to offline (default: \'**/*\')', '**/*')
  .option('--import-scripts <importScripts>', 'a comma-separated list of additional scripts to import into offline-worker.js')
  .action(function(dir, options) {
    offline({
      rootDir: dir,
      fileGlobs: options.fileGlobs ? options.fileGlobs.split(',') : null,
      importScripts: options.importScripts ? options.importScripts.split(',') : null,
    })
    .catch(function(err) {
      console.error(err);
    });
  });

program
  .command('bootstrap [dir]')
  .description('bootstrap the directory with a template app')
  .action(function(dir) {
    bootstrap({
      rootDir: dir,
    })
    .catch(function(err) {
      console.error(err);
    });
  });

  program
    .command('integrate [dir]')
    .description('integrate the offline-manager.js script into your app')
    .action(function(dir) {
      integrate({
        dir: dir,
      })
      .catch(function(err) {
        console.error(err);
      });
    });

program.parse(process.argv);

if (!process.argv.slice(2).length) {
  program.outputHelp();
}
<|endoftext|>


<commit_before>"use strict";

var util = require("./util_module.js");
var queue = require("./queue_module.js");
var ring = require("./ring_module.js");
var account = require("./account_entry_module.js");
var account_mgr = require("./account_mgr_module.js");
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var state;

util.init();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(__dirname + "/clients/util"));
app.use(express.static(__dirname + "/clients/go"));
app.post("/go_msg", processPost);
app.get("/go_msg", processGet);
app.use(processNotFount);
app.use(processFailure);
app.listen(8080);

function processPost(req, res) {
    var my_link, his_link;

    postLogit("processPost", "start");
    state = "post start";

    var my_link_id = Number(req.body.link_id);
    my_link = account_mgr.search(req.body.my_name, req.body.his_name, my_link_id);
    if (!my_link) {
        abend("processPost", "null my_link");
        return;
    }
    if (my_link.link_id === 0) {
        abend("processPost", "null my_link = 0");
        return;
    }
    logit("processPost", "link=" + req.body.link_id + " "  + req.body.my_name + "=>" + req.body.his_name + " " + req.body.data + " " + req.body.xmt_seq + "=" + my_link.up_seq);
    if (req.body.my_name === req.body.his_name) {
        his_link = my_link;
    }
    else {
        his_link = account_mgr.search(req.body.his_name, req.body.my_name, -1);
        if (!his_link) {
            abend("processPost", "null his_link");
            return;
        }
        if (his_link.link_id === 0) {
            abend("processPost", "null his_link = 0");
            return;
        }
    }

    if (req.body.xmt_seq === my_link.up_seq) {
        state = "post 1000";
        queue.enqueue(his_link.queue, req.body.data);
        state = "post 1200";
        ring.enqueue(his_link.ring, req.body.data);
        state = "post 1900";
        my_link.up_seq += 1;
    } else if (req.body.xmt_seq < my_link.up_seq) {
        state = "post 2000";
         if (req.body.xmt_seq === 0) {
            queue.enqueue(his_link.queue, req.body.data);
            ring.enqueue(his_link.ring, req.body.data);
            my_link.up_seq = 1;
            logit("processPost", req.body.data + " post " + req.body.xmt_seq + " reset");
        } else {
            logit("processPost", req.body.data + " post " + req.body.xmt_seq + " dropped");
        }
    } else {
        state = "post 3000";
        logit("***abend: processPost", req.body.data + " post seq=" + req.body.xmt_seq + " dropped");
    }
    state = "post end";
}

function processGet (req, res) {
    if (req.headers.setup_link === "yes") {
        setupLink(req, res);
        return;
    }

    logit("processGet ", req.headers.his_name + "=>" + req.headers.my_name + " link_id=" +  req.headers.link_id);
    state = "get start";
    var link_id = Number(req.headers.link_id);
    var my_link = account_mgr.search(req.headers.my_name, req.headers.his_name, link_id);
    if (!my_link) {
        abend("processGet", "null my_link");
        return;
    }
    if (my_link.link_id === 0) {
        abend("processGet", "null my_link = 0");
        return;
    }
    if (!my_link.queue) {
        abend("processGet", "null queue");
        return;
    }
    res.type('application/json');
    state = "get 2000";
    var data = queue.dequeue(my_link.queue);
    state = "get 3000";
    var data1 = ring.dequeue(my_link.ring);
    state = "get 4000";
    if (data !== data1) {
        logit("*****Abend: processGet", "queue and ring not match");
    }
    if (!data) {
        //logit("processGet", "null data");
        return;
    }
    if (!data1) {
        //logti("processGet", "null data1");
        return;
    }

    state = "get 5000";
    logit("processGet ", req.headers.his_name + "=>" + req.headers.my_name + " " + data);
    state = "get 9000";
    res.send(data);
    state = "get end";
}

function setupLink (req, res) {
    var link, link_id_str;
    link = account_mgr.search_and_create(req.headers.my_name, req.headers.his_name, 0);
    if (!link) {
        abend("setupLink", "null link");
        return;
    }
    link_id_str = "" + link.link_id;
    res.send(link_id_str);
    logit("setupLink  ", req.headers.his_name + "=>" + req.headers.my_name);
}

function processNotFount (req, res) {
    console.log("use" + req);
    res.type('text/plain');
    res.status(404);
    res.send('Not Found');
}

function processFailure (err, req, res, next) {
    console.log("express error: " + state);
}

function postLogit(str1_val, str2_val) {
    var debugPost = false;
    if (debugPost) {
        logit(str1_val, str2_val);
    }
}

function logit (str1_val, str2_val) {
    return util.utilLogit("NodeMain." + str1_val, str2_val);
}

function abend (str1_val, str2_val) {
    return util.utilAbend("NodeMain." + str1_val, str2_val);
}
<commit_msg>dummy<commit_after>"use strict";

var util = require("./util_module.js");
var queue = require("./queue_module.js");
var ring = require("./ring_module.js");
var account = require("./account_entry_module.js");
var account_mgr = require("./account_mgr_module.js");
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var state;

util.init();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(__dirname + "/clients/util"));
app.use(express.static(__dirname + "/clients/go"));
app.post("/go_msg", processPost);
app.get("/go_msg", processGet);
app.use(processNotFount);
app.use(processFailure);
app.listen(8080);

function processPost(req, res) {
    var my_link, his_link;

    postLogit("processPost", "start");
    state = "post start";

    var my_link_id = Number(req.body.link_id);
    my_link = account_mgr.search(req.body.my_name, req.body.his_name, my_link_id);
    if (!my_link) {
        abend("processPost", "null my_link");
        return;
    }
    if (my_link.link_id === 0) {
        abend("processPost", "null my_link = 0");
        return;
    }
    logit("processPost", "link=" + req.body.link_id + " "  + req.body.my_name + "=>" + req.body.his_name + " " + req.body.data + " " + req.body.xmt_seq + "=" + my_link.up_seq);
    if (req.body.my_name === req.body.his_name) {
        his_link = my_link;
    }
    else {
        his_link = account_mgr.search(req.body.his_name, req.body.my_name, -1);
        if (!his_link) {
            abend("processPost", "null his_link");
            return;
        }
        if (his_link.link_id === 0) {
            abend("processPost", "null his_link = 0");
            return;
        }
    }

    if (req.body.xmt_seq === my_link.up_seq) {
        state = "post 1000";
        queue.enqueue(his_link.queue, req.body.data);
        state = "post 1200";
        ring.enqueue(his_link.ring, req.body.data);
        state = "post 1900";
        my_link.up_seq += 1;
    } else if (req.body.xmt_seq < my_link.up_seq) {
        state = "post 2000";
         if (req.body.xmt_seq === 0) {
            queue.enqueue(his_link.queue, req.body.data);
            ring.enqueue(his_link.ring, req.body.data);
            my_link.up_seq = 1;
            logit("processPost", req.body.data + " post " + req.body.xmt_seq + " reset");
        } else {
            logit("processPost", req.body.data + " post " + req.body.xmt_seq + " dropped");
        }
    } else {
        state = "post 3000";
        logit("***abend: processPost", req.body.data + " post seq=" + req.body.xmt_seq + " dropped");
    }
    state = "post end";
}

function processGet (req, res) {
    if (req.headers.setup_link === "yes") {
        setupLink(req, res);
        return;
    }

    //logit("processGet ", "link=" + req.headers.link_id + " "  + req.headers.his_name + "=>" + req.headers.my_name);
    state = "get start";
    var link_id = Number(req.headers.link_id);
    var my_link = account_mgr.search(req.headers.my_name, req.headers.his_name, link_id);
    if (!my_link) {
        abend("processGet", "null my_link");
        return;
    }
    if (my_link.link_id === 0) {
        abend("processGet", "null my_link = 0");
        return;
    }
    if (!my_link.queue) {
        abend("processGet", "null queue");
        return;
    }
    res.type('application/json');
    state = "get 2000";
    var data = queue.dequeue(my_link.queue);
    state = "get 3000";
    var data1 = ring.dequeue(my_link.ring);
    state = "get 4000";
    if (data !== data1) {
        logit("*****Abend: processGet", "queue and ring not match");
    }
    if (!data) {
        //logit("processGet", "null data");
        return;
    }
    if (!data1) {
        //logti("processGet", "null data1");
        return;
    }

    state = "get 5000";
    logit("processGet ", "link=" + req.headers.link_id + " "  + req.headers.his_name + "=>" + req.headers.my_name + " " + data);
    state = "get 9000";
    res.send(data);
    state = "get end";
}

function setupLink (req, res) {
    var link, link_id_str;
    link = account_mgr.search_and_create(req.headers.my_name, req.headers.his_name, 0);
    if (!link) {
        abend("setupLink", "null link");
        return;
    }
    link_id_str = "" + link.link_id;
    res.send(link_id_str);
    logit("setupLink  ", req.headers.his_name + "=>" + req.headers.my_name);
}

function processNotFount (req, res) {
    console.log("use" + req);
    res.type('text/plain');
    res.status(404);
    res.send('Not Found');
}

function processFailure (err, req, res, next) {
    console.log("express error: " + state);
}

function postLogit(str1_val, str2_val) {
    var debugPost = false;
    if (debugPost) {
        logit(str1_val, str2_val);
    }
}

function logit (str1_val, str2_val) {
    return util.utilLogit("NodeMain." + str1_val, str2_val);
}

function abend (str1_val, str2_val) {
    return util.utilAbend("NodeMain." + str1_val, str2_val);
}
<|endoftext|>


<commit_before>var
  rpc = require('./jsonrpc'),
  settings = require('./settings');

  retorno = '?';

module.exports = {
  masternodesCount: "$",

  getMasternodesCount : function() {
      retorno = "!";

      this.rpc = new rpc.Client({
        "host": settings.wallet.host,
        "port": settings.wallet.port,
        "user": settings.wallet.user,
        "pass": settings.wallet.pass
      });

      var args = function (err, response){
        if(err){console.log(err); res.send("There was an error. Check your console.");}
        else{
          if(typeof response === 'object'){
            res.send(response);
          }
          else{
            res.send(""+response);
          }
        }
      };

      this.rpc.call(
        [ { method: 'masternode', params: ["count"] } ],
        args,
        function(){
          var args = [].slice.call(arguments);
          retorno = args.toString();
          masternodesCount: retorno;
        }, function(err){
           //global.retorno = "error: "+response;
        }
      );

        console.log(retorno);
      return retorno;
  }
}
<commit_msg>remove deprecated<commit_after><|endoftext|>


<commit_before>const fs = require("fs");
const path = require("path");
const Ajv = require("ajv");
const schema = require("./data/cfg.schema.json");

/** @type {ModmailConfig} */
let config = {};

// Config files to search for, in priority order
const configFiles = [
  "config.ini",
  "config.json",
  "config.json5",
  "config.js",

  // Possible config files when file extensions are hidden
  "config.ini.ini",
  "config.ini.txt",
  "config.json.json",
  "config.json.txt",
];

let foundConfigFile;
for (const configFile of configFiles) {
  try {
    fs.accessSync(__dirname + "/../" + configFile);
    foundConfigFile = configFile;
    break;
  } catch (e) {}
}

// Load config values from a config file (if any)
if (foundConfigFile) {
  console.log(`Loading configuration from ${foundConfigFile}...`);
  try {
    if (foundConfigFile.endsWith(".js")) {
      config = require(`../${foundConfigFile}`);
    } else {
      const raw = fs.readFileSync(__dirname + "/../" + foundConfigFile, {encoding: "utf8"});
      if (foundConfigFile.endsWith(".ini") || foundConfigFile.endsWith(".ini.txt")) {
        config = require("ini").decode(raw);
      } else {
        config = require("json5").parse(raw);
      }
    }
  } catch (e) {
    throw new Error(`Error reading config file! The error given was: ${e.message}`);
  }
}

// Set dynamic default values which can't be set in the schema directly
config.dbDir = path.join(__dirname, "..", "db");
config.logDir = path.join(__dirname, "..", "logs"); // Only used for migrating data from older Modmail versions

// Load config values from environment variables
const envKeyPrefix = "MM_";
let loadedEnvValues = 0;

for (const [key, value] of Object.entries(process.env)) {
  if (! key.startsWith(envKeyPrefix)) continue;

  // MM_CLOSE_MESSAGE -> closeMessage
  // MM_COMMAND_ALIASES__MV => commandAliases.mv
  const configKey = key.slice(envKeyPrefix.length)
    .toLowerCase()
    .replace(/([a-z])_([a-z])/g, (m, m1, m2) => `${m1}${m2.toUpperCase()}`)
    .replace("__", ".");

  config[configKey] = value.includes("||")
    ? value.split("||")
    : value;

  loadedEnvValues++;
}

if (process.env.PORT && ! process.env.MM_PORT) {
  // Special case: allow common "PORT" environment variable without prefix
  config.port = process.env.PORT;
  loadedEnvValues++;
}

if (loadedEnvValues > 0) {
  console.log(`Loaded ${loadedEnvValues} ${loadedEnvValues === 1 ? "value" : "values"} from environment variables`);
}

// Convert config keys with periods to objects
// E.g. commandAliases.mv -> commandAliases: { mv: ... }
for (const [key, value] of Object.entries(config)) {
  if (! key.includes(".")) continue;

  const keys = key.split(".");
  let cursor = config;
  for (let i = 0; i < keys.length; i++) {
    if (i === keys.length - 1) {
      cursor[keys[i]] = value;
    } else {
      cursor[keys[i]] = cursor[keys[i]] || {};
      cursor = cursor[keys[i]];
    }
  }

  delete config[key];
}

// mainGuildId => mainServerId
// mailGuildId => inboxServerId
if (config.mainGuildId && ! config.mainServerId) {
  config.mainServerId = config.mainGuildId;
}
if (config.mailGuildId && ! config.inboxServerId) {
  config.inboxServerId = config.mailGuildId;
}

if (! config.dbType) {
  config.dbType = "sqlite";
}

if (! config.sqliteOptions) {
  config.sqliteOptions = {
    filename: path.resolve(__dirname, "..", "db", "data.sqlite"),
  };
}

// categoryAutomation.newThreadFromGuild => categoryAutomation.newThreadFromServer
if (config.categoryAutomation && config.categoryAutomation.newThreadFromGuild && ! config.categoryAutomation.newThreadFromServer) {
  config.categoryAutomation.newThreadFromServer = config.categoryAutomation.newThreadFromGuild;
}

// guildGreetings => serverGreetings
if (config.guildGreetings && ! config.serverGreetings) {
  config.serverGreetings = config.guildGreetings;
}

// Move greetingMessage/greetingAttachment to the serverGreetings object internally
// Or, in other words, if greetingMessage and/or greetingAttachment is set, it is applied for all servers that don't
// already have something set up in serverGreetings. This retains backwards compatibility while allowing you to override
// greetings for specific servers in serverGreetings.
if (config.greetingMessage || config.greetingAttachment) {
  config.serverGreetings = config.serverGreetings || {};
  for (const guildId of config.mainServerId) {
    if (config.serverGreetings[guildId]) continue;
    config.serverGreetings[guildId] = {
      message: config.greetingMessage,
      attachment: config.greetingAttachment
    };
  }
}

// newThreadCategoryId is syntactic sugar for categoryAutomation.newThread
if (config.newThreadCategoryId) {
  config.categoryAutomation.newThread = config.newThreadCategoryId;
  delete config.newThreadCategoryId;
}

// Delete empty string options (i.e. "option=" without a value in config.ini)
for (const [key, value] of Object.entries(config)) {
  if (value === "") {
    delete config[key];
  }
}

// Validate config and assign defaults (if missing)
const ajv = new Ajv({
  useDefaults: true,
  coerceTypes: "array",
  extendRefs: true, // Hides an error about ignored keywords when using $ref with $comment
});

// https://github.com/ajv-validator/ajv/issues/141#issuecomment-270692820
const truthyValues = ["1", "true", "on", "yes"];
const falsyValues = ["0", "false", "off", "no"];
ajv.addKeyword("coerceBoolean", {
  compile(value) {
    return (data, dataPath, parentData, parentKey) => {
      if (! value) {
        // Disabled -> no coercion
        return true;
      }

      // https://github.com/ajv-validator/ajv/issues/141#issuecomment-270777250
      // The "data" argument doesn't update within the same set of schemas inside "allOf",
      // so we're referring to the original property instead.
      // This also means we can't use { "type": "boolean" }, as it would test the un-updated data value.
      const realData = parentData[parentKey];

      if (typeof realData === "boolean") {
        return true;
      }

      if (truthyValues.includes(realData)) {
        parentData[parentKey] = true;
      } else if (falsyValues.includes(realData)) {
        parentData[parentKey] = false;
      } else {
        return false;
      }

      return true;
    };
  },
});

ajv.addKeyword("multilineString", {
  compile(value) {
    return (data, dataPath, parentData, parentKey) => {
      if (! value) {
        // Disabled -> no coercion
        return true;
      }

      const realData = parentData[parentKey];

      if (typeof realData === "string") {
        return true;
      }

      parentData[parentKey] = realData.join("\n");

      return true;
    };
  },
});

const validate = ajv.compile(schema);
const configIsValid = validate(config);

if (! configIsValid) {
  console.error("");
  console.error("NOTE! Issues with configuration:");
  for (const error of validate.errors) {
    if (error.params.missingProperty) {
      console.error(`- Missing required option: "${error.params.missingProperty.slice(1)}"`);
    } else {
      console.error(`- The "${error.dataPath.slice(1)}" option ${error.message}`);
    }
  }
  console.error("");
  console.error("Please restart the bot after fixing the issues mentioned above.");
  console.error("");
  process.exit(1);
}

console.log("Configuration ok!");

/**
 * @type {ModmailConfig}
 */
module.exports = config;
<commit_msg>Fix crash when using newThreadCategoryId without categoryAutomation<commit_after>const fs = require("fs");
const path = require("path");
const Ajv = require("ajv");
const schema = require("./data/cfg.schema.json");

/** @type {ModmailConfig} */
let config = {};

// Config files to search for, in priority order
const configFiles = [
  "config.ini",
  "config.json",
  "config.json5",
  "config.js",

  // Possible config files when file extensions are hidden
  "config.ini.ini",
  "config.ini.txt",
  "config.json.json",
  "config.json.txt",
];

let foundConfigFile;
for (const configFile of configFiles) {
  try {
    fs.accessSync(__dirname + "/../" + configFile);
    foundConfigFile = configFile;
    break;
  } catch (e) {}
}

// Load config values from a config file (if any)
if (foundConfigFile) {
  console.log(`Loading configuration from ${foundConfigFile}...`);
  try {
    if (foundConfigFile.endsWith(".js")) {
      config = require(`../${foundConfigFile}`);
    } else {
      const raw = fs.readFileSync(__dirname + "/../" + foundConfigFile, {encoding: "utf8"});
      if (foundConfigFile.endsWith(".ini") || foundConfigFile.endsWith(".ini.txt")) {
        config = require("ini").decode(raw);
      } else {
        config = require("json5").parse(raw);
      }
    }
  } catch (e) {
    throw new Error(`Error reading config file! The error given was: ${e.message}`);
  }
}

// Set dynamic default values which can't be set in the schema directly
config.dbDir = path.join(__dirname, "..", "db");
config.logDir = path.join(__dirname, "..", "logs"); // Only used for migrating data from older Modmail versions

// Load config values from environment variables
const envKeyPrefix = "MM_";
let loadedEnvValues = 0;

for (const [key, value] of Object.entries(process.env)) {
  if (! key.startsWith(envKeyPrefix)) continue;

  // MM_CLOSE_MESSAGE -> closeMessage
  // MM_COMMAND_ALIASES__MV => commandAliases.mv
  const configKey = key.slice(envKeyPrefix.length)
    .toLowerCase()
    .replace(/([a-z])_([a-z])/g, (m, m1, m2) => `${m1}${m2.toUpperCase()}`)
    .replace("__", ".");

  config[configKey] = value.includes("||")
    ? value.split("||")
    : value;

  loadedEnvValues++;
}

if (process.env.PORT && ! process.env.MM_PORT) {
  // Special case: allow common "PORT" environment variable without prefix
  config.port = process.env.PORT;
  loadedEnvValues++;
}

if (loadedEnvValues > 0) {
  console.log(`Loaded ${loadedEnvValues} ${loadedEnvValues === 1 ? "value" : "values"} from environment variables`);
}

// Convert config keys with periods to objects
// E.g. commandAliases.mv -> commandAliases: { mv: ... }
for (const [key, value] of Object.entries(config)) {
  if (! key.includes(".")) continue;

  const keys = key.split(".");
  let cursor = config;
  for (let i = 0; i < keys.length; i++) {
    if (i === keys.length - 1) {
      cursor[keys[i]] = value;
    } else {
      cursor[keys[i]] = cursor[keys[i]] || {};
      cursor = cursor[keys[i]];
    }
  }

  delete config[key];
}

// mainGuildId => mainServerId
// mailGuildId => inboxServerId
if (config.mainGuildId && ! config.mainServerId) {
  config.mainServerId = config.mainGuildId;
}
if (config.mailGuildId && ! config.inboxServerId) {
  config.inboxServerId = config.mailGuildId;
}

if (! config.dbType) {
  config.dbType = "sqlite";
}

if (! config.sqliteOptions) {
  config.sqliteOptions = {
    filename: path.resolve(__dirname, "..", "db", "data.sqlite"),
  };
}

// categoryAutomation.newThreadFromGuild => categoryAutomation.newThreadFromServer
if (config.categoryAutomation && config.categoryAutomation.newThreadFromGuild && ! config.categoryAutomation.newThreadFromServer) {
  config.categoryAutomation.newThreadFromServer = config.categoryAutomation.newThreadFromGuild;
}

// guildGreetings => serverGreetings
if (config.guildGreetings && ! config.serverGreetings) {
  config.serverGreetings = config.guildGreetings;
}

// Move greetingMessage/greetingAttachment to the serverGreetings object internally
// Or, in other words, if greetingMessage and/or greetingAttachment is set, it is applied for all servers that don't
// already have something set up in serverGreetings. This retains backwards compatibility while allowing you to override
// greetings for specific servers in serverGreetings.
if (config.greetingMessage || config.greetingAttachment) {
  config.serverGreetings = config.serverGreetings || {};
  for (const guildId of config.mainServerId) {
    if (config.serverGreetings[guildId]) continue;
    config.serverGreetings[guildId] = {
      message: config.greetingMessage,
      attachment: config.greetingAttachment
    };
  }
}

// newThreadCategoryId is syntactic sugar for categoryAutomation.newThread
if (config.newThreadCategoryId) {
  config.categoryAutomation = config.categoryAutomation || {};
  config.categoryAutomation.newThread = config.newThreadCategoryId;
  delete config.newThreadCategoryId;
}

// Delete empty string options (i.e. "option=" without a value in config.ini)
for (const [key, value] of Object.entries(config)) {
  if (value === "") {
    delete config[key];
  }
}

// Validate config and assign defaults (if missing)
const ajv = new Ajv({
  useDefaults: true,
  coerceTypes: "array",
  extendRefs: true, // Hides an error about ignored keywords when using $ref with $comment
});

// https://github.com/ajv-validator/ajv/issues/141#issuecomment-270692820
const truthyValues = ["1", "true", "on", "yes"];
const falsyValues = ["0", "false", "off", "no"];
ajv.addKeyword("coerceBoolean", {
  compile(value) {
    return (data, dataPath, parentData, parentKey) => {
      if (! value) {
        // Disabled -> no coercion
        return true;
      }

      // https://github.com/ajv-validator/ajv/issues/141#issuecomment-270777250
      // The "data" argument doesn't update within the same set of schemas inside "allOf",
      // so we're referring to the original property instead.
      // This also means we can't use { "type": "boolean" }, as it would test the un-updated data value.
      const realData = parentData[parentKey];

      if (typeof realData === "boolean") {
        return true;
      }

      if (truthyValues.includes(realData)) {
        parentData[parentKey] = true;
      } else if (falsyValues.includes(realData)) {
        parentData[parentKey] = false;
      } else {
        return false;
      }

      return true;
    };
  },
});

ajv.addKeyword("multilineString", {
  compile(value) {
    return (data, dataPath, parentData, parentKey) => {
      if (! value) {
        // Disabled -> no coercion
        return true;
      }

      const realData = parentData[parentKey];

      if (typeof realData === "string") {
        return true;
      }

      parentData[parentKey] = realData.join("\n");

      return true;
    };
  },
});

const validate = ajv.compile(schema);
const configIsValid = validate(config);

if (! configIsValid) {
  console.error("");
  console.error("NOTE! Issues with configuration:");
  for (const error of validate.errors) {
    if (error.params.missingProperty) {
      console.error(`- Missing required option: "${error.params.missingProperty.slice(1)}"`);
    } else {
      console.error(`- The "${error.dataPath.slice(1)}" option ${error.message}`);
    }
  }
  console.error("");
  console.error("Please restart the bot after fixing the issues mentioned above.");
  console.error("");
  process.exit(1);
}

console.log("Configuration ok!");

/**
 * @type {ModmailConfig}
 */
module.exports = config;
<|endoftext|>