Source

infohub/Scope.js

// $Id$
// @ts-check

'use strict';

import { RestServices } from '../tools/RestServices.js';
import { ApiResponse } from '../tools/ApiResponse.js';
import { Context } from './Context.js';
import { Licence, LicenceServices } from './Licence.js';


/**
 *
 * @category COLNEO infohub
 *
 * @classdesc
 *  COLNEO infohub Scope. <br>
 *  Each scope defines the boundary at which resources are managed in a database and isolated,
 *  typically tied to a tenant that represents an organization or licensee.
 *
 * @property {object} info - Scope info
 * @property {Licence[]} licences - Licences
 *
 *
 * @example {
 *   "info": {
 *     "id": "scope1",
 *     "name": "Scope 1",
 *     "description": "Scope 1 description",
 *     "createdon": "2025-01-01",
 *     "updatedon": "2025-01-01",
 *     "createdby": "user1",
 *     "updatedby": "user1"
 *   },
 *   "licences": [
 *           {
 *   "contract": "",
 *   "created": "2025-01-01T00:00:00Z",
 *   "createdby": "user@example.com",
 *   "expirated": null,
 *   "id": "00000000-0000-0000-0000-000000000000",
 *   "licenced_userid": ["user@example.com"],
 *   "licencee": {
 *     "address": "{\"street\":\"...\", \"city\":\"...\", \"zip\":\"...\"}",
 *     "email": "info@example.com",
 *     "name": "ACME"
 *   },
 *   "maxpermissions": "edit",
 *   "maxseats": 0,
 *   "module": "objecttypes",
 *   "scope": "cn_dv",
 *   "startdate": "2025-01-01T00:00:00Z",
 *   "updated": "2025-01-01T00:00:00Z",
 *   "updatedby": "user@example.com"
 * },
 *   ]
 * }
 *
 * @since 09.2025, aab, jh
 *
 */
export class Scope {

  /**
   *  Since scope information is retrieved from COLNEO infohub, this constructor is not
   *  called directly. Use factory method `ScopeServicves.getScope()` instead.
   *
   * @since 09.2025, aab
   */
  constructor(info = {}) {
    Object.assign(this, info);
  }

  /**
   * 
   * @param {*} data 
   * @returns {void}
   */
  setData(data = /** @type {object} */ ({})) {
    Object.assign(this, data);
  }
  
  /**
   * 
   * @returns {object} Scope data without function properties
   */
  getData() {
    return Object.fromEntries(Object.entries(this).filter(([_, v]) => typeof v !== 'function'));
  }
}

/**
 *
 * @category COLNEO infohub
 *
 * @classdesc
 *  Functions to get scope information from infohub.
 *
 */
export class ScopeServices {
  /**
   * @since 1.0, 09.2025, aab
   * @param {Context} ctx - Context instance
   *
   */
  constructor(ctx) {
    this._ctx = ctx;
  }

  /**
   *  Get scope from COLNEO infohub
   *  @returns {Promise<ApiResponse>} data: Scope
   *  @param {string} scopeid - Scope ID
   */
  async getScopeById(scopeid) {
    if (!scopeid) {
      return new ApiResponse(400, null);
    }
    try {
      const url = `${this._ctx.getServiceUrl(Context.SERVICE.IDP)}/admin/scopes/${scopeid}`;
      const res = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.GET, url);
      const scope = new Scope(res.data || {});
      return res.status < 300 ? new ApiResponse(200, scope) : new ApiResponse(res.status, null, res.message);
    } catch (error) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Get scopes of a user by user ID
   * @since 09.2025, aab
   * @param {string} userid - User ID
   * @returns {Promise<ApiResponse>}
   */
  async getScopesOfUser(userid) {
    if (!userid) {
      return new ApiResponse(400, null);
    }
    try {
      const url = `${this._ctx.getServiceUrl(Context.SERVICE.IDP)}/users/${userid}/scopelist`;
      const res = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.GET, url);
      const arr = Array.isArray(res.data) ? /** @type {any[]} */ (res.data) : [];
      const list = arr.map((d) => new Scope(d));
      return res.status < 300 ? new ApiResponse(200, list) : new ApiResponse(res.status, null, res.message);
    } catch (error) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Get licences within a scope
   * @param {string} scopeid - Scope ID
   * @returns {Promise<ApiResponse>}
   */
  async getLicencesOfScope(scopeid) {
    if (!scopeid) {
      return new ApiResponse(400, null);
    }
    try {
      const licenceServices = new LicenceServices(this._ctx);
      const licences = await licenceServices.getLicences({ scope: scopeid });
      const list = Array.isArray(licences.data) ? /** @type {Licence[]} */ (licences.data) : [];
      const scope = new Licence(list);
      return licences.status < 300 ? new ApiResponse(200, scope) : new ApiResponse(licences.status, null, licences.message);
    } catch (error) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Get scope groups
   * @param {string} id - Scope ID
   * @param {object} query - Query object containing filter, members, etc.
   * @param {string} [query.filter] - Filter
   * @param {string} [query.members] - Members
   * @returns {Promise<ApiResponse>}
   */
  async getScopeGroups(id, query) {
    if (!id) {
      return new ApiResponse(400, null);
    }
    const queryString = this.#buildQueryString(query);
    const url = `${this._ctx.getServiceUrl(Context.SERVICE.USR)}/${id}/usergroups`;
    try {
      const res = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.GET, url + queryString);
      return res.status < 300 ? new ApiResponse(200, res.data) : new ApiResponse(res.status, null, res.message);
    } catch (_) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Get scope roles
   * @param {string} id - Scope ID
   * @param {object} query - Query object containing filter, members, etc.
   * @param {string} [query.filter] - Filter
   * @param {string} [query.members] - Members
   * @returns {Promise<ApiResponse>}
   */
  async getScopeRoles(id, query) {
    if (!id) {
      return new ApiResponse(400, null);
    }
    const queryString = this.#buildQueryString(query);
    try {
      const url = `${this._ctx.getServiceUrl(Context.SERVICE.USR)}/${id}/userroles`;
      const res = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.GET, url + queryString);

      return res.status < 300 ? new ApiResponse(200, res.data) : new ApiResponse(res.status, null, res.message);
    } catch (_) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Create scope
   * @param {object} ScopeInfo_new - Scope information
   * @param {string} [ScopeInfo_new.name] - Scope name
   * @param {string} [ScopeInfo_new.description] - Scope description
   * @returns {Promise<ApiResponse>}
   */
  async createScope(ScopeInfo_new) {
    if (!ScopeInfo_new?.name) {
      return new ApiResponse(400, null);
    }
    try {
      const url = `${this._ctx.getServiceUrl(Context.SERVICE.IDP)}/admin/scopes`;
      const res = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.POST, url, ScopeInfo_new);

      return res.status < 300 ? new ApiResponse(200, res.data) : new ApiResponse(res.status, null, res.message);
    } catch (_) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Update scope
   * @param {string} id - Scope ID
   * @param {object} ScopeInfo_update - Scope information
   * @param {string} [ScopeInfo_update.name] - Scope name
   * @param {string} [ScopeInfo_update.description] - Scope description
   * @returns {Promise<ApiResponse>}
   */
  async updateScope(id, ScopeInfo_update) {
    if (!id || !ScopeInfo_update?.name) {
      return new ApiResponse(400, null);
    }
    try {
      const url = `${this._ctx.getServiceUrl(Context.SERVICE.IDP)}/admin/scopes/${id}`;
      const res = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.PUT, url, ScopeInfo_update);

      return res.status < 300 ? new ApiResponse(200, res.data) : new ApiResponse(res.status, null, res.message);
    } catch (_) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Delete scope
   * @param {string} id - Scope ID
   * @returns {Promise<ApiResponse>}
   */
  async deleteScope(id) {
    if (!id) {
      return new ApiResponse(400, null);
    }
    try {
      const url = `${this._ctx.getServiceUrl(Context.SERVICE.IDP)}/admin/scopes/${id}`;
      const res = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.DELETE, url);

      return res.status < 300 ? new ApiResponse(200, res.data) : new ApiResponse(res.status, null, res.message);
    } catch (_) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Query string builder
   * @param {object} [query] - Query object containing filter, members, etc.
   * @param {string} defaultMember - Default member value if not specified
   * @returns {string} - Properly formatted query string
   */
  #buildQueryString(query = /** @type {object} */ ({}), defaultMember = 'info') {
    let queryString = '';
    let c = '?';
    if (query['filter']) {
      queryString += c + 'filter=' + encodeURIComponent(query['filter']);
      c = '&';
    }
    if (query['members']) {
      let membersString = query['members'];
      if (defaultMember && !membersString.includes(defaultMember)) {
        membersString = membersString + ',' + defaultMember;
      }
      queryString += c + 'members=' + encodeURIComponent(membersString);
      c = '&';
    } else if (defaultMember) {
      queryString += c + 'members=' + encodeURIComponent(defaultMember);
      c = '&';
    }
    return queryString;
  }
}