Source: RouterEvents/eventVisibility.js

// @ts-check
/**
 * Event Visibility Service
 * 
 * Shared service for managing event visibility across event and eventGroup modules.
 * Handles visibility filters and access control for events.
 */

import { transaction, query, UUID2hex, HEX2uuid} from "@commtool/sql-query"
import { visibilityList} from "../tree/executeFilters/executeFilters.js"

/**
 * Add group visibility to an event
 * @param {Buffer} UIDevent - Event UID
 * @param {Buffer|Buffer[]} groupUID - Group UID or array of group UIDs
 * @param {Object} req - Request object with session data
 * @param {Object|null} connection - Optional database connection for transaction
 */
export const addGroupVisibility=async (UIDevent,groupUID, req, connection=null) =>
{
    // this will add the visisbility filters to the event based on the group or groups supplied
    const root=UUID2hex(req.session.root)
    const user=UUID2hex(req.session.user)
    //console.log('addGroupVisibility', HEX2uuid(UIDevent))
    // get the groups this group belongs to
    let allGroups=[]
    const [eventT]= await query(`SELECT ObjectBase.Data
        FROM ObjectBase 
        INNER JOIN Links ON (Links.UIDTarget=ObjectBase.UID AND Links.Type='event')
        WHERE Links.UID=? AND ObjectBase.Type='eventT'`,[UIDevent],{cast:['json']})
    const template=eventT.Data
    if(Array.isArray(groupUID))
    {

        allGroups=await query (`
            SELECT  UID ,hierarchie,Title,UIDchangeable,UIDvisible
            FROM
            ((
                        SELECT myGroup.UID AS UID ,myGroup.hierarchie,myGroup.Title,UIDV1() AS UIDchangeable, UIDV1() AS UIDvisible
                        FROM ObjectBase AS myGroup 
                        INNER JOIN Links ON (Links.UIDTarget=myGroup.UID AND Links.Type IN ('member','memberA','memberS'))
                        WHERE Links.UID IN (?) AND myGroup.Type='group'
                        GROUP BY myGroup.UID
                    )
                    UNION ALL
                    (
                        SELECT myGroup.UID AS UID ,myGroup.hierarchie,myGroup.Title,UIDV1() AS UIDchangeable, UIDV1() AS UIDvisible
                        FROM ObjectBase AS myGroup 
                        WHERE myGroup.UID IN (?) AND myGroup.Type='group'
                        GROUP BY myGroup.UID
                )) AS total
                 GROUP BY UID;

        `,[groupUID,groupUID],{connection})
    }
    else
    {
        allGroups=await query (`
                SELECT  UID ,hierarchie,Title,UIDchangeable,UIDvisible
                FROM
                ((
                            SELECT myGroup.UID AS UID ,myGroup.hierarchie,myGroup.Title,UIDV1() AS UIDchangeable, UIDV1() AS UIDvisible
                            FROM ObjectBase AS myGroup 
                            INNER JOIN Links ON (Links.UIDTarget=myGroup.UID AND Links.Type IN ('member','memberA','memberS'))
                            WHERE Links.UID =? AND myGroup.Type='group'
                            GROUP BY myGroup.UID
                        )
                        UNION ALL
                        (
                            SELECT myGroup.UID AS UID ,myGroup.hierarchie,myGroup.Title,UIDV1() AS UIDchangeable, UIDV1() AS UIDvisible
                            FROM ObjectBase AS myGroup 
                            WHERE myGroup.UID =? AND myGroup.Type='group'
                            GROUP BY myGroup.UID
                )) AS total
                GROUP BY UID;
       
        `,[groupUID,groupUID],{connection})
    }
    // get the already existing filters for this event
    const filters=await query(`SELECT ObjectBase.UID, ObjectBase.Type, ObjectBase.UIDBelongsTo
        FROM ObjectBase 
        INNER JOIN Links ON (Links.UID=ObjectBase.UID AND Links.Type ='list') 
        WHERE ObjectBase.Type IN ('visible','changeable') AND Links.UIDTarget=?`,
        [UIDevent],{connection}
    )


    // for the changeable filter
    for(const share of template.shares)
    {
        const shareGroups=allGroups
            .filter(g=>g.hierarchie===share.hierarchie) // filter the groups with the given hierarchie
            .filter(g=>!filters.find(f=>(f.UIDBelongsTo.equals(g.UID) && f.Type==='changeable')))  // only groups which do not have a changeable filter
        if(shareGroups.length)
        {
            //console.log('filter',shareGroups)
            // add filter to group for extraction of the changeable objects and pointing to the event
            await query(`
                INSERT INTO ObjectBase(UID,Type,UIDBelongsTo,Title,Display,SortName, FullTextIndex, dindex,Data)
                VALUES (?,'changeable',?,?,?,'changeable','',0,?)
                `,
               
                shareGroups.map(g=>([g.UIDchangeable, g.UID, template.name,g.Title,JSON.stringify(share.Data)])),
                {batch:true,connection}
                
            )
            await query(`INSERT IGNORE INTO Links (UID,Type,UIDTarget) 
                VALUES(?,'list',?)`,shareGroups.map(g=>([g.UIDchangeable,UIDevent])),
                {batch:true,connection})
           /* // now put the initial execution  of the filters into the queue. This will generate as well the filter link
            queueAddArray(req,shareGroups.map(g=>({
                type:'changeable',
                UID: g.UIDchangeable,
                UIDBelongsTo: g.UID,
                oldTarget: null,
                newTarget: UIDevent,
            })))*/

            // now execute the filters
            for( const source of shareGroups )
            {
               
                await visibilityList(source.UIDchangeable,UIDevent,connection)
            }
        }
    }
    for(const share of template.sharesRead)
    {
      
        const shareGroups=allGroups
            .filter(g=>g.hierarchie===share.hierarchie) // filter the groups with the given hierarchie
            .filter(g=>!filters.find(f=>(f.UIDBelongsTo.equals(g.UID) && f.Type==='visible')))  // only groups which do not have a changeable filter
        if(shareGroups.length)
        {

            // add filter to group for extraction of the changeable objects and pointing to the event
            await query(`
                INSERT INTO ObjectBase(UID,Type,UIDBelongsTo,Title,Display,SortName, FullTextIndex, dindex,Data)
                VALUES (?,'changeable',?,?,?,'changeable','',0,?)
                `,
                
                shareGroups.map(g=>([g.UIDvisible, g.UID, template.name,g.Title,JSON.stringify(share.Data)])),
                {batch:true,connection}
                
            )
            await query(`INSERT IGNORE INTO Links (UID,Type,UIDTarget) 
                VALUES(?,'list',?)`,shareGroups.map(g=>([g.UIDvisible,UIDevent])),
            {batch:true,connection})
            // now put the initial execution  of the filters into the queue. This will generate as well the filter link
            /*queueAddArray(req,shareGroups.map(g=>({
                type:'visible',
                UID: g.UIDvisible,
                UIDBelongsTo: g.UID,
                oldTarget: null,
                newTarget: UIDevent,
            })))*/
            for( const source of shareGroups )
            {
                await visibilityList(source.UIDvisible,UIDevent,connection)
            }
        }
    }

 }

/**
 * Rebuild event visibility from scratch
 * @param {Buffer} UIDevent - Event UID
 * @param {Object} req - Request object with session data
 * @param {Object|null} connection - Optional database connection for transaction
 */
export const rebuildEventVisibility=async(UIDevent,req, connection=null)=>
{

    const visibilityTransaction=async (con)=>
    {
        // remove all visibility
        await con.query(`DELETE FROM Visible WHERE UID=?`,[UIDevent])
        // delete all visiblity filter 
        await con.query(`
            DELETE filter,Links
            FROM Links 
            LEFT JOIN ObjectBase AS filter ON (Links.UID=filter.UID AND filter.Type IN ('changeable','visible'))
              WHERE Links.UIDTarget=? AND Links.Type='list' `,[UIDevent])

        // rebuild group visibility
        const [rgroup]=await con.query('SELECT UIDBelongsTo FROM ObjectBase WHERE UID=?',[UIDevent]) // get the responsible group

        const groups= await con.query(`SELECT ObjectBase.UIDBelongsTo AS UID FROM ObjectBase
            INNER JOIN Links ON (Links.UID=ObjectBase.UID AND Links.Type='memberA' )
            WHERE ObjectBase.Type='gentry' AND Links.UIDTarget=? `,[UIDevent])
        await addGroupVisibility(UIDevent,[rgroup.UIDBelongsTo,...groups.map(g=>g.UID)],req,con)
        // add visibility to all jobs 
        await con.query(`INSERT INTO Visible (UID,Type,UIDUser)
            SELECT ? ,IF(Links.Type='memberA','admin','changeable'),job.UIDBelongsTo
            FROM ObjectBase AS job 
            INNER JOIN Links ON (Links.UID=job.UID AND Links.Type IN ('member','memberA'))
            WHERE Links.UIDTarget=? AND job.Type='eventJob'
            GROUP BY job.UIDBelongsTo`,[UIDevent,UIDevent])

    }
    if(connection)
    {
        // use existing transaction
        await visibilityTransaction(connection)
    }
    else
    {
        await transaction(async (con)=>
        {
            await visibilityTransaction(con)
        },{beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;`)})
    }
}

/**
 * Rebuild event access completely
 * Deletes all visibility filters and visibilities, then rebuilds them
 * @param {Buffer} UIDevent - Event UID
 * @param {Object} req - Request object with session data
 */
export const rebuildEventAccess=async (UIDevent,req)=>
{
    // we do a complete veisibility rebuild for this list
    // this means: we delete all visibiliy filter and all visibilities for this event
    // we then add the visibility to the person who entered the event
    // we then a add the filters, like it would be a new event
    // of course we do all taht in a transactionso that only the final reuslt gets committed
    await transaction(async (connection)=>{
        // delete all filters and there links
        await query(`DELETE ObjectBase,Links FROM Links
            INNER JOIN ObjectBase ON (ObjectBase.UID=Links.UID AND ObjectBase.Type IN ('visible','changeable'))
            WHERE Links.Type = 'list' AND Links.UIDTarget=? `,
            [UIDevent],{connection})
        // delete all visibility for this event
        await query(`DELETE FROM Visible WHERE UID=?`,[UIDevent],{connection})
        // add the visibility for all having a job in this event, theones with mamberA loink have admin rights
        await query(`INSERT INTO Visible (UID,Type,UIDUser)
            SELECT ?, IF(Links.Type='memberA','admin','changeable'),ObjectBase.UIDBelongsTo
            FROM ObjectBase 
            INNER JOIN Links ON (Links.UID=ObjectBase.UID AND Links.Type IN ('member','memberA'))
            WHERE ObjectBase.Type='eventJob' AND Links.UIDTarget=?
            `,
            [UIDevent,UIDevent],
            {connection, log:false}
        )
        // add the group vsibility according to the filters from the template for the participating groups (type gentry) or administratrive group (type group)
        // get all participating groups
        const eventGroups=await query(`SELECT  ObjectBase.UIDBelongsTo AS UID   FROM 
            ObjectBase 
            INNER JOIN Links ON(Links.UID=ObjectBase.UID AND Links.Type='memberA')
            WHERE ObjectBase.Type ='gentry' AND Links.UIDTarget=?`,
            [UIDevent], {log:false})
        // and the administrative group
        const eventGroups2=await query(`SELECT  ObjectBase.UID  FROM 
            ObjectBase 
            INNER JOIN Links ON(Links.UIDTarget=ObjectBase.UID AND Links.Type='memberA')
            WHERE ObjectBase.Type ='group' AND Links.UID=?`,
            [UIDevent], {log:false})
        if(eventGroups.length>0 || eventGroups2.length>0 )
            await addGroupVisibility(UIDevent,[...eventGroups.map(g=>g.UID),...eventGroups2.map(g=>g.UID)],req,connection)
        // now it should be done...
        // when the transaction closes, the new visibility will become active, until than still the old one was active
    },
    {beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;`)})

}