<commit_before>(function ($) {
  var queryParser = function (a) {
      var i, p, b = {};
      if (a === "") {
        return {};
      }
      for (i = 0; i < a.length; i += 1) {
        p = a[i].split('=');
        if (p.length === 2) {
          b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
        }
      }
      return b;
    };
  $.queryParams = function () {
    return queryParser(window.location.search.substr(1).split('&'));
  };
  $.hashParams = function () {
    return queryParser(window.location.hash.substr(1).split('&'));
  };

  window.Swiftype = window.Swiftype || {};
  Swiftype.root_url = Swiftype.root_url || 'http://api.swiftype.com';
  Swiftype.pingUrl = function (endpoint, callback) {
    var img = new Image();
    img.onload = img.onerror = callback;
    img.src = endpoint;
    setTimeout(callback, 350);
    return false;
  };
  Swiftype.pingSearchResultClick = function (engineKey, docId, callback) {
    var params = {
      t: new Date().getTime(),
      engine_key: engineKey,
      doc_id: docId,
      q: Swiftype.currentQuery
    };
    var url = Swiftype.root_url + '/api/v1/public/analytics/pc?' + $.param(params);
    Swiftype.pingUrl(url, callback);
  };

  $.fn.swiftypeSearch = function (options) {
    var options = $.extend({}, $.fn.swiftypeSearch.defaults, options);

    return this.each(function () {
      var $this = $(this);
      var config = $.meta ? $.extend({}, options, $this.data()) : options;
      $this.data('swiftype-config', config);

      $this.selectedCallback = function (data) {
        return function (e) {
          var $el = $(this);
          e.preventDefault();
          Swiftype.pingSearchResultClick(config.engineKey, data['id'], function() {
            window.location = $el.attr('href');
          });
        };
      };

      $this.registerResult = function ($element, data) {
        $element.data('swiftype-item', data);
        $('a', $element).click($this.selectedCallback(data));
      };

      $this.getContentCache = function () {
        return $('#' + contentCacheId);
      };

      var $resultContainer = $(config.resultContainingElement),
        initialContentOfResultContainer = $resultContainer.html(),
        contentCacheId = 'st-content-cache',
        $contentCache = $this.getContentCache();

      var setSearchHash = function (query, page) {
          location.hash = "stq=" + query + "&stp=" + page;
        };

      var submitSearch = function (query, options) {
          options = $.extend({
            per_page: 10,
            page: 1
          }, options);
          var params = {};

          if (!$contentCache.length) {
            $resultContainer.after("

");
            $contentCache.html(initialContentOfResultContainer).hide();
          }
          config.loadingFunction(query, $resultContainer);

          Swiftype.currentQuery = query;
          params['q'] = query;
          params['engine_key'] = config.engineKey;
          params['page'] = options.page;
          params['per_page'] = options.per_page;

          if (config.searchFields !== undefined) {
            params['search_fields'] = config.searchFields;
          }
          if (config.fetchFields !== undefined) {
            params['fetch_fields'] = config.fetchFields;
          }
          if (config.filters !== undefined) {
            var filters = config.filters;
            if (typeof filters === 'function') {
              filters = filters.call();
            }
            params['filters'] = filters;
          }
          if (config.documentTypes !== undefined) {
            params['document_types'] = config.documentTypes;
          }
          if (config.functionalBoosts !== undefined) {
            params['functional_boosts'] = config.functionalBoosts;
          }
          if (config.sortField !== undefined) {
            var sortField = config.sortField;
            if (typeof sortField === 'function') {
              sortField = sortField.call();
            }
            params['sort_field'] = sortField;
          }
          if (config.sortDirection !== undefined) {
            var sortDirection = config.sortDirection;
            if (typeof sortDirection === 'function') {
              sortDirection = sortDirection.call();
            }
            params['sort_direction'] = sortDirection;
          }

          $.getJSON(Swiftype.root_url + "/api/v1/public/engines/search.json?callback=?", params).success(renderSearchResults);
        };

      $(window).hashchange(function () {
        var params = $.hashParams();
        if (params.stq) {
          submitSearch(params.stq, {
            page: params.stp
          });
        } else {
          var $contentCache = $this.getContentCache();
          if ($contentCache.length) {
            $resultContainer.html(contentCache.html());
            $contentCache.remove();
          }
        }
      });

      var $containingForm = $this.parents('form');
      if ($containingForm) {
        $containingForm.bind('submit', function (e) {
          e.preventDefault();
          var searchQuery = $this.val();
          setSearchHash(searchQuery, 1);
        });
      }

      $('[data-hash][data-page]').live('click', function (e) {
        e.preventDefault();
        var $this = $(this);
        setSearchHash($.hashParams().stq, $this.data('page'));
      });

      var renderSearchResults = function (data) {
          $resultContainer.html('');
          if (typeof config.preRenderFunction === 'function') {
            config.preRenderFunction.call($this, data);
          }

          $.each(data.records, function (documentType, items) {
            $.each(items, function (idx, item) {
              $this.registerResult($(config.renderFunction(documentType, item)).appendTo($resultContainer), item);
            });
          });
          renderPagination($this.getContext(), data.info);
        };

      $this.getContext = function () {
        return {
          config: config,
          resultContainer: $resultContainer,
          registerResult: $this.registerResult
        };
      };

      $(window).hashchange(); // if the swiftype query hash is present onload (maybe the user is pressing the back button), submit a query onload
    });
  };

  var renderPagination = function (ctx, resultInfo) {
    var maxPagesType, maxPages = -1;
    $.each(resultInfo, function(documentType, typeInfo) {
      if (typeInfo.num_pages > maxPages) {
        maxPagesType = documentType;
      }
    });
    var currentPage = resultInfo[maxPagesType].current_page,
      totalPages = resultInfo[maxPagesType].num_pages;
    $(renderPaginationForType(maxPagesType, currentPage, totalPages)).appendTo(ctx.resultContainer);
  };

  var renderPaginationForType = function (type, currentPage, totalPages) {
      var pages = '',
        previousPage, nextPage;
      if (currentPage != 1) {
        previousPage = currentPage - 1;
        pages = pages + '« previous'
      }
      if (currentPage < totalPages) {
        nextPage = currentPage + 1;
        pages = pages + 'next »'
      }
      return pages;
    };

  var normalize = function (str) {
      return $.trim(str).toLowerCase();
    };

  var defaultRenderFunction = function (document_type, item) {
      return '

' + item['title'] + '

';
    };

  var defaultLoadingFunction = function(query, $resultContainer) {
      $resultContainer.html('
loading...
');
    };

  $.fn.swiftypeSearch.defaults = {
    attachTo: undefined,
    documentTypes: undefined,
    filters: undefined,
    engineKey: undefined,
    searchFields: undefined,
    functionalBoosts: undefined,
    sortField: undefined,
    sortDirection: undefined,
    fetchFields: undefined,
    preRenderFunction: undefined,
    loadingFunction: defaultLoadingFunction,
    renderFunction: defaultRenderFunction
  };
})(jQuery);<commit_msg>put previous and next buttons in a div<commit_after>(function ($) {
  var queryParser = function (a) {
      var i, p, b = {};
      if (a === "") {
        return {};
      }
      for (i = 0; i < a.length; i += 1) {
        p = a[i].split('=');
        if (p.length === 2) {
          b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
        }
      }
      return b;
    };
  $.queryParams = function () {
    return queryParser(window.location.search.substr(1).split('&'));
  };
  $.hashParams = function () {
    return queryParser(window.location.hash.substr(1).split('&'));
  };

  window.Swiftype = window.Swiftype || {};
  Swiftype.root_url = Swiftype.root_url || 'http://api.swiftype.com';
  Swiftype.pingUrl = function (endpoint, callback) {
    var img = new Image();
    img.onload = img.onerror = callback;
    img.src = endpoint;
    setTimeout(callback, 350);
    return false;
  };
  Swiftype.pingSearchResultClick = function (engineKey, docId, callback) {
    var params = {
      t: new Date().getTime(),
      engine_key: engineKey,
      doc_id: docId,
      q: Swiftype.currentQuery
    };
    var url = Swiftype.root_url + '/api/v1/public/analytics/pc?' + $.param(params);
    Swiftype.pingUrl(url, callback);
  };

  $.fn.swiftypeSearch = function (options) {
    var options = $.extend({}, $.fn.swiftypeSearch.defaults, options);

    return this.each(function () {
      var $this = $(this);
      var config = $.meta ? $.extend({}, options, $this.data()) : options;
      $this.data('swiftype-config', config);

      $this.selectedCallback = function (data) {
        return function (e) {
          var $el = $(this);
          e.preventDefault();
          Swiftype.pingSearchResultClick(config.engineKey, data['id'], function() {
            window.location = $el.attr('href');
          });
        };
      };

      $this.registerResult = function ($element, data) {
        $element.data('swiftype-item', data);
        $('a', $element).click($this.selectedCallback(data));
      };

      $this.getContentCache = function () {
        return $('#' + contentCacheId);
      };

      var $resultContainer = $(config.resultContainingElement),
        initialContentOfResultContainer = $resultContainer.html(),
        contentCacheId = 'st-content-cache',
        $contentCache = $this.getContentCache();

      var setSearchHash = function (query, page) {
          location.hash = "stq=" + query + "&stp=" + page;
        };

      var submitSearch = function (query, options) {
          options = $.extend({
            per_page: 10,
            page: 1
          }, options);
          var params = {};

          if (!$contentCache.length) {
            $resultContainer.after("

");
            $contentCache.html(initialContentOfResultContainer).hide();
          }
          config.loadingFunction(query, $resultContainer);

          Swiftype.currentQuery = query;
          params['q'] = query;
          params['engine_key'] = config.engineKey;
          params['page'] = options.page;
          params['per_page'] = options.per_page;

          if (config.searchFields !== undefined) {
            params['search_fields'] = config.searchFields;
          }
          if (config.fetchFields !== undefined) {
            params['fetch_fields'] = config.fetchFields;
          }
          if (config.filters !== undefined) {
            var filters = config.filters;
            if (typeof filters === 'function') {
              filters = filters.call();
            }
            params['filters'] = filters;
          }
          if (config.documentTypes !== undefined) {
            params['document_types'] = config.documentTypes;
          }
          if (config.functionalBoosts !== undefined) {
            params['functional_boosts'] = config.functionalBoosts;
          }
          if (config.sortField !== undefined) {
            var sortField = config.sortField;
            if (typeof sortField === 'function') {
              sortField = sortField.call();
            }
            params['sort_field'] = sortField;
          }
          if (config.sortDirection !== undefined) {
            var sortDirection = config.sortDirection;
            if (typeof sortDirection === 'function') {
              sortDirection = sortDirection.call();
            }
            params['sort_direction'] = sortDirection;
          }

          $.getJSON(Swiftype.root_url + "/api/v1/public/engines/search.json?callback=?", params).success(renderSearchResults);
        };

      $(window).hashchange(function () {
        var params = $.hashParams();
        if (params.stq) {
          submitSearch(params.stq, {
            page: params.stp
          });
        } else {
          var $contentCache = $this.getContentCache();
          if ($contentCache.length) {
            $resultContainer.html(contentCache.html());
            $contentCache.remove();
          }
        }
      });

      var $containingForm = $this.parents('form');
      if ($containingForm) {
        $containingForm.bind('submit', function (e) {
          e.preventDefault();
          var searchQuery = $this.val();
          setSearchHash(searchQuery, 1);
        });
      }

      $('[data-hash][data-page]').live('click', function (e) {
        e.preventDefault();
        var $this = $(this);
        setSearchHash($.hashParams().stq, $this.data('page'));
      });

      var renderSearchResults = function (data) {
          $resultContainer.html('');
          if (typeof config.preRenderFunction === 'function') {
            config.preRenderFunction.call($this, data);
          }

          $.each(data.records, function (documentType, items) {
            $.each(items, function (idx, item) {
              $this.registerResult($(config.renderFunction(documentType, item)).appendTo($resultContainer), item);
            });
          });
          renderPagination($this.getContext(), data.info);
        };

      $this.getContext = function () {
        return {
          config: config,
          resultContainer: $resultContainer,
          registerResult: $this.registerResult
        };
      };

      $(window).hashchange(); // if the swiftype query hash is present onload (maybe the user is pressing the back button), submit a query onload
    });
  };

  var renderPagination = function (ctx, resultInfo) {
    var maxPagesType, maxPages = -1;
    $.each(resultInfo, function(documentType, typeInfo) {
      if (typeInfo.num_pages > maxPages) {
        maxPagesType = documentType;
      }
    });
    var currentPage = resultInfo[maxPagesType].current_page,
      totalPages = resultInfo[maxPagesType].num_pages;
    $(renderPaginationForType(maxPagesType, currentPage, totalPages)).appendTo(ctx.resultContainer);
  };

  var renderPaginationForType = function (type, currentPage, totalPages) {
      var pages = '
',
        previousPage, nextPage;
      if (currentPage != 1) {
        previousPage = currentPage - 1;
        pages = pages + '« previous';
      }
      if (currentPage < totalPages) {
        nextPage = currentPage + 1;
        pages = pages + 'next »';
      }
      pages += '
';
      return pages;
    };

  var normalize = function (str) {
      return $.trim(str).toLowerCase();
    };

  var defaultRenderFunction = function (document_type, item) {
      return '

' + item['title'] + '

';
    };

  var defaultLoadingFunction = function(query, $resultContainer) {
      $resultContainer.html('
loading...
');
    };

  $.fn.swiftypeSearch.defaults = {
    attachTo: undefined,
    documentTypes: undefined,
    filters: undefined,
    engineKey: undefined,
    searchFields: undefined,
    functionalBoosts: undefined,
    sortField: undefined,
    sortDirection: undefined,
    fetchFields: undefined,
    preRenderFunction: undefined,
    loadingFunction: defaultLoadingFunction,
    renderFunction: defaultRenderFunction
  };
})(jQuery);<|endoftext|>


<commit_before>const service = require('feathers-mongoose');
const { Forbidden, NotFound, BadRequest } = require('@feathersjs/errors');
const logger = require('winston');
const { ObjectId } = require('mongoose').Types;
const {
	newsModel, targetModels, newsHistoryModel, newsPermissions,
} = require('./model');
const hooks = require('./hooks');
const newsModelHooks = require('./hooks/newsModel.hooks');
const { flatten } = require('../../utils/array');

class AbstractService {
	setup(app) {
		this.app = app;
	}

	/**
	* Returns school query if the user is allowed to see.
	* @param userId The user's Id
	* @param schoolId The schoolId
	* @returns Query
	*/
	createSchoolQuery(userId, schoolId, permission) {
		return this.hasSchoolPermission(userId, schoolId, permission)
			.then((hasPermission) => {
				if (!hasPermission) {
					return null;
				}
				return [{
					schoolId,
					target: { $exists: false },
				}];
			});
	}

	/**
		 * Returns scoped news the user is allowed to see
		 *
		 * @param {BsonId|String} userId the user's Id
		 * @param {BsonId|String} target (optional) Id of the news target (course, team, etc.)
		 * @returns Array<Query>
		 */
	async createScopedQuery(userId, permission, target = null, targetModel = null) {
		// check params
		if (target ? !targetModel : targetModel) {
			throw new BadRequest('target and targetModel, both must be given or not');
		}
		// only one target requested
		if (target && targetModel) {
			return this.hasPermission(userId, permission, { target, targetModel })
				.then(() => (
					[{
						targetModel,
						target,
					}]));
		}
		// return data of all user scopes
		const ops = targetModels.map(async (scope) => {
			// For each possible target model, find all targets the user has NEWS_VIEW permissions in.
			const scopeListService = this.app.service(`/users/:scopeId/${scope}`);
			if (scopeListService === undefined) {
				return null;
			}
			const scopeItems = await scopeListService.find({
				route: { scopeId: userId.toString() },
				query: {
					permissions: [permission],
				},
			});
			return scopeItems.map(item => ({
				targetModel: scope,
				target: item._id,
			}));
		});
		const results = await Promise.all(ops);
		return flatten(results.filter(r => r !== null));
	}

	/* Permissions */

	async getPermissions(userId, { target, targetModel, schoolId } = {}) {
		// target and school might be populated or not
		const isObjectId = o => o instanceof ObjectId || typeof o === 'string';
		// scope case: user role in scope must have given permission
		if (target && targetModel) {
			const targetId = isObjectId(target) ? target.toString() : target._id.toString();
			const scope = this.app.service(`${targetModel}/:scopeId/userPermissions/`);
			const params = { route: { scopeId: targetId } };
			const scopePermissions = await scope.get(userId, params);
			return scopePermissions;
		}

		// default school case: dataItem and users schoolId must match and user permission must exist
		return this.getSchoolPermissions(userId, isObjectId(schoolId) ? schoolId : schoolId._id);
	}

	/**
	 * Checks scoped permission for a user and given news.
	 * @param {BsonId|String} userId
	 * @param {String} permission
	 * @param {BsonId|String} targetId (optional) news target (scope) id. Only valid with targetModel.
	 * @param {String} targetModel (optional) news target (scope) model. Only valid with target.
	 * @returns {Promise<Boolean>} Promise that resolves to true/false
	 * @example
	 * await hasPermission(user._id, 'NEWS_VIEW') => true
	 * await hasPermission(user._id, 'NEWS_CREATE', team._id, 'teams') => false
	 * @memberof NewsService
	 */
	async hasPermission(userId, permission, dataItem) {
		if (!dataItem) {
			// maybe, use hasSchoolPermission instead...
			return false;
		}
		const permissions = await this.getPermissions(userId, dataItem);
		return permissions.includes(permission);
	}

	/**
	 * Tests if a user with given userId has a (global) permission within a school.
	 * @param {ObjectId} userId
	 * @param {ObjectId} schoolId
	 * @returns {Array<String>}
	 */
	async getSchoolPermissions(userId, schoolId) {
		// test user exists
		const user = await this.app.service('users').get(userId);
		if (user == null) return [];

		// test user is school member
		const sameSchool = schoolId.toString() === user.schoolId.toString();
		if (!sameSchool) return [];

		return user.permissions;
	}

	async hasSchoolPermission(userId, schoolId, permision) {
		const permissions = await this.getSchoolPermissions(userId, schoolId);
		return permissions.includes(permision);
	}

	/**
	 * Throw an error if the user is not allowed to perform the given operation
	 * @param {News} news news item (required: {schoolId}, optional: {target, targetModel})
	 * @param {Object} { userId, schoolId }
	 * @param {String} permission permision to check
	 * @returns undefined
	 * @example
	 * await authorize(news, params.account, 'NEWS_VIEW') => undefined
	 * await authorize(news, params.account, 'NEWS_CREATE') => (throws Forbidden)
	 * @memberof NewsService
	 */
	async authorize(news = {}, userId, permission) {
		const authorized = await this.hasPermission(userId, permission, news);
		if (!authorized) {
			throw new Forbidden('Not authorized.');
		}
	}

	checkExistence(news, query) {
		if (!news) {
			logger.error(`Cannot find news item with query "${query}" => "${news}"`);
			throw new NotFound('News item does not exist.');
		}
	}
}

class NewsService extends AbstractService {
	static populateParams() {
		return {
			query: {
				$populate: [
					{ path: 'schoolId', select: ['_id', 'name'] },
					{ path: 'creatorId', select: ['_id', 'firstName', 'lastName'] },
					{ path: 'updaterId', select: ['_id', 'firstName', 'lastName'] },
					{ path: 'target', select: ['_id', 'name'] },
				],
			},
		};
	}

	static decorateResults(result) {
		const decorate = n => ({
			...n,
			school: n.schoolId,
			schoolId: (n.schoolId || {})._id,
			creator: n.creatorId,
			creatorId: (n.creatorId || {})._id,
			updater: n.updaterId,
			updaterId: (n.updaterId || {})._id,
		});
		if (result instanceof Array) {
			return result.map(decorate);
		}
		if (result.data) { // paginated result set
			const dataIdsFixed = result.data.map(decorate);
			return { ...result, data: dataIdsFixed };
		}
		return decorate(result);
	}

	async decoratePermissions(result, userId) {
		// decorate permissions in data objects
		const decoratedData = await Promise.all(result.data.map(async n => ({
			...n,
			permissions: await this.getPermissions(userId, {
				target: (n.target || {})._id,
				targetModel: n.targetModel,
				schoolId: n.schoolId,
			}),
		})));
		return { ...result, data: decoratedData };
	}

	/**
	 * Create a copy of the original news if it is edited
	 * @param {News} oldItem
	 * @returns {Promise<NewsHistory>} the created news history document
	 * @memberof NewsService
	 * @static
	 */
	static createHistoryEntry(oldItem) {
		const historyEntry = {
			title: oldItem.title,
			content: oldItem.content,
			displayAt: oldItem.displayAt,
			creatorId: oldItem.updaterId ? oldItem.updaterId : oldItem.creatorId,
			parentId: oldItem._id,
		};
		return newsHistoryModel.create(historyEntry);
	}

	async buildFindQuery(params, baseFilter) {
		const query = [];
		const scoped = !!(params.query && (params.query.target || params.query.targetModel));

		if (scoped) {
			// add selected scope news
			query.push(await super.createScopedQuery(
				params.account.userId, baseFilter.permission, params.query.target, params.query.targetModel,
			));
		} else {
			// add school news
			query.push(await super.createSchoolQuery(
				params.account.userId, params.account.schoolId, baseFilter.permission,
			));
			if (params.query && params.query.target !== 'school') {
				// add all scope news if more than the current school is requested
				query.push(await super.createScopedQuery(
					params.account.userId, baseFilter.permission,
				));
			}
		}
		return flatten(query);
	}

	/**
	 * GET /news/{id}
	 * Returns the news item specified by id
	 * @param {BsonId|String} id
	 * @param {Object} params
	 * @returns one news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async get(id, params) {
		const news = await this.app.service('newsModel').get(id, NewsService.populateParams());
		this.checkExistence(news, id);
		await this.authorize(news, params.account.userId, newsPermissions.VIEW);
		news.permissions = await this.getPermissions(params.account.userId, news);
		return news;
	}

	/**
	 * GET /news/
	 * Returns all news the user can see.
	 * @param {Object} params
	 * @returns paginated array of news items
	 * @memberof NewsService
	 */
	async find(params) {
		const now = Date.now();
		// based on params.unpublished divide between view published news and unpublished news with edit permission
		const baseFilter = {
			published: params.unpublished ? { $gt: now } : { $lte: now },
			permission: params.unpublished ? newsPermissions.EDIT : newsPermissions.VIEW,
		};
		const query = {
			query: {
				displayAt: baseFilter.published,
				$or: await this.buildFindQuery(params, baseFilter),
				$sort: (params.query || {}).$sort,
				$limit: (params.query || {}).$limit,
				$skip: (params.query || {}).$skip,
				$populate: NewsService.populateParams().query.$populate,
			},
			$paginate: (params.query || {}).$paginate,
		};
		return this.app.service('newsModel')
			.find(query)
			.then(NewsService.decorateResults)
			.then(result => this.decoratePermissions(result, params.account.userId));
	}

	/**
	 * POST /news/
	 * Creates a news item
	 * @param {Object} data @see NewsModel
	 * @param {Object} params
	 * @returns {News} the created news object
	 * @throws {Forbidden} if not authorized
	 * @memberof NewsService
	 */
	async create(data, params) {
		await this.authorize(data, params.account.userId, newsPermissions.CREATE);
		const newNewsData = {
			...data,
			creatorId: params.account.userId,
			updaterId: null,
		};
		return this.app.service('newsModel').create(newNewsData);
	}

	/**
	 * DELETE /news/{id}
	 * Deletes a single news item.
	 * @param {BsonId|String} id The news item's Id
	 * @param {Object} params Note that params.query won't work here
	 * @returns {News} the deleted news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async remove(id, params) {
		const news = await this.app.service('newsModel').get(id, NewsService.populateParams());
		this.checkExistence(news, id);
		await this.authorize(news, params.account.userId, newsPermissions.REMOVE);
		await this.app.service('newsModel').remove(id);
		return news;
	}

	/**
	 * PUT /news/{id}
	 * Replaces a single news item
	 * @param {BsonId|String} id The news item's Id
	 * @param {News} data updated news item
	 * @param {Object} params Feathers request params (note that using params.query won't work here)
	 * @returns {News} updated news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async update(id, data, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account.userId, newsPermissions.EDIT);
		const updatedNewsData = {
			...data,
			updaterId: params.account.userId,
		};
		const updatedNews = await this.app.service('newsModel').update(id, {
			...updatedNewsData, ...NewsService.populateParams(),
		});
		await NewsService.createHistoryEntry(news);
		return updatedNews;
	}

	/**
	 * PATCH /news/{id}
	 * Patches a single news item
	 * @param {BsonId|String} id The news item's Id
	 * @param {Object} data object containing updated news item attributes
	 * @param {Object} params Feathers request params (note that using params.query won't work here)
	 * @returns {News} patched news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async patch(id, data, params) {
		const news = await this.app.service('newsModel').get(id, NewsService.populateParams());
		this.checkExistence(news, id);
		await this.authorize(news, params.account.userId, newsPermissions.EDIT);
		const patchedNewsData = {
			...data,
			updaterId: params.account.userId,
		};
		const patchedNews = await this.app.service('newsModel').patch(id, {
			...patchedNewsData,
			...NewsService.populateParams(),
		});
		await NewsService.createHistoryEntry(news);
		return patchedNews;
	}
}

module.exports = function news() {
	const app = this;

	// use /news to access a user's news
	app.use('/news', new NewsService());
	app.service('news').hooks(hooks);

	// use /newsModel to directly access the model from other services
	// (external requests are blocked)
	app.use('/newsModel', service({
		Model: newsModel,
		lean: true,
		paginate: {
			default: 25,
		},
	}));
	app.service('/newsModel').hooks(newsModelHooks);
};
<commit_msg>Decorate returned results<commit_after>const service = require('feathers-mongoose');
const { Forbidden, NotFound, BadRequest } = require('@feathersjs/errors');
const logger = require('winston');
const { ObjectId } = require('mongoose').Types;
const {
	newsModel, targetModels, newsHistoryModel, newsPermissions,
} = require('./model');
const hooks = require('./hooks');
const newsModelHooks = require('./hooks/newsModel.hooks');
const { flatten } = require('../../utils/array');

class AbstractService {
	setup(app) {
		this.app = app;
	}

	/**
	* Returns school query if the user is allowed to see.
	* @param userId The user's Id
	* @param schoolId The schoolId
	* @returns Query
	*/
	createSchoolQuery(userId, schoolId, permission) {
		return this.hasSchoolPermission(userId, schoolId, permission)
			.then((hasPermission) => {
				if (!hasPermission) {
					return null;
				}
				return [{
					schoolId,
					target: { $exists: false },
				}];
			});
	}

	/**
		 * Returns scoped news the user is allowed to see
		 *
		 * @param {BsonId|String} userId the user's Id
		 * @param {BsonId|String} target (optional) Id of the news target (course, team, etc.)
		 * @returns Array<Query>
		 */
	async createScopedQuery(userId, permission, target = null, targetModel = null) {
		// check params
		if (target ? !targetModel : targetModel) {
			throw new BadRequest('target and targetModel, both must be given or not');
		}
		// only one target requested
		if (target && targetModel) {
			return this.hasPermission(userId, permission, { target, targetModel })
				.then(() => (
					[{
						targetModel,
						target,
					}]));
		}
		// return data of all user scopes
		const ops = targetModels.map(async (scope) => {
			// For each possible target model, find all targets the user has NEWS_VIEW permissions in.
			const scopeListService = this.app.service(`/users/:scopeId/${scope}`);
			if (scopeListService === undefined) {
				return null;
			}
			const scopeItems = await scopeListService.find({
				route: { scopeId: userId.toString() },
				query: {
					permissions: [permission],
				},
			});
			return scopeItems.map(item => ({
				targetModel: scope,
				target: item._id,
			}));
		});
		const results = await Promise.all(ops);
		return flatten(results.filter(r => r !== null));
	}

	/* Permissions */

	async getPermissions(userId, { target, targetModel, schoolId } = {}) {
		// target and school might be populated or not
		const isObjectId = o => o instanceof ObjectId || typeof o === 'string';
		// scope case: user role in scope must have given permission
		if (target && targetModel) {
			const targetId = isObjectId(target) ? target.toString() : target._id.toString();
			const scope = this.app.service(`${targetModel}/:scopeId/userPermissions/`);
			const params = { route: { scopeId: targetId } };
			const scopePermissions = await scope.get(userId, params);
			return scopePermissions;
		}

		// default school case: dataItem and users schoolId must match and user permission must exist
		return this.getSchoolPermissions(userId, isObjectId(schoolId) ? schoolId : schoolId._id);
	}

	/**
	 * Checks scoped permission for a user and given news.
	 * @param {BsonId|String} userId
	 * @param {String} permission
	 * @param {BsonId|String} targetId (optional) news target (scope) id. Only valid with targetModel.
	 * @param {String} targetModel (optional) news target (scope) model. Only valid with target.
	 * @returns {Promise<Boolean>} Promise that resolves to true/false
	 * @example
	 * await hasPermission(user._id, 'NEWS_VIEW') => true
	 * await hasPermission(user._id, 'NEWS_CREATE', team._id, 'teams') => false
	 * @memberof NewsService
	 */
	async hasPermission(userId, permission, dataItem) {
		if (!dataItem) {
			// maybe, use hasSchoolPermission instead...
			return false;
		}
		const permissions = await this.getPermissions(userId, dataItem);
		return permissions.includes(permission);
	}

	/**
	 * Tests if a user with given userId has a (global) permission within a school.
	 * @param {ObjectId} userId
	 * @param {ObjectId} schoolId
	 * @returns {Array<String>}
	 */
	async getSchoolPermissions(userId, schoolId) {
		// test user exists
		const user = await this.app.service('users').get(userId);
		if (user == null) return [];

		// test user is school member
		const sameSchool = schoolId.toString() === user.schoolId.toString();
		if (!sameSchool) return [];

		return user.permissions;
	}

	async hasSchoolPermission(userId, schoolId, permision) {
		const permissions = await this.getSchoolPermissions(userId, schoolId);
		return permissions.includes(permision);
	}

	/**
	 * Throw an error if the user is not allowed to perform the given operation
	 * @param {News} news news item (required: {schoolId}, optional: {target, targetModel})
	 * @param {Object} { userId, schoolId }
	 * @param {String} permission permision to check
	 * @returns undefined
	 * @example
	 * await authorize(news, params.account, 'NEWS_VIEW') => undefined
	 * await authorize(news, params.account, 'NEWS_CREATE') => (throws Forbidden)
	 * @memberof NewsService
	 */
	async authorize(news = {}, userId, permission) {
		const authorized = await this.hasPermission(userId, permission, news);
		if (!authorized) {
			throw new Forbidden('Not authorized.');
		}
	}

	checkExistence(news, query) {
		if (!news) {
			logger.error(`Cannot find news item with query "${query}" => "${news}"`);
			throw new NotFound('News item does not exist.');
		}
	}
}

class NewsService extends AbstractService {
	static populateParams() {
		return {
			query: {
				$populate: [
					{ path: 'schoolId', select: ['_id', 'name'] },
					{ path: 'creatorId', select: ['_id', 'firstName', 'lastName'] },
					{ path: 'updaterId', select: ['_id', 'firstName', 'lastName'] },
					{ path: 'target', select: ['_id', 'name'] },
				],
			},
		};
	}

	static decorateResults(result) {
		const decorate = n => ({
			...n,
			school: n.schoolId,
			schoolId: (n.schoolId || {})._id,
			creator: n.creatorId,
			creatorId: (n.creatorId || {})._id,
			updater: n.updaterId,
			updaterId: (n.updaterId || {})._id,
		});
		if (result instanceof Array) {
			return result.map(decorate);
		}
		if (result.data) { // paginated result set
			const dataIdsFixed = result.data.map(decorate);
			return { ...result, data: dataIdsFixed };
		}
		return decorate(result);
	}

	async decoratePermissions(result, userId) {
		// decorate permissions in data objects
		const decoratedData = await Promise.all(result.data.map(async n => ({
			...n,
			permissions: await this.getPermissions(userId, {
				target: (n.target || {})._id,
				targetModel: n.targetModel,
				schoolId: n.schoolId,
			}),
		})));
		return { ...result, data: decoratedData };
	}

	/**
	 * Create a copy of the original news if it is edited
	 * @param {News} oldItem
	 * @returns {Promise<NewsHistory>} the created news history document
	 * @memberof NewsService
	 * @static
	 */
	static createHistoryEntry(oldItem) {
		const historyEntry = {
			title: oldItem.title,
			content: oldItem.content,
			displayAt: oldItem.displayAt,
			creatorId: oldItem.updaterId ? oldItem.updaterId : oldItem.creatorId,
			parentId: oldItem._id,
		};
		return newsHistoryModel.create(historyEntry);
	}

	async buildFindQuery(params, baseFilter) {
		const query = [];
		const scoped = !!(params.query && (params.query.target || params.query.targetModel));

		if (scoped) {
			// add selected scope news
			query.push(await super.createScopedQuery(
				params.account.userId, baseFilter.permission, params.query.target, params.query.targetModel,
			));
		} else {
			// add school news
			query.push(await super.createSchoolQuery(
				params.account.userId, params.account.schoolId, baseFilter.permission,
			));
			if (params.query && params.query.target !== 'school') {
				// add all scope news if more than the current school is requested
				query.push(await super.createScopedQuery(
					params.account.userId, baseFilter.permission,
				));
			}
		}
		return flatten(query);
	}

	/**
	 * GET /news/{id}
	 * Returns the news item specified by id
	 * @param {BsonId|String} id
	 * @param {Object} params
	 * @returns one news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async get(id, params) {
		const news = await this.app.service('newsModel').get(id, NewsService.populateParams());
		this.checkExistence(news, id);
		await this.authorize(news, params.account.userId, newsPermissions.VIEW);
		news.permissions = await this.getPermissions(params.account.userId, news);
		return NewsService.decorateResults(news);
	}

	/**
	 * GET /news/
	 * Returns all news the user can see.
	 * @param {Object} params
	 * @returns paginated array of news items
	 * @memberof NewsService
	 */
	async find(params) {
		const now = Date.now();
		// based on params.unpublished divide between view published news and unpublished news with edit permission
		const baseFilter = {
			published: params.unpublished ? { $gt: now } : { $lte: now },
			permission: params.unpublished ? newsPermissions.EDIT : newsPermissions.VIEW,
		};
		const query = {
			query: {
				displayAt: baseFilter.published,
				$or: await this.buildFindQuery(params, baseFilter),
				$sort: (params.query || {}).$sort,
				$limit: (params.query || {}).$limit,
				$skip: (params.query || {}).$skip,
				$populate: NewsService.populateParams().query.$populate,
			},
			$paginate: (params.query || {}).$paginate,
		};
		return this.app.service('newsModel')
			.find(query)
			.then(NewsService.decorateResults)
			.then(result => this.decoratePermissions(result, params.account.userId));
	}

	/**
	 * POST /news/
	 * Creates a news item
	 * @param {Object} data @see NewsModel
	 * @param {Object} params
	 * @returns {News} the created news object
	 * @throws {Forbidden} if not authorized
	 * @memberof NewsService
	 */
	async create(data, params) {
		await this.authorize(data, params.account.userId, newsPermissions.CREATE);
		const newNewsData = {
			...data,
			creatorId: params.account.userId,
			updaterId: null,
		};
		return this.app.service('newsModel').create(newNewsData);
	}

	/**
	 * DELETE /news/{id}
	 * Deletes a single news item.
	 * @param {BsonId|String} id The news item's Id
	 * @param {Object} params Note that params.query won't work here
	 * @returns {News} the deleted news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async remove(id, params) {
		const news = await this.app.service('newsModel').get(id, NewsService.populateParams());
		this.checkExistence(news, id);
		await this.authorize(news, params.account.userId, newsPermissions.REMOVE);
		await this.app.service('newsModel').remove(id);
		return NewsService.decorateResults(news);
	}

	/**
	 * PUT /news/{id}
	 * Replaces a single news item
	 * @param {BsonId|String} id The news item's Id
	 * @param {News} data updated news item
	 * @param {Object} params Feathers request params (note that using params.query won't work here)
	 * @returns {News} updated news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async update(id, data, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account.userId, newsPermissions.EDIT);
		const updatedNewsData = {
			...data,
			updaterId: params.account.userId,
		};
		const updatedNews = await this.app.service('newsModel').update(id, {
			...updatedNewsData, ...NewsService.populateParams(),
		});
		await NewsService.createHistoryEntry(news);
		return NewsService.decorateResults(updatedNews);
	}

	/**
	 * PATCH /news/{id}
	 * Patches a single news item
	 * @param {BsonId|String} id The news item's Id
	 * @param {Object} data object containing updated news item attributes
	 * @param {Object} params Feathers request params (note that using params.query won't work here)
	 * @returns {News} patched news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async patch(id, data, params) {
		const news = await this.app.service('newsModel').get(id, NewsService.populateParams());
		this.checkExistence(news, id);
		await this.authorize(news, params.account.userId, newsPermissions.EDIT);
		const patchedNewsData = {
			...data,
			updaterId: params.account.userId,
		};
		const patchedNews = await this.app.service('newsModel').patch(id, {
			...patchedNewsData,
			...NewsService.populateParams(),
		});
		await NewsService.createHistoryEntry(news);
		return NewsService.decorateResults(patchedNews);
	}
}

module.exports = function news() {
	const app = this;

	// use /news to access a user's news
	app.use('/news', new NewsService());
	app.service('news').hooks(hooks);

	// use /newsModel to directly access the model from other services
	// (external requests are blocked)
	app.use('/newsModel', service({
		Model: newsModel,
		lean: true,
		paginate: {
			default: 25,
		},
	}));
	app.service('/newsModel').hooks(newsModelHooks);
};
<|endoftext|>


<commit_before>$(document).ready(function() {
	var store = "http://143.93.114.137/sparqlproxy/SPARQL";
	var query = 'SELECT * WHERE { ?s ?p ?o.  }';
	query = encodeURIComponent(query);
	$.ajax({
		async: false,
		dataType: 'json',
		data: {
			repo: 'idarit',
			query: query,
			format: 'json'
		},
		url: store,
		error: function(jqXHR, textStatus, errorThrown) {
			alert(errorThrown);
		},
		success: function(response) {
			var bindings = response.results.bindings;
			console.log(bindings);
		}
	});
});

$(document).ready(function() {
	var store = "https://dbpedia.org/sparql";
	var query = 'SELECT * WHERE { ?s ?p ?o. FILTER (?s=<http://dbpedia.org/resource/Mainz>) }';
	$.ajax({
		async: false,
		data: {
			"default-graph-uri": "http://dbpedia.org",
			"query": query,
			"format": 'application/sparql-results+json'
		},
		url: store,
		error: function(jqXHR, textStatus, errorThrown) {
			alert(errorThrown);
		},
		success: function(response) {
			var bindings = response.results.bindings;
			console.log(bindings);
		}
	});
});

$(document).ready(function() {
	var store = "http://api.geonames.org/get";
	$.ajax({
		async: false,
		url: store,
		data: {
			geonameId: 6554818,
			username: "i3obm"
		},
		error: function(jqXHR, textStatus, errorThrown) {
			alert(errorThrown);
		},
		success: function(response) {
			console.log(response);
			console.log(response.getElementsByTagName("name")[0].innerHTML);
		}
	});
});

$(document).ready(function() {
	var store = "http://143.93.114.137/sparqlproxy/GND";
	var uri = 'http://d-nb.info/gnd/1063654211';
	$.ajax({
		async: false,
		dataType: 'json',
		data: {
			uri: uri
		},
		url: store,
		error: function(jqXHR, textStatus, errorThrown) {
			alert(errorThrown);
		},
		success: function(response) {
			var object = response[uri];
			console.log(object["http://d-nb.info/standards/elementset/gnd#preferredNameForTheCorporateBody"][0].value);
		}
	});
});<commit_msg>modiefied examples<commit_after>$(document).ready(function() {
	var store = "http://143.93.114.137/sparqlproxy/SPARQL";
	var query = 'SELECT * WHERE { ?s ?p ?o. } LIMIT 10';
	query = encodeURIComponent(query);
	$.ajax({
		async: false,
		dataType: 'json',
		data: {
			repo: 'annedb',
			query: query,
			format: 'json'
		},
		url: store,
		error: function(jqXHR, textStatus, errorThrown) {
			alert(errorThrown);
		},
		success: function(response) {
			var bindings = response.results.bindings;
			console.log(bindings);
		}
	});
});

$(document).ready(function() {
	var store = "https://dbpedia.org/sparql";
	var query = 'SELECT * WHERE { ?s ?p ?o. FILTER (?s=<http://dbpedia.org/resource/Mainz>) }';
	$.ajax({
		async: false,
		data: {
			"default-graph-uri": "http://dbpedia.org",
			"query": query,
			"format": 'application/sparql-results+json'
		},
		url: store,
		error: function(jqXHR, textStatus, errorThrown) {
			alert(errorThrown);
		},
		success: function(response) {
			var bindings = response.results.bindings;
			console.log(bindings);
		}
	});
});

$(document).ready(function() {
	var store = "http://api.geonames.org/get";
	$.ajax({
		async: false,
		url: store,
		data: {
			geonameId: 6554818,
			username: "i3obm"
		},
		error: function(jqXHR, textStatus, errorThrown) {
			alert(errorThrown);
		},
		success: function(response) {
			console.log(response);
			console.log(response.getElementsByTagName("name")[0].innerHTML);
		}
	});
});

$(document).ready(function() {
	var store = "http://143.93.114.137/sparqlproxy/GND";
	var uri = 'http://d-nb.info/gnd/1063654211';
	$.ajax({
		async: false,
		dataType: 'json',
		data: {
			uri: uri
		},
		url: store,
		error: function(jqXHR, textStatus, errorThrown) {
			alert(errorThrown);
		},
		success: function(response) {
			var object = response[uri];
			console.log(object["http://d-nb.info/standards/elementset/gnd#preferredNameForTheCorporateBody"][0].value);
		}
	});
});<|endoftext|>


<commit_before>// TODO: Ideally the separator would be defined in one place. Right now it is
//       also defined corehq.apps.userreports.reports.filters.CHOICE_DELIMITER
var select2Separator = "\u001F";

var ReportModule = (function () {
    function Config(dict) {
        var self = this;

        dict = dict || {};

        self.keyValuePairs = ko.observableArray();
        _.each(dict, function(value, key) {
            self.keyValuePairs.push([ko.observable(key), ko.observable(value)]);
        });

        self.addConfig = function() {
            self.keyValuePairs.push([ko.observable(), ko.observable()]);
        };
    }

    function GraphConfig(report_id, reportId, availableReportIds, reportCharts, graph_configs, changeSaveButton) {
        var self = this;

        graph_configs = graph_configs || {};

        this.graphConfigs = {};
        for (var i = 0; i < availableReportIds.length; i++) {
            var currentReportId = availableReportIds[i];
            self.graphConfigs[currentReportId] = {};
            for (var j = 0; j < reportCharts[currentReportId].length; j++) {
                var currentChart = reportCharts[currentReportId][j];
                var graph_config = graph_configs[currentChart.chart_id] || {};
                var series_configs = {};
                var chart_series = [];
                for(var k = 0; k < currentChart.y_axis_columns.length; k++) {
                    var series = currentChart.y_axis_columns[k].column_id;
                    chart_series.push(series);
                    series_configs[series] = new Config(
                        currentReportId === report_id ? (graph_config.series_configs || {})[series] || {} : {}
                    );
                }

                self.graphConfigs[currentReportId][currentChart.chart_id] = {
                    graph_type: ko.observable(currentReportId === report_id ? graph_config.graph_type || 'bar' : 'bar'),
                    series_configs: series_configs,
                    chart_series: chart_series,
                    config: new Config(
                        currentReportId === report_id ? graph_config.config || {} : {}
                    )
                };
            }
        }

        this.currentGraphConfigs = ko.computed(function() {
            return self.graphConfigs[reportId()];
        });

        this.currentCharts = ko.computed(function() {
            return reportCharts[reportId()];
        });

        this.getCurrentGraphConfig = function(chart_id) {
            return self.currentGraphConfigs()[chart_id] || {config: {keyValuePairs: []}};
        };

        this.toJSON = function () {
            function configToDict(config) {
                var dict = {};
                var keyValuePairs = config.keyValuePairs();
                for (var i = 0; i < keyValuePairs.length; i++) {
                    dict[keyValuePairs[i][0]()] = keyValuePairs[i][1]();
                }
                return dict;
            }

            var chartsToConfigs = {};
            var currentChartsToConfigs = self.currentGraphConfigs();
            _.each(currentChartsToConfigs, function(graph_config, chart_id) {
                chartsToConfigs[chart_id] = {
                    series_configs: {}
                };
                _.each(graph_config.series_configs, function(series_config, series) {
                    chartsToConfigs[chart_id].series_configs[series] = configToDict(series_config);
                });
                chartsToConfigs[chart_id].graph_type = graph_config.graph_type();
                chartsToConfigs[chart_id].config = configToDict(graph_config.config);
            });
            return chartsToConfigs;
        };

        this.addSubscribersToSaveButton = function() {
            var addSubscriberToSaveButton = function(observable) {
                observable.subscribe(changeSaveButton);
            };
            var addConfigToSaveButton = function(config) {
                addSubscriberToSaveButton(config.keyValuePairs);
                _.each(config.keyValuePairs(), function(keyValuePair) {
                    addSubscriberToSaveButton(keyValuePair[0]);
                    addSubscriberToSaveButton(keyValuePair[1]);
                });
            };
            _.each(self.graphConfigs, function(reportGraphConfigs) {
                _.each(reportGraphConfigs, function(graphConfig) {
                    addSubscriberToSaveButton(graphConfig.graph_type);
                    addConfigToSaveButton(graphConfig.config);
                    _.each(graphConfig.series_configs, addConfigToSaveButton);
                });
            });
        };

        this.allGraphTypes = ['bar', 'time', 'xy'];
    }

    /**
     * View-model for the filters of a mobile UCR.
     *
     * @param savedReportId - the id of the report, currently saved. Can be undefined for unsaved report.
     * @param selectedReportId - KO observable for the id of the currently selected report
     * @param filterValues - { slug : saved filter data } for each saved filter
     * @param reportFilters - { report id --> [ { slug: filter slug } for each filter in report ] for each report }
     * @param changeSaveButton - function that enables the "Save" button
     */
    function FilterConfig(savedReportId, selectedReportId, filterValues, reportFilters, changeSaveButton) {
        var self = this;

        this.reportFilters = JSON.parse(JSON.stringify(reportFilters || {}));
        _.each(this.reportFilters, function(filtersInReport, id) {
            for (var i = 0; i < filtersInReport.length; i++) {
                var filter = filtersInReport[i];
                if (id === savedReportId && filterValues.hasOwnProperty(filter.slug)) {
                    filter.selectedValue = filterValues[filter.slug];
                    filter.selectedValue.doc_type = ko.observable(filter.selectedValue.doc_type);
                } else {
                    filter.selectedValue = {
                        doc_type: ko.observable(null)
                    };
                }
                var filterFields = [
                    'custom_data_property',
                    'date_range',
                    'filter_type',
                    'select_value'
                ];
                for(var filterFieldsIndex = 0; filterFieldsIndex < filterFields.length; filterFieldsIndex++) {
                    filter.selectedValue[filterFields[filterFieldsIndex]] = ko.observable(filter.selectedValue[filterFields[filterFieldsIndex]] || '');
                }
                filter.selectedValue.value = ko.observable(filter.selectedValue.value ? filter.selectedValue.value.join(select2Separator) : '');

                filter.dynamicFilterName = ko.computed(function () {
                    return selectedReportId() + '/' + filter.slug;
                });

                if(filter.choices !== undefined && filter.show_all) {
                    filter.choices.unshift({value: "_all", display: "Show All"}); // TODO: translate
                }
            }
        });

        this.selectedFilterStructure = ko.computed(function () { // for the chosen report
            return self.reportFilters[selectedReportId()];
        });

        this.toJSON = function () {
            var selectedFilterStructure = self.selectedFilterStructure();
            var selectedFilterValues = {};
            for (var i = 0; i < selectedFilterStructure.length; i++) {
                var filter = selectedFilterStructure[i];
                if (filter.selectedValue.doc_type()) {
                    selectedFilterValues[filter.slug] = {};
                    selectedFilterValues[filter.slug].doc_type = filter.selectedValue.doc_type();
                    // Depending on doc_type, pull the correct observables' values
                    var docTypeToField = {
                        AutoFilter: 'filter_type',
                        CustomDataAutoFilter: 'custom_data_property',
                        StaticChoiceFilter: 'select_value',
                        StaticDatespanFilter: 'date_range'
                    };
                    _.each(docTypeToField, function(field, docType) {
                        if(filter.selectedValue.doc_type() === docType) {
                            selectedFilterValues[filter.slug][field] = filter.selectedValue[field]();
                        }
                    });
                    if(filter.selectedValue.doc_type() === 'StaticChoiceListFilter') {
                        selectedFilterValues[filter.slug].value = filter.selectedValue.value().split(select2Separator);
                    }
                }
            }
            return selectedFilterValues;
        };

        this.addSubscribersToSaveButton = function() {
            var addSubscriberToSaveButton = function(observable) {
                observable.subscribe(changeSaveButton);
            };
            _.each(this.reportFilters, function(filtersInReport) {
                for (var i = 0; i < filtersInReport.length; i++) {
                    var filter = filtersInReport[i];
                    _.each(filter.selectedValue, addSubscriberToSaveButton);
                }
            });
        };

        // TODO - add user-friendly text
        this.filterDocTypes = [null, 'AutoFilter', 'StaticDatespanFilter', 'CustomDataAutoFilter', 'StaticChoiceListFilter', 'StaticChoiceFilter'];
        this.autoFilterTypes = ['case_sharing_group', 'location_id', 'username', 'user_id'];
        this.date_range_options = ['last7', 'last30', 'lastmonth', 'lastyear'];
    }

    function ReportConfig(report_id, display, description, uuid, availableReportIds,
                          reportCharts, graph_configs,
                          filterValues, reportFilters,
                          language, changeSaveButton) {
        var self = this;
        this.lang = language;
        this.fullDisplay = display || {};
        this.availableReportIds = availableReportIds;
        this.display = ko.observable(this.fullDisplay[this.lang]);
        this.description = ko.observable(description);
        this.uuid = uuid;
        this.reportId = ko.observable(report_id);
        this.graphConfig = new GraphConfig(report_id, this.reportId, availableReportIds, reportCharts, graph_configs, changeSaveButton);
        this.filterConfig = new FilterConfig(report_id, this.reportId, filterValues, reportFilters, changeSaveButton);

        this.toJSON = function () {
            self.fullDisplay[self.lang] = self.display();
            return {
                report_id: self.reportId(),
                graph_configs: self.graphConfig.toJSON(),
                filters: self.filterConfig.toJSON(),
                header: self.fullDisplay,
                description: self.description(),
                uuid: self.uuid
            };
        };
    }

    function ReportModule(options) {
        var self = this;
        var currentReports = options.currentReports || [];
        var availableReports = options.availableReports || [];
        var saveURL = options.saveURL;
        self.lang = options.lang;
        self.moduleName = options.moduleName;
        self.currentModuleName = ko.observable(options.moduleName[self.lang]);
        self.reportTitles = {};
        self.reportDescriptions = {};
        self.reportCharts = {};
        self.reportFilters = {};
        self.reports = ko.observableArray([]);
        for (var i = 0; i < availableReports.length; i++) {
            var report = availableReports[i];
            var report_id = report.report_id;
            self.reportTitles[report_id] = report.title;
            self.reportDescriptions[report_id] = report.description;
            self.reportCharts[report_id] = report.charts;
            self.reportFilters[report_id] = report.filter_structure;
        }

        self.availableReportIds = _.map(options.availableReports, function (r) { return r.report_id; });

        self.defaultReportTitle = function (reportId) {
            return self.reportTitles[reportId];
        };
        self.defaultReportDescription = function (reportId) {
            return self.reportDescriptions[reportId];
        };

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unsaved changes in your report list module",
            save: function () {
                // validate that all reports have valid data
                var reports = self.reports();
                for (var i = 0; i < reports.length; i++) {
                    if (!reports[i].reportId() || !reports[i].display()) {
                        alert('Reports must have all properties set!');
                    }
                }
                self.moduleName[self.lang] = self.currentModuleName();
                self.saveButton.ajax({
                    url: saveURL,
                    type: 'post',
                    dataType: 'json',
                    data: {
                        name: JSON.stringify(self.moduleName),
                        reports: JSON.stringify(_.map(self.reports(), function (r) { return r.toJSON(); }))
                    }
                });
            }
        });

        var changeSaveButton = function () {
            self.saveButton.fire('change');
        };

        self.currentModuleName.subscribe(changeSaveButton);

        function newReport(options) {
            options = options || {};
            var report = new ReportConfig(
                options.report_id,
                options.header,
                options.description,
                options.uuid,
                self.availableReportIds,
                self.reportCharts,
                options.graph_configs,
                options.filters,
                self.reportFilters,
                self.lang,
                changeSaveButton
            );
            report.display.subscribe(changeSaveButton);
            report.reportId.subscribe(changeSaveButton);
            report.reportId.subscribe(function (reportId) {
                report.display(self.defaultReportTitle(reportId));
            });
            report.reportId.subscribe(function (reportId) {
                report.description(self.defaultReportDescription(reportId));
            });

            return report;
        }
        this.addReport = function () {
            self.reports.push(newReport());
        };
        this.removeReport = function (report) {
            self.reports.remove(report);
            changeSaveButton();
        };

        // add existing reports to UI
        for (i = 0; i < currentReports.length; i += 1) {
            var report = newReport(currentReports[i]);
            self.reports.push(report);
        }
    }

    return ReportModule;
}());
<commit_msg>enable Save button upon editing the Report Description<commit_after>// TODO: Ideally the separator would be defined in one place. Right now it is
//       also defined corehq.apps.userreports.reports.filters.CHOICE_DELIMITER
var select2Separator = "\u001F";

var ReportModule = (function () {
    function Config(dict) {
        var self = this;

        dict = dict || {};

        self.keyValuePairs = ko.observableArray();
        _.each(dict, function(value, key) {
            self.keyValuePairs.push([ko.observable(key), ko.observable(value)]);
        });

        self.addConfig = function() {
            self.keyValuePairs.push([ko.observable(), ko.observable()]);
        };
    }

    function GraphConfig(report_id, reportId, availableReportIds, reportCharts, graph_configs, changeSaveButton) {
        var self = this;

        graph_configs = graph_configs || {};

        this.graphConfigs = {};
        for (var i = 0; i < availableReportIds.length; i++) {
            var currentReportId = availableReportIds[i];
            self.graphConfigs[currentReportId] = {};
            for (var j = 0; j < reportCharts[currentReportId].length; j++) {
                var currentChart = reportCharts[currentReportId][j];
                var graph_config = graph_configs[currentChart.chart_id] || {};
                var series_configs = {};
                var chart_series = [];
                for(var k = 0; k < currentChart.y_axis_columns.length; k++) {
                    var series = currentChart.y_axis_columns[k].column_id;
                    chart_series.push(series);
                    series_configs[series] = new Config(
                        currentReportId === report_id ? (graph_config.series_configs || {})[series] || {} : {}
                    );
                }

                self.graphConfigs[currentReportId][currentChart.chart_id] = {
                    graph_type: ko.observable(currentReportId === report_id ? graph_config.graph_type || 'bar' : 'bar'),
                    series_configs: series_configs,
                    chart_series: chart_series,
                    config: new Config(
                        currentReportId === report_id ? graph_config.config || {} : {}
                    )
                };
            }
        }

        this.currentGraphConfigs = ko.computed(function() {
            return self.graphConfigs[reportId()];
        });

        this.currentCharts = ko.computed(function() {
            return reportCharts[reportId()];
        });

        this.getCurrentGraphConfig = function(chart_id) {
            return self.currentGraphConfigs()[chart_id] || {config: {keyValuePairs: []}};
        };

        this.toJSON = function () {
            function configToDict(config) {
                var dict = {};
                var keyValuePairs = config.keyValuePairs();
                for (var i = 0; i < keyValuePairs.length; i++) {
                    dict[keyValuePairs[i][0]()] = keyValuePairs[i][1]();
                }
                return dict;
            }

            var chartsToConfigs = {};
            var currentChartsToConfigs = self.currentGraphConfigs();
            _.each(currentChartsToConfigs, function(graph_config, chart_id) {
                chartsToConfigs[chart_id] = {
                    series_configs: {}
                };
                _.each(graph_config.series_configs, function(series_config, series) {
                    chartsToConfigs[chart_id].series_configs[series] = configToDict(series_config);
                });
                chartsToConfigs[chart_id].graph_type = graph_config.graph_type();
                chartsToConfigs[chart_id].config = configToDict(graph_config.config);
            });
            return chartsToConfigs;
        };

        this.addSubscribersToSaveButton = function() {
            var addSubscriberToSaveButton = function(observable) {
                observable.subscribe(changeSaveButton);
            };
            var addConfigToSaveButton = function(config) {
                addSubscriberToSaveButton(config.keyValuePairs);
                _.each(config.keyValuePairs(), function(keyValuePair) {
                    addSubscriberToSaveButton(keyValuePair[0]);
                    addSubscriberToSaveButton(keyValuePair[1]);
                });
            };
            _.each(self.graphConfigs, function(reportGraphConfigs) {
                _.each(reportGraphConfigs, function(graphConfig) {
                    addSubscriberToSaveButton(graphConfig.graph_type);
                    addConfigToSaveButton(graphConfig.config);
                    _.each(graphConfig.series_configs, addConfigToSaveButton);
                });
            });
        };

        this.allGraphTypes = ['bar', 'time', 'xy'];
    }

    /**
     * View-model for the filters of a mobile UCR.
     *
     * @param savedReportId - the id of the report, currently saved. Can be undefined for unsaved report.
     * @param selectedReportId - KO observable for the id of the currently selected report
     * @param filterValues - { slug : saved filter data } for each saved filter
     * @param reportFilters - { report id --> [ { slug: filter slug } for each filter in report ] for each report }
     * @param changeSaveButton - function that enables the "Save" button
     */
    function FilterConfig(savedReportId, selectedReportId, filterValues, reportFilters, changeSaveButton) {
        var self = this;

        this.reportFilters = JSON.parse(JSON.stringify(reportFilters || {}));
        _.each(this.reportFilters, function(filtersInReport, id) {
            for (var i = 0; i < filtersInReport.length; i++) {
                var filter = filtersInReport[i];
                if (id === savedReportId && filterValues.hasOwnProperty(filter.slug)) {
                    filter.selectedValue = filterValues[filter.slug];
                    filter.selectedValue.doc_type = ko.observable(filter.selectedValue.doc_type);
                } else {
                    filter.selectedValue = {
                        doc_type: ko.observable(null)
                    };
                }
                var filterFields = [
                    'custom_data_property',
                    'date_range',
                    'filter_type',
                    'select_value'
                ];
                for(var filterFieldsIndex = 0; filterFieldsIndex < filterFields.length; filterFieldsIndex++) {
                    filter.selectedValue[filterFields[filterFieldsIndex]] = ko.observable(filter.selectedValue[filterFields[filterFieldsIndex]] || '');
                }
                filter.selectedValue.value = ko.observable(filter.selectedValue.value ? filter.selectedValue.value.join(select2Separator) : '');

                filter.dynamicFilterName = ko.computed(function () {
                    return selectedReportId() + '/' + filter.slug;
                });

                if(filter.choices !== undefined && filter.show_all) {
                    filter.choices.unshift({value: "_all", display: "Show All"}); // TODO: translate
                }
            }
        });

        this.selectedFilterStructure = ko.computed(function () { // for the chosen report
            return self.reportFilters[selectedReportId()];
        });

        this.toJSON = function () {
            var selectedFilterStructure = self.selectedFilterStructure();
            var selectedFilterValues = {};
            for (var i = 0; i < selectedFilterStructure.length; i++) {
                var filter = selectedFilterStructure[i];
                if (filter.selectedValue.doc_type()) {
                    selectedFilterValues[filter.slug] = {};
                    selectedFilterValues[filter.slug].doc_type = filter.selectedValue.doc_type();
                    // Depending on doc_type, pull the correct observables' values
                    var docTypeToField = {
                        AutoFilter: 'filter_type',
                        CustomDataAutoFilter: 'custom_data_property',
                        StaticChoiceFilter: 'select_value',
                        StaticDatespanFilter: 'date_range'
                    };
                    _.each(docTypeToField, function(field, docType) {
                        if(filter.selectedValue.doc_type() === docType) {
                            selectedFilterValues[filter.slug][field] = filter.selectedValue[field]();
                        }
                    });
                    if(filter.selectedValue.doc_type() === 'StaticChoiceListFilter') {
                        selectedFilterValues[filter.slug].value = filter.selectedValue.value().split(select2Separator);
                    }
                }
            }
            return selectedFilterValues;
        };

        this.addSubscribersToSaveButton = function() {
            var addSubscriberToSaveButton = function(observable) {
                observable.subscribe(changeSaveButton);
            };
            _.each(this.reportFilters, function(filtersInReport) {
                for (var i = 0; i < filtersInReport.length; i++) {
                    var filter = filtersInReport[i];
                    _.each(filter.selectedValue, addSubscriberToSaveButton);
                }
            });
        };

        // TODO - add user-friendly text
        this.filterDocTypes = [null, 'AutoFilter', 'StaticDatespanFilter', 'CustomDataAutoFilter', 'StaticChoiceListFilter', 'StaticChoiceFilter'];
        this.autoFilterTypes = ['case_sharing_group', 'location_id', 'username', 'user_id'];
        this.date_range_options = ['last7', 'last30', 'lastmonth', 'lastyear'];
    }

    function ReportConfig(report_id, display, description, uuid, availableReportIds,
                          reportCharts, graph_configs,
                          filterValues, reportFilters,
                          language, changeSaveButton) {
        var self = this;
        this.lang = language;
        this.fullDisplay = display || {};
        this.availableReportIds = availableReportIds;
        this.display = ko.observable(this.fullDisplay[this.lang]);
        this.description = ko.observable(description);
        this.description.subscribe(changeSaveButton);
        this.uuid = uuid;
        this.reportId = ko.observable(report_id);
        this.graphConfig = new GraphConfig(report_id, this.reportId, availableReportIds, reportCharts, graph_configs, changeSaveButton);
        this.filterConfig = new FilterConfig(report_id, this.reportId, filterValues, reportFilters, changeSaveButton);

        this.toJSON = function () {
            self.fullDisplay[self.lang] = self.display();
            return {
                report_id: self.reportId(),
                graph_configs: self.graphConfig.toJSON(),
                filters: self.filterConfig.toJSON(),
                header: self.fullDisplay,
                description: self.description(),
                uuid: self.uuid
            };
        };
    }

    function ReportModule(options) {
        var self = this;
        var currentReports = options.currentReports || [];
        var availableReports = options.availableReports || [];
        var saveURL = options.saveURL;
        self.lang = options.lang;
        self.moduleName = options.moduleName;
        self.currentModuleName = ko.observable(options.moduleName[self.lang]);
        self.reportTitles = {};
        self.reportDescriptions = {};
        self.reportCharts = {};
        self.reportFilters = {};
        self.reports = ko.observableArray([]);
        for (var i = 0; i < availableReports.length; i++) {
            var report = availableReports[i];
            var report_id = report.report_id;
            self.reportTitles[report_id] = report.title;
            self.reportDescriptions[report_id] = report.description;
            self.reportCharts[report_id] = report.charts;
            self.reportFilters[report_id] = report.filter_structure;
        }

        self.availableReportIds = _.map(options.availableReports, function (r) { return r.report_id; });

        self.defaultReportTitle = function (reportId) {
            return self.reportTitles[reportId];
        };
        self.defaultReportDescription = function (reportId) {
            return self.reportDescriptions[reportId];
        };

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unsaved changes in your report list module",
            save: function () {
                // validate that all reports have valid data
                var reports = self.reports();
                for (var i = 0; i < reports.length; i++) {
                    if (!reports[i].reportId() || !reports[i].display()) {
                        alert('Reports must have all properties set!');
                    }
                }
                self.moduleName[self.lang] = self.currentModuleName();
                self.saveButton.ajax({
                    url: saveURL,
                    type: 'post',
                    dataType: 'json',
                    data: {
                        name: JSON.stringify(self.moduleName),
                        reports: JSON.stringify(_.map(self.reports(), function (r) { return r.toJSON(); }))
                    }
                });
            }
        });

        var changeSaveButton = function () {
            self.saveButton.fire('change');
        };

        self.currentModuleName.subscribe(changeSaveButton);

        function newReport(options) {
            options = options || {};
            var report = new ReportConfig(
                options.report_id,
                options.header,
                options.description,
                options.uuid,
                self.availableReportIds,
                self.reportCharts,
                options.graph_configs,
                options.filters,
                self.reportFilters,
                self.lang,
                changeSaveButton
            );
            report.display.subscribe(changeSaveButton);
            report.reportId.subscribe(changeSaveButton);
            report.reportId.subscribe(function (reportId) {
                report.display(self.defaultReportTitle(reportId));
            });
            report.reportId.subscribe(function (reportId) {
                report.description(self.defaultReportDescription(reportId));
            });

            return report;
        }
        this.addReport = function () {
            self.reports.push(newReport());
        };
        this.removeReport = function (report) {
            self.reports.remove(report);
            changeSaveButton();
        };

        // add existing reports to UI
        for (i = 0; i < currentReports.length; i += 1) {
            var report = newReport(currentReports[i]);
            self.reports.push(report);
        }
    }

    return ReportModule;
}());
<|endoftext|>