Source

infohub/Node.js

// $Id$
// @ts-check

'use strict';

import { RestResponse, 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 
 *      Node (Version of Object) on COLNEO infohub
 *
 */
export class Node extends cnObject {

    static className = "Node";

    /*     
     *
     * @since 09.2025, jh
     */
    constructor() {
        super();
    }

}

/**
 *
 *  @category COLNEO infohub
 *
 *  @classdesc
 *      Node Services to get/write objects as tree, etc.
 *      
 */
export class NodeServices {


    #_ctx = null


    /**
     * @since 1.0, 09.2025, jh
     * @param {Context} ctx - Context instance
     *
     */
    constructor(ctx) {
        this.#_ctx = ctx;
    }

    /**
     *
     * @param {string} node_sid
     * @param {object} params = {
     *      'depth'
     *      'members'
     *      'properties'
     *      'geometry'
     *      'filter'
     *      'nodes'
     *      'relations'
     *      'relations_right'
     *      'groupby'
     *      'sortby'
     *      'limit'
     *      'offset'
     * } each parameter has to be encoded as URI component
     * 
     * @example
     * {
      *      'members': ['info', 'properties', 'relations', 'relations_right'],
      *      'filter': '$object_type ~eq~ \'act_issue\'',
      *      'depth': 0,
      *      'sortby': 'issue:wbs##xs:string',
      *      'relations_right': JSON.stringify({
      *          'members': ['info', 'properties'],
      *          'filter': '$name ~eq~ \'cnSUCCESSORS\'',
      *      }),
      * }
     * }
     * @param {function} cb
     }}
     */
    async getTree(node_sid, params, cb) {

        let q = ''
        let c = '?'
        
        for (const p in params) {
            q += `${c}${p}=${encodeURIComponent(params[p])}`
            c = '&'
        }

        let scope = this.#_ctx.getScope()

        let path_endpoint = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${scope}/nodes/${node_sid}/tree${q}`

        console.log(`call [${path_endpoint}]`)

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

        // console.log( "RESPONSE:" )
        // console.log( JSON.stringify(resp.getData()) )

        return cb(resp)

    }

    /**
     * 
     * Update node tree.
     * Keep existing nodes in parentnode_sid
     * 
     * PUT /{scope}/nodes/{node}/tree
     * 
     * @param {*} parentnode_sid 
     * @param {*} node_tree 
     * @param {*} cb 
     */
    async updateTree(parentnode_sid, node_tree, cb) {

        let scope = this.#_ctx.getScope()

        let path_endpoint = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${scope}/nodes/${parentnode_sid}/tree`

        console.log(`call [${path_endpoint}]`)

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        let resp = await RestServices.makeApiCall(this.#_ctx.getToken(), RestServices.METHODS.PUT, path_endpoint, node_tree)
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        return cb(resp)

    }

    /**
     * 
     * Update/create node tree.
     * Replace existing nodes in 'parentnode_sid', if existing nodes are deleted if not in 'node_tree'
     * 
     * POST /{scope}/nodes/{node}/tree
     * 
     * @param {*} parentnode_sid 
     * @param {*} node_tree 
     * @param {*} cb 
     */
    async saveTree(parentnode_sid, node_tree, cb) {

        let scope = this.#_ctx.getScope()

        let path_endpoint = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${scope}/nodes/${parentnode_sid}/tree`

        console.log(`call [${path_endpoint}]`)

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        let resp = await RestServices.makeApiCall(this.#_ctx.getToken(), RestServices.METHODS.POST, path_endpoint, node_tree)
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        return cb(resp)

    }

    /**
   * 
   * @param {*}         node_sid 
   * @param {object}    query =
   * {
   *        "as_blob" : true    download as file, false if the document should be loaded as json   
   * }
   * @param {*}         cb data = {
   *        "doc" :     json or blob   
   * }
   */
    async getDocument(node_sid, query, cb) {

        let xhr = new XMLHttpRequest()

        xhr.addEventListener("load", function (e) {

            if (xhr.status < 300) {

                let ret = null

                let fn = xhr.getResponseHeader('x-filename')

                if (query.as_blob == true) {

                    // let fn = xhr.getResponseHeader('x-filename')

                    let r = {
                        'doc': xhr.response,
                        "filename": fn
                    }

                    return cb(new ApiResponse(200, r))

                }

                // console.log( 'JSON DOC : ')
                // console.log( xhr.responseText)

                try {
                    ret = JSON.parse(xhr.responseText)
                } catch (e) {
                    console.log("ERROR")
                    return cb(new ApiResponse(500, null))
                }

                if (ret == '') {
                    console.log("DOC IS EMPTY")
                    return cb(new ApiResponse(400, null, "DOC IS EMPTY"))
                }

                let r = {
                    'doc': ret,
                    "filename": fn
                }

                return cb(new ApiResponse(200, r))

            } else {
                console.log("status : " + xhr.status)
                return cb(new ApiResponse(xhr.status, null))
            }

        }, false)

        xhr.addEventListener("error", function (e) {
            console.log(JSON.stringify(e))
            return cb(new ApiResponse(500, null))
        })

        // #########################################################################################################
        const target = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/nodes/${node_sid}`
        // #########################################################################################################

        // console.log("GET : " + target)

        xhr.open("GET", target)
        xhr.setRequestHeader('Authorization', 'Bearer ' + this.#_ctx.getToken())
        xhr.setRequestHeader('Accept', 'application/object')
        if (query.as_blob == true) xhr.responseType = "blob"
        xhr.send()

    }

    /**
   *
   * Get path of node hierarchy from root to node.
   *
   * @param {*} node_sid
   * @param {*} query
   * @param {*} cb
   * @returns {Promise<ApiResponse>}
   */
    async getNodePath(node_sid, query, cb) {

        let q = ''
        let c = '?'

        if (query['members']) {
            let plist = query['members'].join(',')
            q += c + 'members=' + encodeURIComponent(plist)
            c = '&'
        }

        const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/nodes/${node_sid}/path/data${q}`;

        console.log(`GET : ${url}`)

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /* RestResponse */ const resp = await RestServices.makeApiCall(this.#_ctx.getToken(), RestServices.METHODS.GET, url)
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        if (resp.status < 300) {

            let array = []

            for (const obj_json of resp.data) {

                let obj = new cnObject()
                obj.setFromJson(obj_json)

                array.push(obj)

            }

            return cb(new ApiResponse(resp.status, array))

        } else {

            return cb(resp)

        }

    }

    /**
     * 
     * @param {*} node_sid 
     * @param {*} query 
     * @param {*} cb 
     * @returns {Promise<RestResponse>}
     */
    async getNodeData(node_sid, query, cb ) {

        let q = ''
        let c = '?'

        if (query.members) {
            let mstr = query.members.join(',')
            q += `${c}members=` + encodeURIComponent(mstr)
            c = '&'
        }

        if (query.properties) {
            let pstr = query.properties.join(',')
            q += `${c}properties=` + encodeURIComponent(pstr)
            c = '&'
        }

        const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/nodes/${node_sid}/data${q}`;

        console.log(`target: [${url}]`)
      
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////      
        /* RestResponse */ const resp = await RestServices.makeApiCall(this.#_ctx.getToken(), RestServices.METHODS.GET, url)
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        return cb(resp)
    
    }


}