// @ts-check
/**
* @import {FilterAction, FilterOptions} from './../../types.js'
*/
import {query,transaction, HEX2uuid, getConnection, UUID2hex} from '@commtool/sql-query'
import {addUpdateList } from '../../server.ws.js'
import { filterObjects, matchObject} from '../../utils/objectfilter/filters/index.js';
import { publishEvent } from '../../utils/events.js';
import { execDListFilter } from './dlistFilter.js';
import { errorLoggerUpdate } from '../../utils/requestLogger.js';
// excute an includer filter
//UIDFilter: (the UID of the filter)
// target: UID of the target list, we have to add the filtered objects to
/**
* Adds a filter to a target and executes the filtering logic
* @param {Buffer|String} UIDFilter - The UID of the filter object
* @param {Buffer|String} target - The UID of the target list/object
* @param {FilterAction} action - The type of filter action
* @param {string} organization - The organization UUID for multi-tenant scoping
* @param {FilterOptions} [options={}] - Additional options for the operation
* @returns {Promise<void>}
*/
export const addFilter=async (UIDFilter,target,action, organization, options={virtual:false, connection: null})=>
{
try {
// Type validation
UIDFilter=UUID2hex(UIDFilter)
target=UUID2hex(target)
if (typeof organization !== 'string') {
throw new TypeError(`organization must be a string, got ${typeof organization}`);
}
if (!['visible', 'changeable', 'include', 'exclude', 'intersect'].includes(action)) {
throw new TypeError(`action must be a valid FilterAction, got ${action}`);
}
// get filter Type
// make the Filter linked to the target
const {connection, virtual}= options
if(!virtual)
{
// virtual is supplied, this is ignored as we assume, that this is a virtual add after a removal
await query(`INSERT IGNORE INTO Links (UID,Type,UIDTarget)
VALUES(?,'list',?)`,[UIDFilter,target],
)
}
if(action==='visible' || action==='changeable')
{
// get the target type
const types=await query(`SELECT Type FROM ObjectBase WHERE UID=? `,[target])
if(types && types.length>0)
{
const Type=types[0].Type
if(Type==='job' || Type==='eventJob')
{
if(action==='visible')
await includeVisibility(UIDFilter,target,connection)
else
await includeChangeability(UIDFilter, target,connection)
}
else if(['list','dlist','email','location','event', 'eventT'].includes(Type))
await visibilityList(UIDFilter,target,connection,organization)
}
}
else
{
await execDListFilter(UIDFilter,target,action,organization,{connection,virtual})
}
}
catch(e)
{
errorLoggerUpdate(e)
}
}
/**
* Visibility filter function
* @param {any} actFilter - The filter object
* @param {Buffer} sourceUID - The source UID
* @param {any} [connection=null] - Database connection
* @returns {Promise<any[]|undefined>} Filtered results
*/
export const visibilityFilter =async (actFilter,sourceUID,connection=null)=>
{
try {
let filtered
//if(!connection)
//console.log(HEX2uuid(sourceUID))
const all=await query(`
(
SELECT ObjectBase.Type, ObjectBase.UID AS UID, ObjectBase.UIDBelongsTo AS UIDBelongsTo,ObjectBase.Data AS ExtraData,Main.Data,
ObjectBase.Title,Main.Display,Main.SortName
FROM ObjectBase
INNER JOIN Links
ON (ObjectBase.UID = Links.UID AND Links.Type IN ('member','memberA','memberS','memberSys') AND ObjectBase.Type NOT IN ('filter','list','dlist','email','event','location','eventT'))
INNER JOIN Member AS Main ON (Main.UID=ObjectBase.UIDBelongsTo)
WHERE (Links.UIDTarget=? ) AND ObjectBase.Type IN('person','extern','job','guest','group','event')
GROUP BY ObjectBase.UID
)
UNION
(
SELECT 'person' AS Type, ObjectBase.UID AS UID, ObjectBase.UIDBelongsTo AS UIDBelongsTo,ObjectBase.Data AS ExtraData,Main.Data,
ObjectBase.Title,Main.Display,Main.SortName
FROM ObjectBase
INNER JOIN Links
ON (ObjectBase.UID = Links.UID AND Links.Type IN ('member','memberA') AND ObjectBase.Type NOT IN ('filter','list','dlist','emeil','event','location','eventT'))
INNER JOIN Member AS Main ON (Main.UID=ObjectBase.UIDBelongsTo)
WHERE (Links.UIDTarget=? ) AND ObjectBase.Type ='entry'
GROUP BY ObjectBase.UID
)
UNION
(
SELECT 'group' AS Type, ObjectBase.UID AS UID, ObjectBase.UIDBelongsTo AS UIDBelongsTo,ObjectBase.Data AS ExtraData,Main.Data,
ObjectBase.Title,Main.Display,Main.SortName
FROM ObjectBase
INNER JOIN Member AS Main ON (Main.UID=ObjectBase.UID)
WHERE ObjectBase.UID=?
)
`,[sourceUID,sourceUID,sourceUID,sourceUID,sourceUID,sourceUID],{connection})
filtered=all
if(Object.keys(actFilter).length)
{
filtered=all.map(o=>{
try
{
return {...o,Data:JSON.parse(o.Data),ExtraData:JSON.parse(o.ExtraData)}
}
catch(e)
{
//console.log(e,o)
}
})
filtered=filterObjects(filtered,actFilter)
}
return filtered
}
catch(e)
{
errorLoggerUpdate(e)
}
}
/**
* Include visibility filter
* @param {Buffer} UIDFilter - Filter UID
* @param {Buffer} target - Target UID
* @param {any} [connection=null] - Database connection
* @returns {Promise<void>}
*/
export const includeVisibility=async (UIDFilter,target,connection=null)=>
{
try
{
//const myConnection=connection ? connection : await getConnection()
const filters=await query (`SELECT Data AS filterJSON, UIDBelongsTo AS sourceUID
FROM ObjectBase WHERE UID=? AND Type='visible'`,[UIDFilter],{cast:['json'],connection})
if(filters.length>0)
{
const [{filterJSON,sourceUID}]=filters
const filtered=await visibilityFilter(filterJSON,sourceUID,connection)
if(filtered && filtered.length)
{
// visibilty has to be added to the person, not to the job
const result = await query(`SELECT UIDBelongsTo AS user FROM Objects WHERE UID=?`,[target],{connection})
if(result.length>0)
{
const [{user}]=result
//console.log('includeVisibility',HEX2uuid(user))
await query(`INSERT INTO Visible (UID,Type,UIDUser) VALUES (?,'visible',?)
ON DUPLICATE KEY UPDATE UID=VALUE(UID)`, filtered.map(/** @param {any} o */ o=>([o.UID,user])),
{batch:true, connection})
// update the clients
addUpdateList(filtered.filter(/** @param {any} o */ o=>(o.Type==='group')).map(/** @param {any} el */ el=>el.UID))
}
}
}
}
catch(e)
{
console.log(e)
}
}
/**
* Include changeability filter
* @param {Buffer} UIDFilter - Filter UID
* @param {Buffer} target - Target UID
* @param {any} [connection=null] - Database connection
* @returns {Promise<void>}
*/
export const includeChangeability=async (UIDFilter,target, connection=null)=>
{
try
{
//const myConnection=connection ? connection : await getConnection()
const filters= await query (`SELECT Data AS filterJSON, UIDBelongsTo AS sourceUID
FROM ObjectBase WHERE UID=? AND Type='changeable'`,[UIDFilter],{cast:['json'],connection})
if(filters.length>0)
{
const [{filterJSON,sourceUID}]=filters
const filtered=await visibilityFilter(filterJSON,sourceUID,connection)
if(filtered && filtered.length)
{
// visibilty has to be added to the person, not to the job, so we get the UIDBelongsTo
const [{user}]= await query(`SELECT UIDBelongsTo AS user FROM Objects WHERE UID=?`,[target],{connection})
//console.log('includeVisibility',HEX2uuid(user))
await query(`INSERT INTO Visible (UID,Type,UIDUser) VALUES (?,'changeable',?)
ON DUPLICATE KEY UPDATE Type='changeable'`,
filtered.map(/** @param {any} o */ o=>([o.UID,user])),
{batch:true,connection}
)
}
}
}
catch(e)
{
errorLoggerUpdate(e)
}
}
/**
* Visibility list filter
* @param {Buffer} UIDFilter - Filter UID
* @param {Buffer} target - Target UID
* @param {any} [connection=null] - Database connection
* @param {string} [organization='UUID-00000000-0000-0000-0000-000000000000'] - Organization UUID for events
* @returns {Promise<void>}
*/
export const visibilityList=async (UIDFilter,target,connection=null,organization='UUID-00000000-0000-0000-0000-000000000000')=>
{
try
{
//const myConnection=connection ? connection : await getConnection()
const filters=await query (`SELECT Data AS filterJSON, UIDBelongsTo AS sourceUID, Type
FROM ObjectBase WHERE UID=? AND Type IN('visible','changeable')`,[UIDFilter])
const tresult=await query(`SELECT Type FROM ObjectBase WHERE UID=?`,[target])
const targetType=tresult[0].Type
if(filters.length>0)
{
const [{filterJSON,sourceUID,Type}]= filters
if(filterJSON)
{
const filterData= JSON.parse(filterJSON)
const filtered=await visibilityFilter(filterData,sourceUID)
const myType = Type==='visible' ? 'visible' : filterData.admin ? 'admin' : 'changeable'
if(filtered && filtered.length)
{
const paras=filtered.map(/** @param {any} o */ o=>([target,myType,o.UIDBelongsTo]))
await query(`INSERT INTO Visible (UID,Type,UIDUser) VALUES (?,?,?)
ON DUPLICATE KEY UPDATE
Type=IF(VALUE(Type)='admin','admin',IF(Type<>'admin'
AND VALUE(Type)='changeable','changeable',Type))`,
paras,
{batch:true, connection: connection})
const myKey=`/add/${targetType}/${myType}/${HEX2uuid(target)}`
publishEvent(myKey, { organization, data: filtered.map(/** @param {any} o */ o=>HEX2uuid(o.UIDBelongsTo)) })
}
}
}
addUpdateList(target)
}
catch(e)
{
errorLoggerUpdate(e)
}
}
/**
* Delete links for filters
* @param {Object} params - Parameters object
* @param {any[]} params.lists - List of objects
* @param {Buffer} params.UIDFilter - Filter UID
* @param {Buffer} params.target - Target UID
* @param {any} connection - Database connection
* @returns {Promise<void>}
*/
export const deleteLinks= async ({lists,UIDFilter,target},connection)=>
{
try {
if(lists.length>0 && lists[0].Type==='include')
{
await query(`DELETE Links FROM Links
LEFT JOIN (
Links AS lViolettOrange
INNER JOIN ObjectBase AS Filter ON (Filter.UID = lViolettOrange.UIDTarget AND Filter.Type ='include')
INNER JOIN Links AS lBlue ON (Filter.UID=lBlue.UID AND lBlue.UIDTarget=?)
)
ON (Links.UID=lViolettOrange.UID AND lViolettOrange.Type='dynamic' AND lViolettOrange.UIDTarget<> ? )
WHERE lViolettOrange.UIDTarget IS NULL AND Links.Type IN ('memberA','member') AND Links.UIDTarget=? `
,[target,UIDFilter,target],
{connection}
)
}
if(lists.length>0 && lists[0].Type!=='include')
{
await query(`DELETE Links FROM Links
LEFT JOIN (
Links AS lViolettOrange
INNER JOIN ObjectBase AS Filter ON (Filter.UID = lViolettOrange.UIDTarget AND Filter.Type =?)
INNER JOIN Links AS lBlue ON (Filter.UID=lBlue.UID AND lBlue.UIDTarget=?)
)
ON (Links.UID=lViolettOrange.UID AND lViolettOrange.Type='dynamic' AND lViolettOrange.UIDTarget<> ? )
WHERE lViolettOrange.UIDTarget IS NULL AND Links.Type ='member0' AND Links.UIDTarget=? `,
[lists[0].Type,target,UIDFilter,target],
{connection}
)
}
}
catch(e)
{
errorLoggerUpdate(e)
}
}
/**
* Delete entries function
* @param {any[]} remove - Array of entries to remove
* @param {any} connection - Database connection
* @returns {Promise<void>}
*/
export const deleteEntries=async (remove,connection)=>
{
try {
await query(`DELETE Entries,DelLinks FROM ObjectBase AS Entries
LEFT JOIN Links AS DelLinks ON (DelLinks.UID=Entries.UID)
WHERE Entries.Type ='entry' AND Entries.UID IN (?)
`,[remove.map(/** @param {any} el */ el=>el.UID)],
{connection})
}
catch(e)
{
errorLoggerUpdate(e)
}
}
/**
* Removes a filter from a target and cleans up associated entries
* @param {string|Buffer} UIDFilter - The UID of the filter to remove
* @param {string|Buffer} target - The UID of the target list/object
* @param {string} organization - The organization UUID for multi-tenant scoping
* @param {FilterOptions} [options={}] - Additional options for the operation
* @returns {Promise<void>}
*/
export const removeFilter=async (UIDFilter,target,organization,options={connection:null,virtual: false})=>
{
// this is called, when a filter is being removed
// get the type of filter
try {
// Type validation
UIDFilter=UUID2hex(UIDFilter)
target=UUID2hex(target)
if (typeof organization !== 'string') {
throw new TypeError(`organization must be a string, got ${typeof organization}`);
}
const {connection, virtual}= options
const lists=await query (`SELECT ObjectBase.Type FROM ObjectBase WHERE UID=?`,[UIDFilter],{connection})
await deleteLinks({lists,UIDFilter,target},connection)
// collect the entries without member or memberA links
const remove= await query(`
SELECT Entries.UID, Entries.UIDBelongsTo
FROM ObjectBase AS Entries
INNER JOIN Links AS DynamicLinks ON (DynamicLinks.UID=Entries.UID AND DynamicLinks.Type='dynamic' )
INNER JOIN ObjectBase AS Filter ON (Filter.UID=DynamicLinks.UIDTarget)
INNER JOIN Links AS ListLink ON (ListLink.UID=DynamicLinks.UIDTarget)
LEFT JOIN Links ON (Links.UID=Entries.UID AND Links.Type IN ('member','memberA'))
WHERE Links.UID IS NULL AND ListLink.UIDTarget=?
`,[target],{connection, log: false}
)
// delete the entries without dynamic links and there membership links
if(remove.length>0)
{
if(!connection)
{
await transaction(async (/** @type {any} */ connection)=>
{
await deleteEntries(remove,connection)
// delete the dynamic Links belonging to the filter
await query(`DELETE FROM Links WHERE UIDTarget=? AND Links.Type='dynamic'`,[UIDFilter], {connection})
// publish removal
publishEvent(`/remove/dlist/person/${HEX2uuid(target)}`, { organization, data: remove.map(/** @param {any} o */ o=>HEX2uuid(o.UIDBelongsTo)) })
})
}
else
{
await deleteEntries(remove,connection)
if(!virtual)
{
await query(`DELETE FROM Links WHERE UIDTarget=? AND Links.Type='dynamic'`,[UIDFilter],{ connection})
// publish removal
publishEvent(`/remove/dlist/person/${HEX2uuid(target)}`, { organization, data: remove.map(o=>HEX2uuid(o.UIDBelongsTo)) })
}
}
}
if(!virtual)
{
// virtual is supplied, this is ignored as we assume, that this is a virtual add after a removal
await query(`DELETE FROM Links WHERE Type='List' AND UID=? AND UIDTarget=? `,[UIDFilter,target],{connection})
}
}
catch(e)
{
errorLoggerUpdate(e)
}
}