Source: Router/filter/controller.js

/**
 * Filter Controller
 * 
 * Contains all the business logic for filter operations:
 * - Add filters to lists (include, exclude, intersect)
 * - Remove filters from lists
 * - Delete specific filters
 * - Get filter details
 * - Get all filters
 * 
 * Handles dynamic list filtering with groups and lists as sources.
 * Integrates with the tree queue system for updates and notifications.
 */

// @ts-check
/**
 * @import {ExpressRequestAuthorized, ExpressResponse} from './../../types.js'
 */


import { query, UUID2hex, HEX2uuid } from '@commtool/sql-query';
import { renderObject } from '../../utils/renderTemplates.js';
import { Templates } from '../../utils/compileTemplates.js';
import { getUID } from '../../utils/UUIDs.js';
import { queueAdd } from '../../tree/treeQueue/treeQueue.js';
import { addUpdateEntry, addUpdateList } from '../../server.ws.js';
import { isObjectAdmin } from '../../utils/authChecks.js';
import { filterWithTime } from '../../utils/objectfilter/filters/withTime.js';
import { getTokenConnection } from '../../utils/transaction.js';
import { addFilter as addFilterBackend, removeFilter as removeFilterBackend } from '../../tree/executeFilters/executeFilters.js';
import { errorLoggerRead, errorLoggerUpdate } from '../../utils/requestLogger.js';

/**
 * Add a filter to a list
 * @param {ExpressRequestAuthorized} req - Express request object
 * @returns {Promise<Object>} Result object with success status and UID
 */
export const addFilter = async (req) => {
    try {
        const connection = getTokenConnection(req);
        const targetUID = UUID2hex(req.params.target);
        const filterUID = await getUID(req);
        const sourceUID = UUID2hex(req.params.source);
        
        const sources = await connection.query(`SELECT Member.Data,ObjectBase.Type,ObjectBase.Title,Member.Display 
            FROM ObjectBase INNER JOIN Member ON (Member.UID=ObjectBase.UID) WHERE ObjectBase.UID=? `, [sourceUID]);
            
        if (!['include', 'exclude', 'intersect'].includes(req.params.type)) {
            return { success: false, message: 'illegal filter type' };
        }
        
        if (!sources || sources.length === 0) {
            return { success: false, message: 'filter source not found' };
        }

        const source = sources[0];
        if (source.Type !== 'group' && source.Type !== 'list') {
            return { success: false, message: 'only lists or groups can be a dynamic list source' };
        }

        let filterData = req.body;
        if (source.Type === 'list') {
            // if we have a list as a source, we do not allow further filtering for simplicity
            filterData = { person: { all: null }, extern: { all: null } };
            if (req.body.name) {
                filterData.name = req.body.name;
            }
        }
        
        const template = Templates[req.session.root].filter;
        const object = await renderObject(template, { type: req.body.type, ...req.body }, req);
        const [target] = await connection.query(`SELECT Data,Type FROM ObjectBase WHERE UID=? `, [targetUID]);

        if (!target) {
            return { success: false, message: 'list not found' };
        }

        // we set dindex =1, if this filter has to be updated dayly as it contains a birthday filter
        const dindex = filterWithTime(filterData);

        await connection.query(`
            INSERT INTO ObjectBase(UID,Type,UIDBelongsTo,Title,Display,SortName, FullTextIndex, dindex,Data)
            VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE Title=VALUE(Title), Display=VALUE(Display), 
                SortName=VALUE(SortName), Data=VALUE(Data)
            `,
            [
                filterUID, req.params.type, sourceUID, object.Title, object.Display, object.SortIndex, object.FullTextIndex, dindex, JSON.stringify({ ...filterData, UID: undefined })
            ]
        );
        
        // now perform the linking and add/remove the members to/from the list and 
        await addFilterBackend(filterUID, targetUID, req.params.type, req.session.root, { connection });
        //queueAdd(UUID2hex(req.session.root),UUID2hex(req.session.user),req.params.type,filterUID,sourceUID,null,targetUID)
        addUpdateEntry(targetUID, { filterAdded: { UID: HEX2uuid(filterUID), UIDBelongsTo: req.params.source, Data: req.body, Type: req.params.type, UIDTarget: req.params.target, Title: source.Title, Display: source.Display } });
        addUpdateList(targetUID);
        return { success: true, UID: filterUID };
    } catch (e) {
        errorLoggerUpdate(e);
        throw e;
    }
};



/**
 * Create or update filter (PUT /:source/:target/:type)
 * * @param {ExpressRequestAuthorized} req - Express request object
 * * @param {ExpressResponse} res - Express response object
 * 
 */
export const createOrUpdateFilter = async (req, res) => {
    try {
        const result = await addFilter(req);
        res.json(result);
    } catch (e) {
        errorLoggerUpdate(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Delete filters by source, target and type
 * @param {ExpressRequestAuthorized} req - Express request object
 * @returns {Promise<Object>} Result object with success status
 */
export const deleteFilter = async (req) => {
    try {
        const connection = getTokenConnection(req);
        if (!['include', 'exclude', 'intersect'].includes(req.params.type)) {
            return { success: false, message: 'illegal filter type' };
        }
        
        const sourceUID = UUID2hex(req.params.source);
        const sources = await connection.query(`SELECT Data,Type FROM ObjectBase WHERE UID=? `, [sourceUID]);
        if (sources.length === 0) {
            return { success: false, message: 'filter source not found' };
        }
        
        const targetUID = UUID2hex(req.params.target);
        const targets = await connection.query(`SELECT Data,Type FROM ObjectBase WHERE UID=? `, [targetUID]);
        if (!targets.length) {
            return { success: false, message: 'target not found' };
        }

        // get matching filters
        const filters = await connection.query(`SELECT Filter.UID,Filter.Type FROM 
                ObjectBase AS Filter 
                INNER JOIN Links ON (Links.UID=Filter.UID) 
                WHERE Filter.UIDBelongsTo=? AND Filter.Type=? AND Links.UIDTarget=? AND Links.Type='list'
                `, [sourceUID, req.params.type, targetUID]);
                
        for (const filter of filters) {
            await removeFilterBackend(filter.UID, targetUID,req.session.root, { connection });
            await connection.query(`DELETE FROM ObjectBase WHERE ObjectBase.UID=?`, [filter.UID]);
            // queueAdd(UUID2hex(req.session.root),UUID2hex(req.session.user),filter.Type,filter.UID,sourceUID,targetUID,null)
        }
        
        addUpdateList(targetUID);
        addUpdateEntry(targetUID, { filtersDeleted: filters.map(el => ({ UID: el.UID, Type: el.Type })) });
        return { success: true };
    } catch (e) {
        errorLoggerUpdate(e);
        throw e;
    }
};

/**
 * Delete filters by source, target and type (DELETE /:source/:target/:type)
 * @param {ExpressRequestAuthorized} req - Express request object
 * @param {ExpressResponse} res - Express response object
 */
export const deleteFiltersByType = async (req, res) => {
    try {
        const result = await deleteFilter(req);
        res.json(result);
    } catch (e) {
        errorLoggerUpdate(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Remove a specific filter by UID
 * @param {ExpressRequestAuthorized} req - Express request object
 * @returns {Promise<Object>} Result object with success status
 */
const removeFilter = async (req) => {
    try {
        const connection = getTokenConnection(req);
        const UID = UUID2hex(req.params.UID);
        const [{ UIDTarget: UIDlist }] = await query(`SELECT UIDTarget FROM Links WHERE Type='list' AND UID=? `, [UID]);
        
        if (!isObjectAdmin(req, UIDlist)) {
            return { success: false, message: 'not allowed' };
        } else {
            const [filter] = await connection.query(`SELECT Filter.UID,Filter.UIDBelongsTo, Filter.Type, Links.UIDTarget
                FROM ObjectBase AS Filter INNER JOIN Links ON (Links.UID=Filter.UID AND Links.Type='list') 
                WHERE Filter.UID=? AND Filter.Type IN ('visible','include','exclude','intersect') `, [UID]);
                
            if (filter) {
                await removeFilterBackend(filter.UID, filter.UIDTarget, req.session.root, { connection });
                await connection.query(`DELETE FROM ObjectBase WHERE ObjectBase.UID=?`, [filter.UID]);
                /*await queueAdd(UUID2hex(req.session.root),UUID2hex(req.session.user),
                    filter.Type,filter.UID,filter.UIDBelongsTo,filter.UIDTarget,null)*/
                addUpdateList(filter.UIDTarget);
                addUpdateEntry(filter.UIDTarget, { filtersDeleted: [{ UID: req.params.UID, Type: filter.Type }] });
            }

            return { success: true };
        }
    } catch (e) {
        errorLoggerUpdate(e);
        throw e;
    }
};

/**
 * Delete specific filter by UID (DELETE /:UID)
 * @param {ExpressRequestAuthorized} req - Express request object
 * @param {ExpressResponse} res - Express response object
 */
export const deleteSpecificFilter = async (req, res) => {
    try {
        const result = await removeFilter(req);
        res.json(result);
    } catch (e) {
        errorLoggerUpdate(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Get specific filter by UID (GET /:UID)
 * @param {ExpressRequestAuthorized} req - Express request object
 * @param {ExpressResponse} res - Express response object
 */
export const getFilter = async (req, res) => {
    try {
        const connection = getTokenConnection(req);
        const filters = await query(`SELECT Filter.UID,Filter.UIDBelongsTo, Filter.Data, Filter.Type,
            Links.UIDTarget, Source.Type AS SourceType
            FROM ObjectBase AS Filter INNER JOIN Links ON (Links.UID=Filter.UID AND Links.Type='list')
            INNER JOIN ObjectBase AS Source ON (Source.UID=Filter.UIDBelongsTo) 
            WHERE Filter.UID=? AND Filter.Type IN ('include','exclude','intersect') `,
            [UUID2hex(req.params.UID)],
            { cast: ['UUID', 'json'], connection });
            
        res.json({ success: true, result: filters[0] });
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Get all filters (GET /)
 * @param {ExpressRequestAuthorized} req - Express request object
 * @param {ExpressResponse} res - Express response object
 */
export const getAllFilters = async (req, res) => {
    try {
        const connection = await getTokenConnection(req);
        const filters = await connection.query(`SELECT Filter.UID,Filter.UIDBelongsTo, Filter.Data, Filter.Type,

            Links.UIDTarget, Source.Type AS SourceType
            FROM ObjectBase AS Filter INNER JOIN Links ON (Links.UID=Filter.UID AND Links.Type='list')
            INNER JOIN ObjectBase AS Source ON (Source.UID=Filter.UIDBelongsTo) 
            WHERE Filter.Type IN ('include','exclude','intersect') `,
            [],
            { cast: ['UUID', 'json'] });

        res.json({ success: true, result: filters });
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};