import {query,transaction, HEX2uuid} from '@commtool/sql-query'
import { publishEvent } from './events.js'
import { addFilter, removeFilter } from '../tree/executeFilters/executeFilters.js'
import { errorLoggerUpdate } from './requestLogger.js'
/**
* Comparator function for sorting filters in ascending order during filter rebuilding.
* Sorts by filter type priority: include > exclude > intersect
*
* @function compareUP
* @param {Object} filterA - First filter object with Type property
* @param {Object} filterB - Second filter object with Type property
* @returns {number} -1 if filterA < filterB, 0 if equal, 1 if filterA > filterB
*/
const compareUP= ( filterA,filterB)=>
{
return filterA.Type===filterB.Type ?
0 : filterA.Type==='include' ? 1 :
filterA.Type==='intersect' ? -1 :
filterB.Type==='include' ? -1 :
1
}
/**
* Comparator function for sorting filters in descending order during filter removal.
* Sorts by filter type priority: include > exclude > intersect (reverse order)
*
* @function compareDOWN
* @param {Object} filterA - First filter object with Type property
* @param {Object} filterB - Second filter object with Type property
* @returns {number} -1 if filterA < filterB, 0 if equal, 1 if filterA > filterB
*/
const compareDOWN= ( filterA,filterB)=>
{
return filterA.Type===filterB.Type ?
0 : filterA.Type==='include' ? -1 :
filterA.Type==='intersect' ? 1 :
filterB.Type==='include' ? 1 :
-1
}
/**
* Rebuilds dynamic list filters and publishes appropriate events for membership changes.
* This function removes and re-adds filters within a transaction, then compares the before/after
* state to publish add/remove events for affected list members.
*
* @async
* @function rebuildFiltersDlist
* @param {Array} rebuildFilters - Array of filter objects to rebuild, each containing UIDFilter, UIDList, and Type
* @param {string|Buffer} UIDOrga - The unique identifier of the organization (required)
* @returns {Promise<Object>} Result object containing beAdded and removed arrays
* @throws Will log an error if any operation fails during execution.
*/
export const rebuildFiltersDlist=async (rebuildFilters, UIDOrga)=>
{
try {
// store the actual members
const aLinks= test ? [] : await query(`SELECT ObjectBase.UID,ObjectBase.UIDBelongsTo,ObjectBase.Title,Links.Type
FROM Links
INNER JOIN ObjectBase ON(ObjectBase.UID=Links.UID AND ObjectBase.Type='entry')
WHERE Links.UIDTarget=? AND Links.Type IN ('member','memberA','member0')
GROUP BY ObjectBase.UIDBelongsTo `,
[rebuildFilters[0].UIDList],{log:false}
)
let nLinks
// remove the filter and add it within a virtual transaction (this avoids unnessecarry history clutter, as everything is rolled back before closeing)
await transaction(async (connection)=>{
rebuildFilters.sort(compareDOWN)
for(const filter of rebuildFilters)
{
// Ensure UIDOrga is in hex format for publishEvent
if (Buffer.isBuffer(UIDOrga)) {
UIDOrga = HEX2uuid(UIDOrga);
}
await removeFilter(filter.UIDFilter,filter.UIDList,{connection, virtual: true}) // as we supply the connection, this will be a virtual removal
}
rebuildFilters.sort(compareUP)
for(const filter of rebuildFilters)
{
await addFilter(filter.UIDFilter,filter.UIDList,filter.Type,{connection,virtual:true})
}
nLinks=await connection.query(`SELECT ObjectBase.UID,ObjectBase.UIDBelongsTo,ObjectBase.Title,Links.Type
FROM Links
INNER JOIN ObjectBase ON(ObjectBase.UID=Links.UID AND ObjectBase.Type='entry')
WHERE Links.UIDTarget=? AND Links.Type IN ('member','memberA','member0') `,
[filter.UIDList])
},
{virtual:true,log:false, beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL REPEATABLE READ`/* READ UNCOMMITTED;*/)})
// compare now nLinks and aLinks and execute the add/removal and publish the events
let beAdded=nLinks.filter(nL=>{
const aLink= aLinks.find(aL=>aL.UIDBelongsTo.equals(nL.UIDBelongsTo))
if(!aLink)
{
//return the nL
return true
}
else
{
// check, if we had before link type member0, if yes, convert it to new member type and publish add
if(aLink.Type==='member0' && nL.Type!=='member0')
{
query(`UPDATE Links SET Type=? WHERE UID=? AND UIDTarget=? AND Type=?`,[nL.Type,aLink.UID,filter.UIDList,aLink.Type])
publishEvent(`/add/dlist/entry/${HEX2uuid(filter.UIDList)}`, UIDOrga, [HEX2uuid(nL.UID)], Date.now()/1000)
publishEvent(`/add/dlist/person/${HEX2uuid(filter.UIDList)}`, UIDOrga, [HEX2uuid(nL.UIDBelongsTo)], Date.now()/1000)
}
}
return false
})
// add the new entry objects and link them
if(beAdded.length>0 && !test)
{
await query(`INSERT INTO ObjectBase (UID,Type,UIDBelongsTo,Title) VALUES (?,'entry',?,?)`,
beAdded.map(o=>([o.UID,o.UIDBelongsTo,o.Title])),
{batch:true})
await query(
`INSERT IGNORE INTO Links (UID,Type,UIDTarget)
VALUES (?,'dynamic',?)`,
beAdded.map(o=>[o.UID,filter.UIDFilter]),
{batch:true})
await query(
`INSERT IGNORE INTO Links (UID,Type,UIDTarget)
VALUES (?,'memberA',?)`,
beAdded.map(o=>[o.UID,filter.UIDList]),
{batch:true})
}
// now the other way around. Check what is not any more menber
const oldDiff= aLinks.filter(aL=>{
const nL=nLinks.find(o=>o.UIDBelongsTo.equals(aL.UIDBelongsTo))
if(!nL || (nL.Type ==='member0' && aL.Type!=='member0') )
{
return true
}
})
const reAdded=oldDiff.filter(o=>o.Type==='member0')
beAdded=[...beAdded,...reAdded]
if(beAdded.length>0)
{
publishEvent(`/add/dlist/entry/${HEX2uuid(filter.UIDList)}`, UIDOrga, beAdded.map(o=>HEX2uuid(o.UID)), Date.now()/1000)
publishEvent(`/add/dlist/person/${HEX2uuid(filter.UIDList)}`, UIDOrga, beAdded.map(o=>HEX2uuid(o.UIDBelongsTo)), Date.now()/1000)
}
const removed=oldDiff.filter(o=>o.Type!=='member0')
if(removed.length>0)
{
// delete the entries and dynamic and memberA links
await query(`
DELETE Links,ObjectBase FROM ObjectBase INNER JOIN Links ON (Links.UID=ObjectBase.UID) WHERE ObjectBase.UID IN(?)
`,[removed.map(o=>(o.UID))],
{log:true})
publishEvent(`/remove/dlist/person/${HEX2uuid(filter.UIDList)}`, UIDOrga, removed.map(o=>HEX2uuid(o.UIDBelongsTo)), Date.now()/1000)
}
return {beAdded,removed}
}
catch(e)
{
errorLoggerUpdate(e)
}
}