Source

infohub/Project.js

// $Id$
// @ts-check

'use strict';

import { RestServices } from '../tools/RestServices.js';
import { ApiResponse } from '../tools/ApiResponse.js';
import { Context } from './Context.js';
import { cnObject } from '../gom/cnObject.js';

/**
 *
 *  @category COLNEO infohub
 *
 *  @classdesc Project on infohub
 * 
 */
export class Project extends cnObject {
  /**
   *  Since scope information is retrieved from COLNEO infohub, this constructor is not
   *  called directly. Use factory method `ProjectServices.getProjectById()` or `ProjectServices.getProjectByShortId()` instead.
   *
   * @since 09.2025, jh
   */
  constructor() {
    super();
  }
}

/**
 *
 *  @category COLNEO infohub
 *
 */
export class ProjectServices {
  /**
   * @since 1.0, 09.2025, jh
   * @param {Context} ctx - Context instance
   *
   */
  constructor(ctx) {
    this._ctx = ctx;
  }

  /**
   *
   * @param {*} project_id
   * @param {*} cb
   * @param {*} query   
   */
  async getProjectById(project_id, cb, query = null) {
    let q = '';

    if (query && Object.keys(query).length > 0) {
      const urlParams = new URLSearchParams();

      for (const key of Object.keys(query)) {
        if (key == 'filter') {
          // pass
        } else {
          urlParams.set(key, query[key]);
        }
      }

      let f = ` ($object_id ~eq~ '${project_id}' )`;
      urlParams.set('filter', f);

      q = `?${urlParams.toString()}`;
    }

    let path_endpoint = `${this._ctx.getServiceUrl(Context.SERVICE.HUB)}/${this._ctx.getScope()}/projects${q}`;

    let resp = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.GET, path_endpoint);

    // resp.dump("PROJECT");

    const arr = Array.isArray(resp.data) ? resp.data : [];
    if (resp.status < 300 && arr.length > 0) {
      let obj = new Project();
      obj.setFromJson(arr[0]);
      return cb(new ApiResponse(resp.status, obj));
    }
    return cb(resp);
  }

  /**
   *
   * @param {*} project_sid
   * @param {*} cb
   * @param {*} query   
   */
  getProjectByShortId(project_sid, cb, query = { members: ['info'] }) {
    // GET /{scope}/nodes/{node}/data
    let q = '';

    if (query && Object.keys(query).length > 0) {
      const urlParams = new URLSearchParams();
      for (const key of Object.keys(query)) {
        urlParams.set(key, query[key]);
      }
      q = `?${urlParams.toString()}`;
    }

    let path_endpoint = `${this._ctx.getServiceUrl(Context.SERVICE.HUB)}/${this._ctx.getScope()}/nodes/${project_sid}/data${q}`;

    // RestServices.makeApiCall returns a Promise
    RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.GET, path_endpoint)
      .then((resp) => {
        // resp.dump("PROJECT");

        if (resp.status < 300) {
          let obj = new Project();
          obj.setFromJson(resp.data);
          // console.log("PROJECT___", JSON.stringify(obj.getAsJson() ) )
          return cb(new ApiResponse(resp.status, obj));
        } else {
          return cb(resp);
        }
      })
      .catch((error) => {
        console.error('Error in getProjectByShortId:', error);
        return cb(new ApiResponse(500, null, error.message || 'Unknown error'));
      });
  }

  /**
   *  Get list of projects.
   * 
   *  @param {*} cb    callback
   *  @param {*} query optional query filter
   */
  async getProjectList( cb , query = null) {
    
    // GET /{scope}/nodes/{node}/data

    let q = '';

    if (query && Object.keys(query).length > 0) {
      const urlParams = new URLSearchParams();
      for (const key of Object.keys(query)) {
        urlParams.set(key, query[key]);
      }
      q = `?${urlParams.toString()}`;
    }

    let path_endpoint = `${this._ctx.getServiceUrl(Context.SERVICE.HUB)}/${this._ctx.getScope()}/projects${q}`;

    let resp = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.GET, path_endpoint);
    // resp.dump("PROJECT LIST");

    return cb(resp);

  }

  /*
   * Get an array of user IDs (emails) for all users in the given project.
   *
   * This method reuses 'getUsersOfProject' to fetch all users along with their groups,
   * then extracts only the user identifiers (email addresses) into a simple array.
   *
   * @param {string} project_sid - The project short ID
   *
   * @returns {Promise<ApiResponse>} ApiResponse with:
   * - `status` 200 and `data` as an array of user IDs (strings), e.g.:   
   [
    "amr.abdou@colneo.email",
    "jane.doe@colneo.email"
   ]   
   * - or an error status (e.g., 500) with an error message if the request fails.
   *
   * @example
   * Returns:   
   [
    "amr.abdou@colneo.email",
    "jane.doe@colneo.email"
   ]
   *
   */
  async getUsersOfProjectAsArray(project_sid) {

    try {
      // Reuse existing function
      let resp = await this.getUsersOfProject(project_sid);

      if (resp.getStatus() >= 300) {
        return resp; // forward error response
      }

      // Extract user ids (emails)
      const users = resp.getData().map(u => u.user);

      return new ApiResponse(200, users);

    } catch (error) {
      return new ApiResponse(500, null, error.message);
    }

  }

  /**
   * @description
      Get users in all groups of a given project.
      Returns a list of users with their email and the groups they belong to.
 *
 * @param {string} project_sid - The project short ID.
 * @param {boolean} membersOnly - Whether to include only the members the project or all contacts, 
 * notice: members of the projet are temporarly the users of the project admin group.
 *
 * @returns {Promise<ApiResponse>} ApiResponse with:
 *   - `status` 200 and `data` as an array of user objects, each containing:
 *     - `user` {string} - The user's email address.
 *     - `usergroups` {Array<Object>} - Array of group info objects, each with:
 *       - `info` {Object} - Group info object, with:
 *       - `id` {string} - Group UUID
 *       - `name` {string} - Group name
 *       - `description` {string} - Group description
 *       - `projcatid` {string} - Project catalog ID
 *       - `created` {string} - Creation timestamp
 *       - `createdby` {string} - Creator email
 *       - `updated` {string} - Update timestamp
 *       - `updatedby` {string} - Updater email
 *       - `permissions` {Object} - Group permissions object:
 *         - `groups` {string} - Group permissions, e.g.:
 *{
 *   "groups": "delete"
* }
 * @example
 * [
 *   {
 *     user: "amr.abdou@colneo.email",
 *     usergroups: [{
 *       info: { id: "...", name: "Projektleitung", description: "..." },
 *       permissions: { ... },
 *     },
 *     {
 *       info: { id: "...", name: "ddd", description: "..." }
 *       permissions: { ... },
 *     }
 * ]
 *
 * @since 1.0
 */
async getUsersOfProject(project_sid, membersOnly = false) {
  const users = {};
  let filteredUsers = {};

  const q = `?members=users,permissions`;
  const path_endpoint = `${this._ctx.getServiceUrl(Context.SERVICE.USR)}/${this._ctx.getScope()}/projects/${project_sid}/usergroups${q}`;

  try {
    const resp = await RestServices.makeApiCall(this._ctx.getToken(), RestServices.METHODS.GET, path_endpoint);

    if (resp.getStatus() < 300) {
      resp.getData().forEach((usergroup) => {
        if (usergroup.users && usergroup.users.length > 0) {
          usergroup.users.forEach((user) => {
            if (!users[user]) users[user] = [];
            const { users: _, ...groupWithoutUsers } = usergroup;
            users[user].push(groupWithoutUsers);
          });
        }
      });
    }

    // If the API call failed, forward the error
    if (resp.getStatus() >= 300) {
      return new ApiResponse(resp.getStatus(), null, resp.getMessage());
    }

    if (membersOnly) {
      // Keep users who have at least one group with permissions.groups === 'delete'
      // but include all their groups (not only the 'delete' ones)
      Object.keys(users).forEach((user) => {
        const hasDeletePermission = users[user].some((usergroup) => usergroup.permissions?.groups === 'delete');
        if (hasDeletePermission) {
          filteredUsers[user] = users[user];
        }
      });
    } else {
      filteredUsers = users;
    }

  } catch (error) {
    return new ApiResponse(500, null, error.message);
  }

  return new ApiResponse(
    200,
    Object.keys(filteredUsers).map((user) => ({
      user,
      usergroups: filteredUsers[user],
    }))
  );
}

}