Source: tree/matchObjects/matchObjects.js

import {query, transaction, HEX2uuid } from '@commtool/sql-query'
import { errorLoggerUpdate } from '../../utils/requestLogger.js';
import { loadVisibleScopes, publishVisibilityDiff, resolveVisibilityOrganization } from '../visibilityEvents.js'

import {filterObjects } from '@commtool/object-filter';


// these two functions will match an object or an array of objects against the supplied sources, respectively there filters  and will add to the respective targets
// so you add a person as a new member to a group so you will check all filters belonging to this group, if it matches
// a filter and will then execute the respective filter
// if andRemove= true, we will as well remove all (!) visibilities which do not match the filters of the sources
/**
 * Setzt Sichtbarkeit fuer die Filter, die an `sources` haengen, und **meldet**
 * die Zeilen, die dabei entstehen oder verschwinden.
 *
 * @param {Buffer|Buffer[]} ObjectUID - das Objekt (oder die Objekte), deren Sichtbarkeit gesetzt wird
 * @param {Buffer[]} sources - die Quellen, deren `visible`/`changeable`-Filter greifen sollen
 * @param {{andRemove?: boolean, organization?: string|Buffer|null}} [paras]
 *        `andRemove` loescht zuerst alle Zeilen der Objekte (Vererbung neu rechnen),
 *        `organization` geht in das Event-Payload (ohne sie greift der Rueckfall
 *        in `resolveVisibilityOrganization`).
 * @returns {Promise<void>}
 */
export const addVisibility=async (ObjectUID,sources,paras={andRemove:false})=>
{

    try {

    if(!sources || sources.length===0)
        return
    const {andRemove,organization=null}=paras
    // get all filters with Type 'visible' and 'changeable'
    const includers=await query(`SELECT DISTINCT ObjectBase.UID,ObjectBase.Data,IF(jobs.Type='job',jobs.UIDBelongsTo,jobs.UID) AS userUID, ObjectBase.Type, 
        jobs.Type AS TargetType FROM ObjectBase 
    INNER JOIN Links ON (Links.UID=ObjectBase.UID AND ObjectBase.Type IN ('visible','changeable' ) AND Links.Type='list')
    INNER JOIN ObjectBase AS jobs ON (Links.UIDTarget=jobs.UID)
    WHERE ObjectBase.UIDBelongsTo IN (?) 
    GROUP BY UID
   
     `,[sources],{cast:['json']})
    // return if we do not have filters 
    if(!includers.length)
        return
    let myObjects
        // get Object Data
    if(Array.isArray(ObjectUID))
    {
         myObjects=await query( `SELECT  ObjectBase.UID,Member.UID AS UIDmember,ObjectBase.Type,
            Member.Data AS Data,
            ObjectBase.Data AS ExtraData,
            MainBase.Data AS MainBaseData, ObjectBase.dindex, ObjectBase.Title,Main.Display
            FROM ObjectBase 
            INNER JOIN ObjectBase AS Main ON (Main.UID=ObjectBase.UIDBelongsTo) 
            INNER JOIN ObjectBase AS MainBase ON (MainBase.UID=ObjectBase.UIDBelongsTo AND MainBase.Type IN ('person','extern','group','ggroup'))
            INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo) WHERE ObjectBase.UID IN(?)`,[ObjectUID],{cast:['json']} )
    }
    else
    {
        myObjects=await query( `SELECT  ObjectBase.UID, Member.UID AS UIDmember, ObjectBase.Type,
                    Member.Data AS Data,
                    ObjectBase.Data AS ExtraData,
                    MainBase.Data AS MainBaseData,  ObjectBase.dindex, ObjectBase.Title,Main.Display 
                    FROM ObjectBase 
                    INNER JOIN ObjectBase AS Main ON (Main.UID=ObjectBase.UIDBelongsTo) 
                    INNER JOIN ObjectBase AS MainBase ON (MainBase.UID=ObjectBase.UIDBelongsTo AND MainBase.Type IN ('person','extern','group','ggroup'))
                    INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo) WHERE ObjectBase.UID =? `,[ObjectUID] ,{cast:['json']})

    }
    const DObjects=myObjects.filter(o=>o.Type!=='filter')
    // go through all includers and add the filtered objects, Filters with 0 results will be eliminated
    let toBeAdded=[]
    includers.map(include=>
        {
            const filter=include.Data
    
            if(Object.keys(include.Data).length>0)
            {
                if(include.TargetType==='job' || include.TargetType==='extern') // super admin target is extern type!
                   toBeAdded=[...toBeAdded,...filterObjects(DObjects,filter).map(o=>([o.UID,include.Type,include.userUID,include.TargetType]))]
                else
                {
                    
                    // we have now a list,dlist , email , event or eventT so we have three visibility states: visible,changeable, admin
                    const myType = include.Type==='visible' ? 'visible' : filter.admin ? 'admin' : 'changeable'
                    const add=filterObjects(DObjects,filter).map(o=>([include.userUID,myType,o.UIDmember,include.TargetType]))

                   toBeAdded=[...toBeAdded,...add]
                  /* if(include.TargetType !=='job' && add.length>0)

                            console.log(add.map(a=>({target:HEX2uuid(a[0]),type:a[1],'user':HEX2uuid(a[2]), targetType: include.TargetType})))*/

                }
            }
            else 
            {
                if(include.TargetType==='job' || include.TargetType==='extern') // super admin target is extern type!
                {
                    toBeAdded=[...toBeAdded, ...DObjects.map(o=>([o.UID,include.Type,include.userUID,include.TargetType]))]
                }
                else
                {
                    toBeAdded=[...toBeAdded, ...DObjects.map(o=>([include.userUID,include.Type,o.UIDmember,include.TargetType]))]
                    
                }
            }
        })
    // now, if we have remaining includers with filtered data, we are going to add visiblity for all jobs
    
    if(toBeAdded.length)
    {
            // Der Vorher-Stand: nur die Zeilen, die dieser Lauf ueberhaupt
            // anfassen kann. `andRemove` loescht alles zu `DObjects`, sonst wird
            // ausschliesslich ueber die Paare aus `toBeAdded` geschrieben — mehr
            // zu lesen waere teurer, nicht genauer.
            const affected=[...DObjects.map(o=>o.UID),...toBeAdded.map(el=>el[0])]
            const before=await loadVisibleScopes(affected)

            let  oldVisibility
            if(andRemove && toBeAdded.length>0)
            {

                // we are performing this in a transaction to  not have conflict with other queries

                await transaction(async(connection)=>
                {
                  /*  oldVisibility=await connection.query(`SELECT Visible.UID, Visible.Type, Visible.UIDUser, ObjectBase.Type AS ObjectType
                    FROM Visible INNER JOIN ObjectBase ON (ObjectBase.UID=Visible.UID) 
                    WHERE Visible.UID IN(?) AND ObjectBase.Type IN ('person','guest','extern','job')
                        `,[DObjects.map(o=>o.UID)])*/
                    await connection.query(`DELETE FROM Visible WHERE UID IN (?)`,DObjects.map(o=>o.UID))
                    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[0],el[1],el[2],])) ,
                    {batch:true})
                   

                },
                {
                    // avoid dead locks in Visible Table
                    beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;`)
                })
                
               
            }
            else if(toBeAdded.length>0)
            {
                await transaction(async(connection)=>
                {
                
                    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[0],el[1],el[2]])),{batch:true})
                },
                {
                    beforeTransaction:async (connection)=> await connection.query(`SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;`)
                })
            }           

            // Bis hierher war dieser Pfad stumm: die Zeile entstand, das Event
            // fehlte. Genau daran hing die Projektion auf dem toten Ast — eine
            // Rechte-Aenderung im Betrieb kam nie an ([057 §16]).
            const after=await loadVisibleScopes(affected)
            publishVisibilityDiff(before,after,organization ?? await resolveVisibilityOrganization(affected))
        }
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }
}