/**
* 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' });
}
};