Source: tree/treeQueue/treeMigrate.js


// @ts-check
/**
 * @import {FilterAction, treeAction} from './../../types.js'
 */

import {query, HEX2uuid} from '@commtool/sql-query'
import {addUpdateList} from '../../server.ws.js'
import {addVisibility} from '../matchObjects/matchObjects.js';
import { checkEntries } from '../matchObjects/checkEntries.js';
import { matchObjectsLists} from '../matchObjects/matchObjectsLists.js'
import rebuildFees from '../rebuildFees.js'
import { publishEvent } from '../../utils/events.js'
import { errorLoggerUpdate } from '../../utils/requestLogger.js';

/**
 * Handles migration actions for moving objects between different groups or targets.
 * Supports migration of persons, families, and groups between different organizational units.
 * 
 * @async
 * @function migrateAction
 * @param {treeAction} action - The migration action object containing source and target information
 * @returns {Promise<void>}
 * @throws {Error} Throws an error if migration is attempted for unsupported object types
 */
export const migrateAction= async (action)=>
{
    try 
    {
        /** @type {number|null} */
        const timestamp = action.timestamp ? (typeof action.timestamp === 'string' ? parseFloat(action.timestamp) : action.timestamp) : null
        /** @type {string} */
        const asOf=timestamp ? `FOR SYSTEM_TIME AS OF FROM_UNIXTIME(${timestamp})` : ''
        if(!['group','person','family','familyB'].includes (action.Type))
        {
           throw('migrate action only supported for types person, family, familyB and group')
        }
        // family update
        else if(action.Type==='family' || action.Type==='familyB')
        {
            await query(`DELETE FROM Links WHERE UID=? AND Type IN('family','familyFees') AND UIDTarget=?`,[action.UIDObjectID, action.UIDoldTarget])
            const rest= await query(`SELECT Links.UID FROM Links WHERE Links.UIDTarget=? AND Type IN('family','familyFees')`,[action.UIDoldTarget])
            if(rest.length>0)
            {
                // update family indices and calculate new membership fees
                rebuildFees(action.UIDoldTarget, HEX2uuid(action.UIDroot))
            }
            else
                query (`DELETE FROM Member WHERE UID=?`,[action.UIDoldTarget])
            await query(`INSERT IGNORE INTO Links (UID,Type,UIDTarget) VALUES(?,?,?)`,[action.UIDObjectID,action.Type==='familyB'? 'familyFees' : 'family', action.UIDnewTarget])
            // update family indices and calculate new membership fees
            rebuildFees(action.UIDnewTarget, HEX2uuid(action.UIDroot))
        }
        else
        {


            // get delta between old and new membership
            const diffOld=await query(`SELECT Links.UIDTarget, ObjectBase.Title,Member.Display, Links.TUIDTarget
                                    FROM Links ${asOf}
                                    INNER JOIN ObjectBase ${asOf} ON (Links.UIDTarget=ObjectBase.UID AND ObjectBase.Type IN ('group','ggroup'))
                                    INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)
                                    WHERE Links.Type IN ('member','memberA') AND Links.UID=?
                                    AND  Links.UIDTarget NOT IN 
                                    (SELECT Links.UIDTarget FROM Links ${asOf}  WHERE Links.Type IN ('member','memberA') AND Links.UID=? )
                                    GROUP BY ObjectBase.UID`,
                                    [action.UIDoldTarget,action.UIDnewTarget])

            /** @type {Buffer[]} */
            const deltaOld=diffOld.map(ob=>ob.UIDTarget).filter(el=>!action.UIDoldTarget.equals(el))
            // add mutation group as well
            /** @type {Buffer[]} */
            const deltaOldPlus=[...deltaOld,action.UIDoldTarget]   

            // get delta between new and old membership
            const diffNew=await query(`
                SELECT UIDTarget,ObjectBase.Title, Member.Display 
                FROM Links  ${asOf}
                INNER JOIN ObjectBase ${asOf} ON (Links.UIDTarget=ObjectBase.UID AND ObjectBase.Type IN ('group','ggroup'))
                INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)
                WHERE Links.Type IN ('member','memberA') AND Links.UID=?
                AND  Links.UIDTarget NOT IN 
                (SELECT UIDTarget FROM Links  ${asOf} 
                    WHERE Type IN ('member','memberA') AND UID=?
                    AND UIDTarget NOT IN (?)
                )
                GROUP BY UIDTarget`,
                [action.UIDnewTarget,action.UIDoldTarget,deltaOldPlus])

            /** @type {Buffer[]} */
            const deltaNew=diffNew.map(ob=>ob.UIDTarget).filter(el=>!action.UIDnewTarget.equals(el))
            // add mutation group as well
            /** @type {Buffer[]} */
            const deltaNewPlus=[...deltaNew,action.UIDnewTarget]   
            if (['person','extern'].includes(action.Type))
            {   
                await addVisibility(action.UIDObjectID,deltaNewPlus)
                const allGroups=await query(
                    `SELECT UIDTarget FROM Links ${asOf} 
                    WHERE Type IN ('member','memberA') AND Links.UID=? `,
                    [action.UIDnewTarget])
                await matchObjectsLists(action.UIDObjectID,[action.UIDnewTarget,...allGroups.map(el=>el.UIDTarget)], HEX2uuid(action.UIDroot))
                await checkEntries(action.UIDObjectID, HEX2uuid(action.UIDroot))
                // now fire the events for adding
                if(deltaNewPlus.length>0)
                {
                    for(const group of deltaNewPlus)
                    {
                        publishEvent(`/add/group/${action.Type}/${HEX2uuid(group)}`, {
                            organization: HEX2uuid(action.UIDroot),
                            data: [HEX2uuid(action.UIDObjectID)],
                            backDate: timestamp
                        })
                    }
                }
                        // now fire the events for removing
                if(deltaOldPlus.length>0)
                {
                    for(const group of deltaOldPlus)
                    {
                        console.log('remove',HEX2uuid(group))
                        publishEvent(`/remove/group/${action.Type}/${HEX2uuid(group)}`, {
                            organization: HEX2uuid(action.UIDroot),
                            data: [HEX2uuid(action.UIDObjectID)],
                            backDate: timestamp
                        })
                    }
                }
            }
            if (action.Type==='group')
            {
                // group mutation not yet tested,and probably not needed

            /* // get objects to be transfered
                const objects= await query(`SELECT ObjectBase.UID,ObjectBase.Type FROM ObjectBase INNER JOIN Links ON (Links.UID=ObjectBase.UID) WHERE Links.UIDTarget=?`,[action.UIDoldTarget])
                const memberCount=objects.filter(ob=>ob.Type==='person').length
                const personObjects= objects.filter(o=>o.Type !=='group').map(o=>o.UID)
                // delete deltaOld membership
                if(objects.length>0 && deltaOld.length>0)
                    await query(`DELETE FROM Links WHERE UID IN (?)  AND UIDTarget IN (?) AND Type ='member'`,[objects,deltaOld])
                await query(`DELETE FROM Links WHERE UID = ?   AND UIDTarget = ? AND Type ='memberA'`,[action.UIDObjectID,action.UIDoldTarget])

            // add deltaNew membership
                const relations=deltaNewPlus.map(delta=>(objects.map(ob=>[ob.UID,'member',delta]))).flat()
                if(relations.length>0)
                    await query(`INSERT IGNORE INTO Links (UID, Type, UIDTarget) VALUES ?`,[relations])
                // add main membership
                await query(`INSERT IGNORE INTO Links (UID, Type, UIDTarget) VALUES (?,'memberA',?)`,[action.UIDObjectID,action.UIDnewTarget])
        
            // job filters
                const deltaJobs=objects.filter(g=>(g.Type==='job')).map(g=>g.UID)
                // delete the old ones
                if(deltaJobs.length>0 && deltaOldPlus.length>0)
                    await query(`DELETE ObjectBase,Links FROM ObjectBase,Links   
                            WHERE ObjectBase.UID=Links.UID AND ObjectBase.Type='filter' AND Links.Type ='filter' AND
                            Links.UIDTarget IN (?) AND ObjectBase.UIDBelongsTo IN (?)`,[deltaJobs,deltaOldPlus])
                // delete the list links, form delta Groups to persons
                if(deltaJobs.length>0 && deltaOldPlus.length>0)
                    await query(`DELETE Links FROM Links WHERE Type='list' AND UID IN (?) AND UIDTarget IN (?)  `,[deltaOldPlus,deltaJobs])
                // add the new ones
                // get the Data for the job and relevant groups
                if(deltaNewPlus.length>0 && deltaJobs.length>0)
                {                           
                    const newFilters=await query(`
                        SELECT ObjectBase.UID, ObjectBase.Title,ObjectBase.Data,Groups.UID AS UIDGroup, Groups.Hierarchie AS GroupHierarchie,
                                Function.Data AS FunctionData, Groups.Title AS GroupsTitle
                        FROM ObjectBase
                        INNER JOIN Links AS FLinks 
                            ON (ObjectBase.UID=FLinks.UIDTarget AND FLinks.Type='function')
                        INNER JOIN ObjectBase AS Function
                            ON (Function.UID=FLinks.UID)
                        INNER JOIN 
                            (SELECT ObjectBase.UID, JSON_VALUE(ObjectBase.Data,'$.hierarchie')  AS Hierarchie, ObjectBase.Title FROM ObjectBase
                                WHERE ObjectBase.Type='group' AND ObjectBase.UID IN (?))
                                AS Groups 
                            ON JSON_CONTAINS_PATH(Function.Data,'one',CONCAT('$.access.',Groups.Hierarchie))
                        WHERE ObjectBase.UID IN (?);`,[deltaNewPlus,deltaJobs])

                    
                    await checkEntries(personObjects, HEX2uuid(action.UIDroot))
                    const objectsUID=objects.map(o=>o.UID)
                    // rebuild visibilty for the existing filters in the new targets with teh new added objects
                    await addVisibility(objectsUID,deltaNewPlus)
                    // add the includer to the groups which will later add the visibilty to the person and add the members of the new target
                    if(newFilters.lenght>0)
                        {
                        for(const filter of newFilters)
                        {
                            // add filter to group for extraction of the visible objects and pointing to job
                            const [{UID:filterUID}]= await query(`SELECT UIDV1() AS UID`)
                            const fdata=JSON.parse(filter.FunctionData)
                            const access=fdata.access[filter.GroupHierarchie]
                            await query(`
                                INSERT INTO ObjectBase(UID,Type,UIDBelongsTo,Title,Display,SortName, FullTextIndex, dindex,Data)
                                VALUES (?,'filter',?,?,?,'visible','',0,?)
                                `,
                                [
                                    filterUID, filter.UIDGroup, filter.Title,filter.GroupsTitle,JSON.stringify(access)
                                ]
                            )
                            // Add where this filter will act to
                            await query(`
                                INSERT INTO Links(UID,Type,UIDTarget)
                                VALUES (?,'filter',?)
                                `,
                                [
                                    filterUID, filter.UID
                                ]
                            )
                            // execute the filter
                            await includeVisibility(filterUID,filter.UID)
                            // Add where the List Link
                            await query(`
                                    INSERT INTO Links(UID,Type,UIDTarget)
                                    VALUES (?,'list',?)
                                `,
                                [
                                    filter.UIDGroup, filter.UID
                                ]
                            )
                            //queueAdd(action.UIDroot, action.UIDuser, 'filter', filterUID,hGroup.UID,null,action.UIDObjectID)
                        }
                    }
                }
        
                // execute all none visibilit Filters for the new Delta

                await matchObjectsLists(personObjects,deltaNewPlus, HEX2uuid(action.UIDroot))
                //query(`UPDATE Objects SET dindex=dindex-? WHERE UID IN(?)`,[memberCount,deltaOldPlus])
                // increment count dindex of the group and deltaNew
                //query(`UPDATE Objects SET dindex=dindex+? WHERE UID IN(?)`,[memberCount,deltaNewPlus])
                // Count updates
            
                // now fire the events for adding
                for(const group of deltaNewPlus)
                {
                    const UIDOrga = await getOrganizationForObject(group.UIDnewTarget);
                    publishEvent(`/add/group/group/${HEX2uuid(group.UIDnewTarget)}`, {
                        organization: HEX2uuid(UIDOrga),
                        data: HEX2uuid(action.UIDObjectID)
                    })
                }
                // now fire the events for adding
                for(const group of deltaOldPlus)
                {
                    const UIDOrga = await getOrganizationForObject(group.UIDoldTarget);
                    publishEvent(`/remove/group/group/${HEX2uuid(group.UIDoldTarget)}`, {
                        organization: HEX2uuid(UIDOrga),
                        data: HEX2uuid(action.UIDObjectID)
                    })
                }
                */
            }
            if(action.Type!=='person' && action.Type!=='extern')
                addUpdateList([...deltaOldPlus,...deltaNewPlus,action.UIDnewTarget])
        }


    }
    catch(e)
    {
        errorLoggerUpdate(e || new Error('treeMigrate: Unknown error occurred'))
    }


}