Source

infohub/Scope.js

// $Id:
// @ts-check

"use strict";

import { cacheStore } from "./cache.internal.js";
import { RestServices } from "../tools/RestServices.js";
import { ApiResponse } from "../tools/ApiResponse.js";
import { cnContext } from "./cnContext.js";
/**
 * @fileoverview
 *  - Scope domain classes and services.
 *  - Scope management for admin as well as user.
 *
 */
/**
 * 
 * @category COLNEO infohub
 * 
 * @classdesc Licence aggregate (licenced_userid, etc.).
 * @example {
 *   "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"
 * }
 */
export class Licence {
  constructor(data = /** @type {object[]} */ ([])) {
    this._data = data;
  }
  setData(data = /** @type {object[]} */ ([])) {
    this._data = data;
  }
  getData() {
    return this._data;
  }
}

/**
 * 
 * @category COLNEO infohub
 * 
 * @classdesc Scope info aggregate (name, description, etc.).
 * @property {object} _data - Scope info data
 * @example {
 *   "id": "scope1",
 *   "name": "Scope 1",
 *   "description": "Scope 1 description",
 *   "createdon": "2025-01-01",
 *   "updatedon": "2025-01-01",
 *   "createdby": "user1",
 *   "updatedby": "user1"
 * }
 */
export class ScopeInfo {
  /**
   * @since 09.2025, aab
   */
  constructor(data = /** @type {object} */ ({})) {
    this._data = data;
  }
  setData(data = /** @type {object} */ ({})) {
    this._data = data;
  }
  getData() {
    return this._data;
  }
}

/**
 * 
 * @category COLNEO infohub
 * 
 * @property {ScopeInfo} info - Scope info
 * @property {Licence[]} licences - Licences
 * @classdesc Scope aggregate (info, licences, userGroups, userRoles).
 * @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"
 * },
 *   ]
 * }
 */
export class Scope {
  /**
   * @since 09.2025, aab
   */
  constructor(info = {}, licences = []) {
    this.info = info;
    this.licences = Array.isArray(licences) ? licences : [];
  }

  /**
   * Get user IDs
   *
   * @returns {string[]}
   */
  getUserIds() {
    const users = new Set();
    for (const lic of this.licences) {
      const list = Array.isArray(lic.licenced_userid)
        ? lic.licenced_userid
        : lic.licenced_userid
        ? [lic.licenced_userid]
        : [];
      for (const u of list) if (typeof u === "string" && u) users.add(u);
    }
    return Array.from(users);
  }
}

/**
 * 
 * @category COLNEO infohub
 * 
 * @classdesc 
 *  Scope services.
 * 
 */
export class ScopeServices {
  
  /**
   * @since 1.0, 09.2025, aab
   * @param {cnContext} ctx - Context instance
   */
  constructor(ctx) {
    this._ctx = ctx;
    this.#cache = cacheStore;
  }
  #cache;

  /**
   * 
   *  Get scope from COLNEO infohub
   * 
   *  @returns {Promise<ApiResponse>} data: Scope
   * 
   *  @param {string} id - Scope ID
   * 
   */
  async getScope(id) {

    if (!id) {
      return new ApiResponse(400, null);
    }
    
    const InfoUrl     = `${this._ctx.getServiceUrl(cnContext.SERVICE.IDP)}/admin/scopes/${id}`;    
    const LicencesUrl = `${this._ctx.getServiceUrl(cnContext.SERVICE.IDP)}/admin/licences?scope=${id}`;

    try {
      const [infoRes, licRes] = await Promise.all([
        RestServices.makeApiCall(
          this._ctx.getToken(),
          RestServices.METHODS.GET,
          InfoUrl
        ),
        RestServices.makeApiCall(
          this._ctx.getToken(),
          RestServices.METHODS.GET,
          LicencesUrl
        ),
      ]);

      if (infoRes.status < 300 && licRes.status < 300) {
        const info = new ScopeInfo(infoRes.data || {});
        const licences = new Licence(licRes.data || []);
        const scope = new Scope(info.getData(), licences.getData());
        return new ApiResponse(200, scope);
      }

      return new ApiResponse(infoRes.status, null);

    } catch (_) {
      return new ApiResponse(500, null);
    }

  }

  /**
   * Get scope groups
   * @param {string} id - Scope ID
   * @param {object} query - Query object containing filter, members, etc.
   * @returns {Promise<ApiResponse>}
   */
  async getScopeGroups(id, query) {
    if (!id) {
      return new ApiResponse(400, null);
    }
    const queryString = this.#buildQueryString(query);
    const url = `${this._ctx.getServiceUrl("usr")}/${id}/usergroups`;
    try {
      const res = await RestServices.makeApiCall(
        this._ctx.getToken(),
        RestServices.METHODS.GET,
        url + queryString
      );

      if (res.status < 300) {
        return new ApiResponse(200, res.data);
      }

      return new ApiResponse(res.status, null);
    } catch (_) {
      return new ApiResponse(500, null);
    }
  }

  /**
   * Get scope roles
   * @param {string} id - Scope ID
   * @param {object} query - Query object containing filter, members, etc.
   * @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("usr")}/${id}/userroles`;
      const res = await RestServices.makeApiCall(
        this._ctx.getToken(),
        RestServices.METHODS.GET,
        url + queryString
      );

      if (res.status < 300) {
        return new ApiResponse(200, res.data);
      }

      return new ApiResponse(res.status, null);
    } 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("idp")}/admin/scopes`;
      const res = await RestServices.makeApiCall(
        this._ctx.getToken(),
        RestServices.METHODS.POST,
        url,
        ScopeInfo_new
      );

      if (res.status < 300) {
        return new ApiResponse(200, res.data);
      }

      return new ApiResponse(res.status, null);
    } 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("idp")}/admin/scopes/${id}`;
      const res = await RestServices.makeApiCall(
        this._ctx.getToken(),
        RestServices.METHODS.PUT,
        url,
        ScopeInfo_update
      );

      if (res.status < 300) {
        return new ApiResponse(200, res.data);
      }

      return new ApiResponse(res.status, null);
    } 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("idp")}/admin/scopes/${id}`;
      const res = await RestServices.makeApiCall(
        this._ctx.getToken(),
        RestServices.METHODS.DELETE,
        url
      );

      if (res.status < 300) {
        return new ApiResponse(200, res.data);
      }

      return new ApiResponse(res.status, null);
    } 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;
  }
}