Source

gom/cnRelation.js

//
// $Id$
//

import { RestServices, RestResponse } from "../tools/RestServices.js"
import { Context } from "./../infohub/Context.js"
import { ApiResponse } from "../tools/ApiResponse.js"

/**
 *  
 *  @category COLNEO gom <br>
 *    <font size="6pt">Generic Object Model</font>
 * 
 *  @classdesc Base Class for nodes/objects on COLNEO infohub. <br>
    Uses the infohub GOM (Generic Object Model)
      
 */
export class cnRelation {

  #_data = null

  constructor() {
  }

  /**
   *  Global unique ID
   */
  get id() { return this.getId(); }
  
  /**
   *  Get ID of Relation (shortid)
   * 
   *  @returns ShortID
   * 
   */
  getId() {
    return this.#_data?.shortid
  }

  getName() {
    return this.#_data?.name
  }

  dump( prefix = '' ) {
    
    if( prefix.length > 0 ) {
      prefix = prefix + ' '
    }

    console.log( `${prefix}type: [cnRelation]`)

    if (this.#_data) {
      console.log(`${prefix}relation:`)
      console.log(JSON.stringify(this.#_data, null, 2))
    } else {
      console.log('-empty-')
    }

  }

  /**
   * 
   * @param {*} obj_as_json 
   * @returns {void}
   */
  setFromJson(obj_as_json) {
    this.#_data = obj_as_json
  }
   
  /**
   * 
   * @returns {*} The relation data as JSON
   */
  getAsJson() {  
    return this.#_data  
  }

}

/**
 *  
 *  @category COLNEO gom <br>
 *    <font size="6pt">Generic Object Model</font>
 * 
 *  @classdesc Relation Services <br>
      Uses the infohub GOM (Generic Object Model)
      
 */
export class cnRelationServices {

  #_ctx = null 

   /**
   *   
   * @param {Context} ctx Infohub Context
   * 
   * @since 08.2025, jh
   * 
   */
  constructor( ctx ) {      
    this.#_ctx = ctx  
  }

  /**
   * 
   * @param {*} left_nodeid 
   * @param {*} relation_data 
   * @param {*} cb 
   * @returns {Promise<ApiResponse>} Returns the created relation
   */
  async createRelation( left_nodeid , relation_data, cb) {

    // console.log( '### <createRelation()> ' )
    
    // 
    //  relation_data
    //  {
    //   "name": "string",
    //   "description": "string",
    //   "weight": 0,
    //   "right_objects": [
    //     "string"
    //   ]
    // }

    // /{scope}/{node}/relations

    const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/${left_nodeid}/relations`;

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

    // resp.dump( "NEW RELATION" )

    if (resp.status < 300) {

      let obj = new cnRelation()
      obj.setFromJson(resp.data)

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

    } else {

      return cb(resp)

    }

  }
  
  /**
   * Get relation by name or create it if it does not exist by using relation_data
   * 
   * @param {*} left_nodeid 
   * @param {*} relation_name 
   * @param {*} cb 
   */
  async getRelation( left_nodeid, relation_name, cb, relation_data = null) {

    console.log( '### <getRelation()> ')
    try {

      let q = ''
      let c = ''
      
      let f = `$relation$name ~eq~ '${relation_name}' `
      q += c + `filter=${f}`

      //
      // RESOLVE MEMBERS

      const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/${left_nodeid}/relations?${q}`;

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

      if (resp.getStatus() < 300) {

        let data = resp.getData()

        console.log(`found ${data.length} relations `)

        if (data.length == 1) {

          console.log("DATA:")
          console.log(JSON.stringify(data[0]))

          let obj = new cnRelation()
          obj.setFromJson(data[0])

          // obj.dump("GOTRELATION")

          return cb(new ApiResponse(200, obj))

        } else if (data.length == 0) {

          if ( relation_data ) {

            // if relation does not exist and relation_data is given, create relation ...

            console.log(`create relation ${left_nodeid} / ${relation_name} ... `)

            this.createRelation( left_nodeid , relation_data, (ret) => {
              return cb(ret)
            })

          } else {            
            return cb(new ApiResponse(404, null, `relation not found with given Name = [${relation_name}].`))
          }

        } else {

          return cb(new ApiResponse(406, null, `${data.length} relations found with given Name = [${relation_name}].`))

        }

      } else {

        if (relation_data) {

          // if object does not exist and object_data is given, create object as child of context_sid ...

          console.log(`create relation ${left_nodeid} / ${relation_name} ... `)

          this.createRelation(left_nodeid, relation_data, (ret) => {
            return cb(ret)
          })

        } else {
          return cb(resp)
        }

      }

    } catch (error) {
      // console.error("getObject failed:", error);
      return cb(new ApiResponse(400, null, "Unknown error"))
    }

  }

  async saveRelation( cnRel, left_nodeid , cb ) {
    
    // create object/node ...

    let object_data = cnRel.getAsJson()

    const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/${left_nodeid}/relations`;
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////    
    /* RestResponse */ const resp = await RestServices.makeApiCall(this.#_ctx.getToken(), RestServices.METHODS.PUT, url, object_data)
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // resp.dump("RETURN UPDATE OBJECT")

    if (resp.status < 300) {

      let obj = new cnRelation()
      obj.setFromJson(resp.data)

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

    } else {

      return cb(resp)

    }

  }

  async patchRelation( rel_data , relation_id , cb ) {
        
    const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/relations/${relation_id}`;
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////    
    /* RestResponse */ const resp = await RestServices.makeApiCall(this.#_ctx.getToken(), RestServices.METHODS.PATCH, url, rel_data)
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    resp.dump("RETURN UPDATE RELATION")

    if (resp.status < 300) {

      let obj = new cnRelation()
      obj.setFromJson(resp.data)

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

    } else {

      return cb(resp)

    }

  }

  /**
   * 
   * @param {*} relation_sid 
   * @param {*} cb 
   * @returns {Promise<RestResponse>} Returns the response from the delete operation
   */
  async deleteRelation(relation_sid, cb) {

    console.log( `### <deleteRelation( ${relation_sid} )> `)

    // /{scope}/relations/{relation}
    
    const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/relations/${relation_sid}`;

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

    return cb(resp)

  }  

  /**
   * 
   * @param {*} left_nodeid 
   * @param {*} query 
   * @param {*} cb 
   * @returns {Promise<ApiResponse>} {
   *    status  :
   *    data    : array of relations
   */
  async getRelationListByNode( left_nodeid , query, cb) {

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

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

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

    // console.log( 'REL REL ' )
    // console.log( JSON.stringify(resp.data) )

    if (resp.status < 300) {

      // resp.dump( "REL LIST" )

      let array = []

      for (const obj_json of resp.data) {

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

        array.push(obj)

      }

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

    } else {

      return cb(resp)

    }

  }

  /**
   * 
   * @param {*} query = {
   *    'members'   : ' ... '
   *    'filter'    : ' ... '
   * }
   * 
   * @param {*} cb 
   * @returns {Promise<ApiResponse>} Returns array of relations
   */
  async getRelationList( query, cb) {

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

    if (query['filter']) {
      // q += c + 'filter=' + encodeURIComponent( query['filter'] )
      const filter = query['filter']
      q += c + 'filter=' + encodeURIComponent(filter)
      c = '&'
    }

    const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/relations${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 cnRelation()
        obj.setFromJson(obj_json)
        array.push(obj)
      }

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

    } else {

      return cb(resp)

    }

  }

  async addToRelation( left_nodeid , relation_name , sid_list , cb )
  {
    
    console.log( `### <addToRelation( ${left_nodeid} , ${relation_name} )> ` )
    // console.log( "add : " )
    // console.log( sid_list )

    let relation_data = {      
        "name": relation_name,
        "description": "Created by infohub SDK",
        "weight": 1,
    }

    this.getRelation( left_nodeid , relation_name , async (ret) => {

      // ret.data.dump("RELATION")
      // ret.data.dump('rel')

      const relation_id = ret.data.getId()      
      const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/relations/${relation_id}/right`;
      const request_data = sid_list

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

      // resp.dump("RESPONSE")

      return cb(resp)      

    } , relation_data )

  }

  async removeFromRelation(left_nodeid, relation_name, sid_list) {

    console.log(`### <removeFromRelation( ${left_nodeid} , ${relation_name} )> `)

    this.getRelation(left_nodeid, relation_name, async (ret) => {

      // ret.data.dump("RELATION")
      // ret.data.dump('rel')

      if (ret.status < 300) {

        const relation_id = ret.data.getId()
        const url = `${this.#_ctx.getServiceUrl(Context.SERVICE.HUB)}/${this.#_ctx.getScope()}/relations/${relation_id}/right`;
        const request_data = sid_list

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

        // resp.dump("RESPONSE")

        return cb(resp)

      } else {

        return cb(new ApiResponse( 404 , null ))

      }

    })

  }

}