// $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],
}))
);
}
}
Source