// @ts-check
/**
* @import {PublishEventOptions} from '../utils/events.js'
*/
import {query,transaction, HEX2uuid} from '@commtool/sql-query'
import { includeChangeability, includeVisibility, visibilityList, visibilityFilter } from './executeFilters/executeFilters.js'
import { matchObject } from '@commtool/object-filter'
import { addVisibility } from './matchObjects/matchObjects.js'
import { publishEvent } from '../utils/events.js'
import { errorLoggerUpdate } from '../utils/requestLogger.js'
export const personRebuildAccess=async (UID)=>
{
try {
// rebuild all visibilities for none list/dlist/email/location the user with this UID has
// delete all existing links for the user UID
await transaction(async(connection)=>
{
//console.log('delete person visibility', HEX2uuid(UID))
await connection.query(`DELETE Visible FROM Visible INNER JOIN ObjectBase
ON (ObjectBase.UID=Visible.UID AND ObjectBase.Type NOT IN ('list','dlist','email','event','eventT','location'))
WHERE Visible.UIDUser=? AND Visible.Accounting IS NULL`,[UID])
// now build access for all functions of the person
// get all job filters and execute them
const jobsFilter=await connection.query(`SELECT filter.UID AS UIDFilter, filter.Type, Jobs.UID AS UIDJob
FROM ObjectBase AS filter INNER JOIN Links ON (Links.UID=filter.UID AND Links.Type='list')
INNER JOIN ObjectBase AS Jobs ON (Jobs.UID=Links.UIDTarget AND Jobs.Type='job')
WHERE filter.Type IN ('visible','changeable') AND Jobs.UIDBelongsTo=?
GROUP BY filter.UID`,[UID])
if(jobsFilter.length)
{
for (const filter of jobsFilter)
{
if(filter.Type==='visible')
await includeVisibility(filter.UIDFilter,filter.UIDJob,connection.connection)
else if(filter.Type==='changeable')
await includeChangeability(filter.UIDFilter,filter.UIDJob,connection.connection)
}
}
// enable user to edit himself
await connection.query(`INSERT INTO Visible (UID,Type,UIDUser) VALUES(?,'changeable',?) ON DUPLICATE KEY UPDATE Type='changeable'`,[UID,UID])
},
{
beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;`)
})
}
catch(e)
{
errorLoggerUpdate(e)
}
}
/**
* Rebuilds accounting visibility (Visible.Accounting) for a person based on
* their accountingVisible/accountingChangeable filter objects (rule-based access).
*
* Evaluates the filter rules and sets Visible.Accounting entries. Respects
* hierarchy: admin > changeable > visible — never overwrites a higher level.
*
* This is a separate step from personRebuildAccess and should only be called
* when accounting rules change (financialMaster toggle or accountingAccess/
* accountingWriteAccess changes), NOT on every visibility rebuild.
*
* @param {Buffer|string} UID - hex or UUID of the person
*/
export const personRebuildAccountingAccess = async (UID) => {
try {
// Clear rule-based Accounting (keep 'admin' from financialMaster)
await query(`UPDATE Visible SET Accounting=NULL
WHERE Visible.UIDUser=? AND Accounting IN ('visible','changeable')`,[UID])
const accountingFilters = await query(`SELECT filter.UID AS UIDFilter, filter.Type, Jobs.UID AS UIDJob
FROM ObjectBase AS filter INNER JOIN Links ON (Links.UID=filter.UID AND Links.Type='list')
INNER JOIN ObjectBase AS Jobs ON (Jobs.UID=Links.UIDTarget AND Jobs.Type='job')
WHERE filter.Type IN ('accountingVisible','accountingChangeable') AND Jobs.UIDBelongsTo=?
GROUP BY filter.UID`, [UID])
for (const filter of accountingFilters) {
const accountingLevel = filter.Type === 'accountingVisible' ? 'visible' : 'changeable'
const [filterData] = await query(`SELECT Data AS filterJSON, UIDBelongsTo AS sourceUID
FROM ObjectBase WHERE UID=?`, [filter.UIDFilter], {cast: ['json']})
if (!filterData) continue
const filtered = await visibilityFilter(filterData.filterJSON, filterData.sourceUID)
if (filtered && filtered.length) {
const [{user}] = await query(`SELECT UIDBelongsTo AS user FROM Objects WHERE UID=?`, [filter.UIDJob])
await query(`INSERT INTO Visible (UID, Type, Accounting, UIDUser) VALUES (?,'visible',?,?)
ON DUPLICATE KEY UPDATE Accounting =
CASE
WHEN VALUES(Accounting) = 'admin' THEN 'admin'
WHEN Accounting = 'admin' THEN Accounting
WHEN VALUES(Accounting) = 'changeable' THEN 'changeable'
WHEN Accounting = 'changeable' THEN Accounting
ELSE VALUES(Accounting)
END`,
filtered.map(o => ([o.UID, accountingLevel, user])),
{batch: true})
}
}
}
catch(e) {
errorLoggerUpdate(e)
}
}
export const objectRebuildAccess=async (UIDobject,targets)=>
{
try {
// rebuild all visibility links for an Object when it is removed from targets
/* console.log('delete object visibility', HEX2uuid(UIDobject),targets.map(u=>HEX2uuid(u)))
await query(`DELETE Visible FROM Visible
INNER JOIN ObjectBase AS Jobs ON (Jobs.UIDBelongsTo=Visible.UIDUser AND Jobs.Type='job' )
INNER JOIN Links ON (Links.UID=Jobs.UID AND Links.Type IN ('member','memberA'))
WHERE Visible.UID=? AND Links.UIDTarget IN (?)`,[UIDobject,targets])*/
// now build access for all functions of the persons, which are not part of the target list
// get all job filters which are still belonging to the object and execute them
// rebuild access for actual targets its belonging to
const sources= await query(`SELECT UID FROM Links WHERE UIDTarget=? AND Type IN ('member','memberA')`,[UIDobject])
if(sources.length)
await addVisibility(UIDobject,sources.map(s=>s.UID),{andRemove:true})
}
catch(e)
{
errorLoggerUpdate(e)
}
}
export const listRebuildAccess=async (UIDtarget, UIDOrga)=>
{
try {
// rebuild all visibility links for a specfic list,dlist,emeil,event,eventT,location supllied in UIDtarget
// delete all existing links for the list
const [{targetType}]=await query(`SELECT Type AS targetType FROM ObjectBase WHERE UID=? `,[UIDtarget])
// Ensure UIDOrga is in hex format for publishEvent
if (Buffer.isBuffer(UIDOrga)) {
UIDOrga = HEX2uuid(UIDOrga);
}
const oldVisibility=await query(`SELECT Visible.UIDUser,Visible.Type
FROM Visible
WHERE Visible.UID=?`,[UIDtarget])
await transaction(async (connection)=>
{
await connection.query(`DELETE Visible FROM Visible WHERE UID=?`,[UIDtarget])
const objectFilter=await query(`SELECT filter.UID AS UIDFilter,filter.Data AS Data, filter.UIDBelongsTo AS UIDSource
FROM ObjectBase AS filter INNER JOIN Links ON (Links.UID=filter.UID AND Links.Type IN ('list','dlist','email','event','eventT','location'))
WHERE filter.Type IN ('visible','changeable') AND Links.UIDTarget=? `,[UIDtarget])
if(objectFilter.length)
{
for (const filter of objectFilter)
{
await visibilityList(filter.UIDFilter,UIDtarget,connection)
}
}
// now add the owner of the list to the visibility
await connection.query(`INSERT INTO Visible (UID,Type,UIDUser)
SELECT Links.UID,'admin',Links.UIDTarget
FROM Links
INNER JOIN ObjectBase ON (ObjectBase.UID=Links.UIDTarget AND ObjectBase.Type='person')
WHERE Links.Type ='member' AND Links.UID=?
ON DUPLICATE KEY UPDATE Type='admin'`,[UIDtarget])
// always add the organization superadmin (memberSys extern) so it survives filter replacements
await connection.query(`INSERT INTO Visible (UID,Type,UIDUser)
SELECT ?, 'admin', Links.UID
FROM Links
INNER JOIN ObjectBase AS Admin ON (Admin.UID=Links.UID AND Admin.Type='extern')
WHERE Links.Type='memberSys' AND Links.UIDTarget=U_UUID2BIN(?)
ON DUPLICATE KEY UPDATE Type='admin'`,[UIDtarget, UIDOrga])
},
{
beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;`)
})
const newVisibility=await query(`SELECT Visible.UIDUser,Visible.Type
FROM Visible
WHERE Visible.UID=?`,[UIDtarget])
const remove=oldVisibility.filter(oldEl=>!newVisibility.find(el=>(oldEl.UIDUser.equals(el.UIDUser) && oldEl.Type===el.Type)))
if(remove.length>0)
{
const admin=remove.filter(el=>el.Type==='admin')
if(admin.length>0)
publishEvent(`/remove/${targetType}/admin/${HEX2uuid(UIDtarget)}`, { organization: UIDOrga, data: admin.map(el=>HEX2uuid(el.UIDUser)) })
const changeable=remove.filter(el=>el.Type==='changeable')
if(changeable.length>0)
publishEvent(`/remove/${targetType}/changeable/${HEX2uuid(UIDtarget)}`, { organization: UIDOrga, data: changeable.map(el=>HEX2uuid(el.UIDUser)) })
const visible=remove.filter(el=>el.Type==='visible')
if(visible.length>0)
publishEvent(`/remove/${targetType}/visible/${HEX2uuid(UIDtarget)}`, { organization: UIDOrga, data: visible.map(el=>HEX2uuid(el.UIDUser)) })
}
const add= newVisibility.filter(newEl=>!oldVisibility.find(el=>(newEl.UIDUser.equals(el.UIDUser) && newEl.Type===el.Type)))
if(add.length>0)
{
const admin=add.filter(el=>el.Type==='admin')
if(admin.length>0)
publishEvent(`/add/${targetType}/admin/${HEX2uuid(UIDtarget)}`, { organization: UIDOrga, data: admin.map(el=>HEX2uuid(el.UIDUser)) })
const changeable=add.filter(el=>el.Type==='changeable')
if(changeable.length>0)
publishEvent(`/add/${targetType}/changeable/${HEX2uuid(UIDtarget)}`, { organization: UIDOrga, data: changeable.map(el=>HEX2uuid(el.UIDUser)) })
const visible=add.filter(el=>el.Type==='visible')
if(visible.length>0)
publishEvent(`/add/${targetType}/visible/${HEX2uuid(UIDtarget)}`, { organization: UIDOrga, data: visible.map(el=>HEX2uuid(el.UIDUser)) })
}
}
catch(e)
{
errorLoggerUpdate(e)
}
}
export const personListRebuildAccess= async (UID, UIDOrga)=>
{
// rebuild all visibilities for list/email/dlist/event/eventT the user with this UID has
// delete all exsiting links for the user UID
try {
// Ensure UIDOrga is in hex format for publishEvent
if (Buffer.isBuffer(UIDOrga)) {
UIDOrga = HEX2uuid(UIDOrga);
}
/** @type {any[]} */
let oldVisibility = []
/** @type {any[]} */
let newVisibility = []
// Read-only: get all filter pairs before the transaction to avoid long-lived locks
const filterPairs=await query(`SELECT filter.UID,filter.Data AS filterData,list.UID AS listUID,job.Type AS ObjectType, job.Data AS ExtraData,Member.Data,
Main.Data AS MainBaseData, list.Display,filter.Type
FROM ObjectBase AS filter
INNER JOIN Links AS gLink ON (gLink.UIDTarget=filter.UIDBelongsTo AND filter.Type IN ('visible','changeable' ) AND gLink.Type IN ('member','memberA','memberSys') )
INNER JOIN Links AS lLink ON (lLink.UID=filter.UID AND lLink.Type='list')
INNER JOIN ObjectBase AS list ON (list.UID=lLink.UIDTarget AND list.Type IN ('list','dlist','email','event','eventT','location'))
INNER JOIN ObjectBase AS job ON (gLink.UID=job.UID AND job.Type IN ('person','guest','extern','job'))
INNER JOIN Member ON (Member.UID=job.UIDBelongsTo)
INNER JOIN ObjectBase AS Main ON (Main.UID=Member.UID)
WHERE job.UIDBelongsTo=?
GROUP BY filter.UID,job.UID
`,[UID])
await transaction(async (connection)=>
{
//console.log('starte transaktion', HEX2uuid(UID))
oldVisibility=await connection.query(`SELECT Visible.UID,Visible.Type,ObjectBase.Type AS ObjectType
FROM Visible INNER JOIN ObjectBase
ON (ObjectBase.UID=Visible.UID AND ObjectBase.Type IN ('list','dlist','email','event','eventT','location'))
WHERE Visible.UIDUser=?`,[UID])
await connection.query(`DELETE Visible FROM Visible INNER JOIN ObjectBase
ON (ObjectBase.UID=Visible.UID AND ObjectBase.Type IN ('list','dlist','email','event','eventT','location'))
WHERE Visible.UIDUser=?`,[UID])
// now build pair of access of lists for all jobs of the person
// now execute all filter object pairs
const toBeAdded=filterPairs.map(el=>
({...el,filterData:JSON.parse(el.filterData),Data:JSON.parse(el.Data),ExtraData:JSON.parse(el.ExtraData),MainBaseData:JSON.parse(el.MainBaseData)})
)
.filter(fp=>(
matchObject(
{UID:UID, Type:fp.ObjectType, Data:fp.Data, ExtraData: fp.ExtraData, MainBaseData: fp.MainBaseData}
,fp.filterData)
))
.reduce((result,actual)=>{
const already=result.find(el=>el.listUID.equals(actual.listUID))
if(already)
{
if(actual.Type==='changeable')
{
if(actual.filterData.admin)
already.Type='admin'
else
already.Type='changeable'
}
return result
}
else
{
const Type= actual.Type==='changeable' ? actual.filterData.admin ? 'admin' :'changeable' :'visible'
return [...result,{listUID:actual.listUID,Type:Type}]
}
},[])
if(toBeAdded.length >0)
{
await connection.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))`,
toBeAdded.map(el=>([el.listUID,el.Type,UID])),
{batch:true})
}
newVisibility=await connection.query(`SELECT Visible.UID,Visible.Type,ObjectBase.Type AS ObjectType
FROM Visible INNER JOIN ObjectBase
ON (ObjectBase.UID=Visible.UID AND ObjectBase.Type IN ('list','dlist','email','event','eventT','location'))
WHERE Visible.UIDUser=?`,[UID])
// console.log('beende transaktion', HEX2uuid(UID))
},
{
beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;`)
})
const remove=oldVisibility.filter(oldEl=>!newVisibility.find(el=>(oldEl.UID.equals(el.UID) && oldEl.Type===el.Type)))
remove.forEach(el => {
publishEvent(`/remove/${el.ObjectType}/${el.Type}/${HEX2uuid(el.UID)}`, { organization: UIDOrga, data: [HEX2uuid(UID)] })
});
const add= newVisibility.filter(newEl=>!oldVisibility.find(el=>(newEl.UID.equals(el.UID) && newEl.Type===el.Type)))
add.forEach(el => {
publishEvent(`/add/${el.ObjectType}/${el.Type}/${HEX2uuid(el.UID)}`, { organization: UIDOrga, data: [HEX2uuid(UID)] })
});
}
catch(e)
{
errorLoggerUpdate(e)
}
}