// @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'))
}
}