components/sitemaps/backend.js

import logger from './../../logger.js';
import fetch from 'node-fetch';
import { getHeaders, findKeyInObj } from '../../utils.js';
import { itemsOfSitemapDb, sitemapsListDb, sitemapsForUserDb } from '../../db.js';
import { CACHE_TIME, CACHE_TIME_ACL, ADMIN_OU, EVERYONE_OU, ORG_SEPARATOR } from '../../server.js';

/**
 * Sitemaps backend namespace. Providing access to the openHAB backend.
 *
 * @namespace sitemapsBackend
 */

/**
 * Get all available Sitemaps.
 * Utilising LokiJS to cache the Sitemap list for better performance.
 *
 * @memberof sitemapsBackend
 * @param {String} HOST hostname of openHAB server
 * @param {*} expressReq request object from expressjs
 * @returns {Array<Object>} array of Sitemaps
 */
export const getAllSitemaps = async function (HOST, expressReq) {
  const now = Date.now();
  const sitemapsList = sitemapsListDb.findOne({ name: 'list' });
  if (sitemapsList) {
    if (now < sitemapsList.lastupdate + CACHE_TIME) {
      // Currently stored version not older than CACHE_TIME.
      logger.debug('getAllSitemaps(): Found in database and not older than CACHE_TIME.');
      return sitemapsList.json;
    }
    sitemapsListDb.findAndRemove({ name: 'list' });
  }

  const headers = await getHeaders(expressReq);
  try {
    const response = await fetch(HOST + '/rest/sitemaps', { headers: headers });
    const json = await response.json();
    sitemapsListDb.insert({ name: 'list', lastupdate: now, json: json });
    const status = response.status;
    logger.debug(`getAllSitemaps(): Successfully requested backend ${HOST + '/rest/sitemaps'}, HTTP response code ${status}`);
    return json;
  } catch (err) {
    const error = new Error(`getAllSitemaps(): An error occurred while getting all Sitemaps from ${HOST + '/rest/sitemaps'}: ${err}`);
    logger.error(error);
    error();
  }
};

/**
 * Get a single Sitemap by name.
 *
 * @memberof sitemapsBackend
 * @param {String} HOST hostname of openHAB server
 * @param {*} expressReq request object from expressjs
 * @param {String} sitemapname Sitemap name
 * @returns {Object} Sitemap
 */
export const getSitemap = async function (HOST, expressReq, sitemapname) {
  const headers = await getHeaders(expressReq);
  try {
    const response = await fetch(HOST + '/rest/sitemaps/' + sitemapname + '?jsoncallback=callback&includeHidden=true', { headers: headers });
    const json = await response.json();
    const status = response.status;
    logger.debug(`getSitemap(): Successfully requested backend ${HOST + '/rest/sitemaps/' + sitemapname + '?jsoncallback=callback&includeHidden=true, HTTP response code ${status}'}`);
    return {
      json: json,
      status: status
    };
  } catch (err) {
    const error = new Error(`getSitemap(): An error occurred when requesting backend ${HOST + '/rest/sitemaps/' + sitemapname + '?jsoncallback=callback&includeHidden=true'}: ${err}`);
    logger.error(error);
    error();
  }
};

/**
 * Get a single Page of Sitemap by name and pageid.
 *
 * @memberof sitemapsBackend
 * @param {String} HOST hostname of openHAB server
 * @param {*} expressReq request object from expressjs
 * @param {String} sitemapname Sitemap name
 * @param {String} pageid Page id 
 * @returns {Object} Sitemap
 */
export const getSitemapPage = async function (HOST, expressReq, sitemapname, pageid) {
  const headers = await getHeaders(expressReq);

  //process only query parameters defined in API
  let query = '';
  if (expressReq.query.subscriptionid) query = 'subscriptionid=' + expressReq.query.subscriptionid;
  query = (query) ? '?' + query + '&includeHidden=true' : '?includeHidden=true';
  
  try {
    const response = await fetch(HOST + '/rest/sitemaps/' + sitemapname + '/' + pageid + query, { headers: headers });
    const json = await response.json();
    const status = response.status;
    logger.debug(`getSitemapPage(): Successfully requested backend ${HOST + '/rest/sitemaps/' + sitemapname + '/' + pageid + query}, HTTP response code ${status}'}`);
    return {
      json: json,
      status: status
    };
  } catch (err) {
    const error = new Error(`getSitemapPage(): An error occurred when requesting backend ${HOST + '/rest/sitemaps/' + sitemapname + '/' + pageid + query}: ${err}`);
    logger.error(error);
    error();
  }
};

/**
 * Get names of all Items in Sitemap.
 * Utilising LokiJS to cache the Items for better performance.
 *
 * @memberof sitemapsBackend
 * @param {String} HOST hostname of openHAB server
 * @param {*} expressReq request object from expressjs
 * @param {*} sitemapname Sitemap name
 * @returns {Array<String>} names of all Items in Sitemap
 */
export const getItemsOfSitemap = async function (HOST, expressReq, sitemapname) {
  const now = Date.now();
  const itemsDb = itemsOfSitemapDb.findOne({ name: sitemapname });
  if (itemsDb) {
    if (now < itemsDb.lastupdate + CACHE_TIME) {
      // Currently stored version not older than CACHE_TIME.
      logger.debug(`getItemsOfSitemap(): Items of Sitemap ${sitemapname} found in database and not older than CACHE_TIME.`);
      return itemsDb.items;
    }
    itemsOfSitemapDb.findAndRemove({ name: sitemapname });
  }

  try {
    const sitemap = await getSitemap(HOST, expressReq, sitemapname);
    const items = findKeyInObj(sitemap.homepage.widgets, 'item').map(item => item.name);
    itemsOfSitemapDb.insert({ name: sitemapname, lastupdate: now, items: items });
    logger.debug({ sitemap: sitemapname }, `getItemOfSitemap(): Items of Sitemap ${sitemapname} fetched from backend`);
    return items;
  } catch (err) {
    throw Error(err);
  }
};

/**
 * Gets sitemapnames's of all allowed Sitemaps for a user.
 * Utilising LokiJS to cache filtered Sitemaps list for better performance.
 *
 * @memberof sitemapsBackend
 * @param {String} HOST hostname of openHAB server
 * @param {*} expressReq request object from expressjs
 * @param {String} user username
 * @param {String|Array<String>} org organizations the user is member of
 * @returns {Array<String>} sitemapname's of sitemaps allowed for a user
 */
export const getSitemapsForUser = async function (HOST, expressReq, user, org) {
  if (!user) throw Error('Parameter user is required!');
  if (!org) org = [];
  if (typeof org === 'string') org = org.toString().split('.');

  const now = Date.now();
  const storedSitemaps = sitemapsForUserDb.findOne({ name: user });
  if (storedSitemaps) {
    if (now < storedSitemaps.lastupdate + CACHE_TIME_ACL) {
      // Currently stored version not older than CACHE_TIME_ACL.
      logger.debug('getSitemapsForUser(): Found in database and not older than CACHE_TIME_ACL.');
      return storedSitemaps.sitemaps;
    }
    sitemapsForUserDb.findAndRemove({ name: user });
  }

  const headers = await getHeaders(expressReq);
  try {
    const response = await fetch(HOST + '/rest/sitemaps', { headers: headers });
    const allSitemaps = await response.json();
    let filteredSitemaps = [];
    for (const i in allSitemaps) {
        // For Sitemaps created in MainUI strip "uicomponents_" from sitemapname
        // If Sitemap name includes ORG_SEPARATOR, return string before ORG_SEPARATOR, else return Sitemap name.
        let nameOfSitemap = '';
        let orgOfSitemap = ''
        if (allSitemaps[i].name.substring(0,13) == "uicomponents_") {
            nameOfSitemap = allSitemaps[i].name.substring(13);
            orgOfSitemap = (nameOfSitemap.includes(ORG_SEPARATOR)) ? nameOfSitemap.split(ORG_SEPARATOR)[0] : nameOfSitemap;
        } else {
            nameOfSitemap = allSitemaps[i].name;
            orgOfSitemap = (nameOfSitemap.includes(ORG_SEPARATOR)) ? nameOfSitemap.split(ORG_SEPARATOR)[0] : nameOfSitemap;
        }
        logger.trace(`getSitemapsForUser(): Organization of Sitemap ${allSitemaps[i].name} is ${orgOfSitemap}`);
        if (nameOfSitemap === user || 
            org.includes(orgOfSitemap) ||
            org.includes(ADMIN_OU) ||
			orgOfSitemap === EVERYONE_OU) {
			//Access allow when sitename is user name, user org or EVERYONE_OU, Member of ADMIN_OU has full access
            if (!filteredSitemaps.includes(allSitemaps[i].name)) filteredSitemaps.push(allSitemaps[i].name);
        }
	}
    sitemapsForUserDb.insert({ name: user, lastupdate: now, sitemaps: filteredSitemaps });
    const status = response.status;
    logger.debug(`getSitemapsForUser(): Successfully requested backend ${HOST + '/rest/sitemaps'}, HTTP response code ${status}`);
    return filteredSitemaps;
  } catch (err) {
    const error = new Error(`getSitemapsForUser(): An error occurred while getting all Sitemaps from ${HOST + '/rest/sitemaps'}: ${err}`);
    logger.error(error);
    error();
  }
};