Source: Router/email/controller.js

// @ts-check
import './../../types.js';

/**
 * Email Controller
 * 
 * Contains all the business logic for email operations:
 * - Creating and updating emails
 * - Managing email recipients (persons/entries)
 * - Handling email status changes and filtering
 * - Managing email sharing and admin permissions
 * - Retrieving email lists and statistics
 * 
 * Emails are complex objects that can have multiple recipients, different statuses,
 * and support filtering, sharing, and administrative features.
 */

import { query, UUID2hex, HEX2uuid } from '@commtool/sql-query';
import { renderObject } from '../../utils/renderTemplates.js';
import { Templates } from '../../utils/compileTemplates.js';
import { getUID, isValidUID } from '../../utils/UUIDs.js';
import { addUpdateEntry, addUpdateList } from '../../server.ws.js';
import { publishEvent } from '../../utils/events.js';
import { isObjectAdmin, isAdmin, isListAdmin, checkVisible, checkObjectAdmin } from '../../utils/authChecks.js';
import { deleteVisibility } from '../../utils/listVisibilty.js';
import { putShare, deleteShare, getSharesAll, getShares, getShared, getShare } from '../list.js';
import { paginateList } from '../../utils/paginateList.js';
import { matchObject } from '../../utils/objectfilter/filters/index.js';
import mysqlTime from '../../utils/mysqlTime.js';
import { errorLoggerRead, errorLoggerUpdate } from '../../utils/requestLogger.js';
import _ from 'lodash';

/**
 * Create or update an email
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const createOrUpdateEmail = async (req, res) => {
    try {
        let ownerUID = UUID2hex(req.session.user);
        if (req.query.user && isObjectAdmin(req, req.query.user)) {
            ownerUID = UUID2hex(req.query.user);
        }
        const senderUID = UUID2hex(req.params.sender);
        const UID = await getUID(req);
        req.body.status = !req.body.status ? 'prepare' : req.body.status;
        const template = Templates[req.session.root].email;
        const object = await renderObject(template, req.body, req);
        object.Data = req.body;

        const resSender = await query(`SELECT ObjectBase.UID, Member.Display, Member.Data, ObjectBase.Title 
            FROM ObjectBase 
            INNER JOIN Member ON (Member.UID=ObjectBase.UID)
            WHERE ObjectBase.UID=? AND ObjectBase.Type='group'`, [senderUID], { cast: ['UUID', 'json'] });
        
        if (resSender.length === 0) {
            res.json({ success: false, message: 'invalid sender group UID' });
            return;
        }
        const sender = resSender[0];

        const [email] = await query(`SELECT Member.Data,ObjectBase.dindex FROM ObjectBase
            INNER JOIN Member ON (Member.UID=ObjectBase.UID)
            WHERE ObjectBase.UID=? AND ObjectBase.Type='email'`, [UID], { cast: ['json'] });

        if (!email) {
            // Create new email
            await query(`INSERT INTO ObjectBase(UID,UIDuser,Type,UIDBelongsTo,Title,dindex,Data)
                VALUES (?,'email',?,?,?,?,?)`, 
                [UID, ownerUID, object.Title, object.dindex, JSON.stringify(object.Data)]);
            await query(`INSERT INTO Member(UID,Display,SortName,FullTextIndex,Data)
                VALUES (?,?,?,?,?)`, 
                [UID, object.Display, object.SortIndex, object.FullTextIndex, JSON.stringify(object.Data)]);
            await query(`INSERT INTO Links(UID, Type, UIDTarget) VALUES (?,'memberA',?)`, [UID, senderUID]);
            await query(`INSERT INTO Links(UID, Type, UIDTarget) VALUES (?,'member',?)`, [UID, ownerUID]);
        } else {
            // Update existing email
            await query(`UPDATE ObjectBase,Member SET 
                ObjectBase.Title=?,Member.Display=?,Member.SortName=?,Member.FullTextIndex=?,ObjectBase.dindex=?,Member.Data=?
                WHERE ObjectBase.UID=? AND ObjectBase.UID=Member.UID`, 
                [object.Title, object.Display, object.SortIndex, object.FullTextIndex, object.dindex, JSON.stringify(object.Data), UID]);

            const oldData = _.cloneDeep(email.Data);
            
            // Check if sender update required
            const oldSender = await query(`SELECT ObjectBase.UID, Member.Display,Member.Data, ObjectBase.Title 
                FROM ObjectBase 
                INNER JOIN Member ON (Member.UID=ObjectBase.UID)
                INNER JOIN Links ON (Links.UIDTarget=ObjectBase.UID AND Links.Type='memberA')
                WHERE Links.UID=?`, [UID], { cast: ['UUID', 'json'] });

            if (oldSender.length === 0 || oldSender[0].UID !== sender.UID) {
                await query(`DELETE FROM Links WHERE UID=? AND Type='memberA'`, [UID]);
                await query(`INSERT INTO Links(UID, Type, UIDTarget) VALUES (?,'memberA',?)`, [UID, senderUID]);
            }

            // Update email owner if changed
            const oldOwner = await query(`SELECT ObjectBase.UID,Member.Display, Member.Data, ObjectBase.Title 
                FROM ObjectBase 
                INNER JOIN Member ON (Member.UID=ObjectBase.UID)
                INNER JOIN Links ON (Links.UIDTarget=ObjectBase.UID AND Links.Type='member')
                WHERE Links.UID=?`, [UID], { cast: ['UUID', 'json'] });

            if (oldOwner.length === 0 || oldOwner[0].UID !== ownerUID) {
                await query(`DELETE FROM Links WHERE UID=? AND Type='member'`, [UID]);
                await query(`INSERT INTO Links(UID, Type, UIDTarget) VALUES (?,'member',?)`, [UID, ownerUID]);
            }

            const resOwner = await query(`SELECT Member.UID, Member.Display, Member.Data, ObjectBase.Title 
                FROM ObjectBase 
                INNER JOIN Member ON (ObjectBase.UIDBelongsTo = Member.UID)
                INNER JOIN Links ON (Links.UIDTarget=ObjectBase.UID AND Links.Type='member')
                WHERE Links.UID=?`, [UID]);
            const owner = resOwner[0];

            object.admin = await isListAdmin(req, UID);
            object.writeable = await isObjectAdmin(req, UID);
            addUpdateEntry(UID, { Data: { ...object, UID: HEX2uuid(object.UID), owner: sender, member: owner }, committed: true });

            if (req.body.status && oldData.status !== req.body.status) {
                publishEvent(`/state/email/${req.body.status}`, {
                    organization: req.session.root,
                    data: HEX2uuid(UID)
                });
            }
        }

        res.json({ success: true, result: { ...object, UID: HEX2uuid(object.UID) } });
    } catch (e) {
        errorLoggerUpdate(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Update email data
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const updateEmail = async (req, res) => {
    try {
        const updateMessage = { committed: true };
        const emailUID = UUID2hex(req.params.UID);
        const result = await query(`SELECT Member.Data FROM ObjectBase INNER Join Member ON (Member.UID=ObjectBase.UID) 
            WHERE ObjectBase.Type='email' AND ObjectBase.UID=?`, [emailUID]);
        
        if (result.length !== 1) {
            res.json({ success: false, message: 'email with this UID does not exist' });
            return;
        }

        const oldData = JSON.parse(result[0].Data);
        let reset = req.body.reset;
        if (req.body.reset) {
            req.body.status = 'prepare';
            delete req.body.reset;
        }

        const data = { ...oldData, ...req.body };
        const template = Templates[req.session.root].email;
        const object = await renderObject(template, data, req);
        
        await query(`UPDATE ObjectBase,Member
            SET ObjectBase.Title=?,Member.Display=?,ObjectBase.dindex=?, Member.Data=? 
            WHERE ObjectBase.UID=? AND ObjectBase.UID=Member.UID`,
            [object.Title, object.Display, object.dindex, JSON.stringify(data), emailUID]);

        if (!req.query.autoSave || req.query.autoSave === 'false') {
            updateMessage.Data = { Data: data };
        }

        object.admin = await isListAdmin(req, req.params.UID);
        object.writeable = await isObjectAdmin(req, req.params.UID);

        if (data.status === 'sending' && oldData.status !== 'prepare') {
            res.json({ success: true, message: 'you can only change from status prepare into status sending' });
            return;
        }

        if (reset && data.status === 'prepare') {
            await query(`UPDATE ObjectBase AS entry INNER JOIN Links ON (entry.UID=Links.UID AND Links.Type IN('member','memberA'))
                SET entry.Data=?,entry.dindex=0
                WHERE Links.UIDTarget=? AND entry.Type='entry'`, [JSON.stringify({ status: 'added' }), emailUID]);
            updateMessage.reset = true;
        }

        if (!_.isEqual(oldData, data)) {
            publishEvent(`/change/email/${HEX2uuid(emailUID)}`, {
                organization: req.session.root,
                data: {}
            });
        }

        if (oldData.status !== data.status) {
            publishEvent('/state/email/' + data.status, {
                organization: req.session.root,
                data: HEX2uuid(emailUID)
            });
        }

        addUpdateEntry(emailUID, updateMessage);
        res.json({ success: true, result: { ...object, UID: HEX2uuid(object.UID) } });
    } catch (e) {
        errorLoggerUpdate(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Delete an email
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const deleteEmail = async (req, res) => {
    try {
        const UID = UUID2hex(req.params.UID);
        const [email] = await query(`SELECT ObjectBase.UIDBelongsTo FROM ObjectBase
                WHERE ObjectBase.UID =? AND ObjectBase.Type='email'`, [UID]);
        
        if (email) {
            await query(`DELETE FROM ObjectBase WHERE UID =? AND Type='email'`, [UID]);
            await query(`DELETE FROM Member WHERE UID =?`, [UID]);
            await query(`DELETE Links FROM Links WHERE UID=? AND Type IN ('memberA','member')`, [UID]);
            await query(`DELETE Links,Entry 
                FROM Links 
                INNER JOIN ObjectBase AS Entry ON (Entry.UID=Links.UID AND Links.Type='memberA' AND Entry.Type='entry') 
                WHERE Links.UIDTarget=?`, [UID]);
            deleteVisibility(UID);
            res.json({ success: true });
        } else {
            res.json({ success: false, message: 'email not found' });
        }
    } catch (e) {
        errorLoggerUpdate(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Get pending emails (admin only)
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const getPendingEmails = async (req, res) => {
    try {
        const orga = UUID2hex(req.session.root);
        const result = await query(`SELECT emember.Data AS Data, email.UID, emember.Display, email.dindex,
            Member.Data AS MemberData, Member.UID AS UIDowner, Member.Display AS DisplayMember
            FROM ObjectBase AS email
            INNER JOIN Member AS emember ON (emember.UID=email.UID)
            INNER JOIN Links AS gLink ON (gLink.UID=email.UID AND gLink.Type='memberA')
            INNER JOIN Links AS rLink ON (rLink.UID=email.UID AND rLink.Type='member')
            INNER JOIN Member ON (Member.UID=rLink.UIDTarget)
            LEFT JOIN Links AS oLink ON (oLink.UID=gLink.UIDTarget AND oLink.Type IN ('member','memberA'))
            WHERE (oLink.UIDTarget=? OR gLink.UIDTarget=? ) 
                AND email.Type='email' AND email.dindex=1   ORDER BY email.validFrom`,
            [orga, orga], { cast: ['UUID', 'json'] });

        const newResult = result.map(r => ({
            UID: r.UID,
            Data: { ...r.Data, UID: r.UID },
            Display: r.Display,
            dindex: r.dindex,
            owner: {
                UID: r.UIDowner,
                Data: r.MemberData,
                Display: r.DisplayMember
            }
        }));
        res.json({ success: true, result: newResult });
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Get a specific email
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const getEmail = async (req, res) => {
    try {
        const UID = UUID2hex(req.params.UID);
        let visible = ` AND Visible.UIDUser=U_UUID2BIN('${req.session.user}')`;
        if (await isAdmin(req.session)) {
            visible = '';
        }

        const resEmail = await query(`SELECT ObjectBase.UID,Member.Data,Member.Display,ObjectBase.dindex 
            FROM ObjectBase 
            INNER JOIN Member ON (Member.UID=ObjectBase.UID)
            INNER JOIN Visible ON (Visible.UID=ObjectBase.UID ${visible} )
            WHERE ObjectBase.UID=? AND ObjectBase.Type='email' 
            GROUP BY ObjectBase.UID`, [UID], { cast: ['UUID', 'json'] });

        if (resEmail && resEmail.length > 0) {
            const email = resEmail[0];

            const resOwner = await query(`SELECT Member.UID, Member.Display, Member.Data, ObjectBase.Title 
                FROM ObjectBase 
                INNER JOIN Member ON (ObjectBase.UIDBelongsTo = Member.UID)
                INNER JOIN Links ON (Links.UIDTarget=ObjectBase.UID AND Links.Type='member')
                WHERE Links.UID=?`, [UID], { cast: ['UUID', 'json'] });
            const owner = resOwner[0];

            const resSender = await query(`SELECT ObjectBase.UID, Member.Display, Member.Data, ObjectBase.Title 
                FROM ObjectBase 
                INNER JOIN Member ON (Member.UID=ObjectBase.UID)
                INNER JOIN Links ON (Links.UIDTarget=ObjectBase.UID AND Links.Type='memberA')
                WHERE Links.UID=?`, [UID], { cast: ['UUID', 'json'] });
            const sender = resSender[0];

            email.admin = await isListAdmin(req, UID);
            email.writeable = await isObjectAdmin(req, UID);
            res.json({ success: true, result: { ...email, owner: sender, member: owner } });
        } else {
            res.json({ success: false, message: 'E-Mail not found or user not authorized to view this E-Mail' });
        }
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Helper function to add persons to an email
 * @param {Array|string} adding - UIDs to add
 * @param {string} UIDemail - Email UID
 * @param {Object} emailData - Email data
 * @param {boolean} noUpdate - Skip update notifications
 * @param {string} addStatus - Status for added entries
 * @param {Buffer} UIDuser - User UID
 * @param {string} organization - Organization UID for multi-tenant context
 * @returns {Promise<Object>} Result object
 */
export const addPersons = async (
    /** @type {Array|string|Object} */ adding, 
    /** @type {Buffer} */ UIDemail, 
    /** @type {Object} */ emailData, 
    /** @type {boolean} */ noUpdate, 
    /** @type {string} */ addStatus = 'added', 
    /** @type {Buffer} */ UIDuser, 
    /** @type {string} */ organization
) => {
    let adds;
    let bodyErrors = [];

    if (Array.isArray(adding)) {
        adds = adding.map(el => {
            try {
                if (typeof (el) === 'string')
                    return UUID2hex(el);
                else if (el.UID)
                    return UUID2hex(el.UID);
                else
                    return null;
            } catch (e) { bodyErrors.push(e); }
        });
    } else {
        try {
            if (typeof (adding) === 'string')
                adds = [UUID2hex(adding)];
            else if (adding && typeof adding === 'object' && adding.UID)
                adds = [UUID2hex(adding.UID)];
        } catch (e) { bodyErrors.push(e); }
    }

    if (bodyErrors.length > 0) {
        return { success: false, message: 'invalid body supplied', errors: bodyErrors };
    }

    const persons = await query(`SELECT Main.UID AS UID, Member.Data AS MemberData, 
            UIDV1() AS UIDNewEntry,ObjectBase.UID AS ObjectUID,Main.Title
            FROM ObjectBase AS Main 
            INNER JOIN  ObjectBase ON (ObjectBase.UIDBelongsTo= Main.UID) 
            INNER JOIN Member ON (Member.UID=Main.UIDBelongsTo)
            LEFT JOIN (ObjectBase AS Entry INNER JOIN Links ON (Entry.UID=Links.UID AND Links.Type='memberA' AND Links.UIDTarget=?))
            ON (Entry.UIDBelongsTo= Main.UID)
            WHERE ObjectBase.UID IN (?) AND Main.Type IN ('person','extern') AND Entry.UID IS NULL`,
        [UIDemail, adds], { log: true, cast: ['json'] });

    const toBeAdded = persons.map(p => ({ ...p, Data: { status: addStatus, address: null } }));
    
    if (toBeAdded.length) {
        const insertParas = toBeAdded.map(a => ([a.UIDNewEntry, UIDuser, 'entry', a.UID, a.Title, 0, a.Data]));
        
        await query(`INSERT INTO ObjectBase(UID,UIDuser,Type,UIDBelongsTo,Title,dindex,Data)
            VALUES (?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE Data=VALUE(Data), Title=VALUE(Title)`,
            insertParas, { batch: true });
        
        await query(`INSERT IGNORE INTO Links(UID, Type, UIDTarget, UIDuser) VALUES (?,'memberA',?,?)`,
            toBeAdded.map(a => ([a.UIDNewEntry, UIDemail, UIDuser])), { batch: true });
        
        if (!noUpdate) {
            addUpdateList(HEX2uuid(UIDemail));
        }
        
        publishEvent(`/add/email/entry/${HEX2uuid(UIDemail)}`, {
            organization: organization,
            data: toBeAdded.map(a => HEX2uuid(a.UIDNewEntry))
        });
    }
    
    return { success: true, result: { added: toBeAdded.map(p => ({ ...p, UID: HEX2uuid(p.UID), UIDNewEntry: HEX2uuid(p.UIDNewEntry), ObjectUID: HEX2uuid(p.ObjectUID) })) } };
};

/**
 * Add persons to email
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const addPersonsToEmail = async (req, res) => {
    try {
        const addStatus = req.query.status ? (['added', 'sent', 'error'].includes(req.query.status) ? req.query.status : 'added') : 'added';
        const UIDemail = UUID2hex(req.params.UIDemail);
        const result = await query(`SELECT Data,UID,Type FROM ObjectBase WHERE ObjectBase.UID=? AND Type ='email'`, [UIDemail]);
        
        if (result.length === 0) {
            res.json({ success: false, message: `invalid email UID supplied` });
            return;
        }
        
        res.json(await addPersons(req.body, UIDemail, JSON.parse(result[0].Data), req.query.noUpdate, addStatus, UUID2hex(req.session.user), req.session.root));
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Update person status in email
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const updatePersonStatus = async (req, res) => {
    try {
        const UIDemail = UUID2hex(req.params.UIDemail);
        const entryStates = ['added', 'sent', 'error'];
        
        if (!entryStates.includes(req.body.status)) {
            res.status(400).json({ success: false, message: 'illegal status' });
            return;
        }
        
        await query(`UPDATE ObjectBase AS email
            INNER JOIN Links ON (Links.UIDTarget=email.UID)
            INNER JOIN ObjectBase AS entry ON (Links.UID=entry.UID AND Links.Type IN ('member' ,'memberA') AND entry.Type='entry')
            SET entry.Data=?,entry.dindex=?
            WHERE email.UID=? AND email.Type ='email' AND entry.UIDBelongsTo=?`,
            [JSON.stringify(req.body), entryStates.indexOf(req.body.status), UIDemail, UUID2hex(req.params.UIDperson)]);
        
        addUpdateEntry(req.params.UIDemail, { personStatus: { Data: req.body, UIDperson: req.params.UIDperson } });
        res.json({ success: true });
    } catch (e) {
        errorLoggerUpdate(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Remove persons from email
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const removePersonsFromEmail = async (req, res) => {
    try {
        const UIDemail = UUID2hex(req.params.UIDemail);
        const result = await query(`SELECT Data,UID,Type FROM ObjectBase WHERE ObjectBase.UID=? AND Type ='email'`, [UIDemail]);
        
        if (result.length === 0) {
            res.json({ success: false, message: `invalid email UID supplied` });
            return;
        }

        let deletes;
        let bodyErrors = [];
        const deleting = req.body;

        if (Array.isArray(deleting)) {
            deletes = deleting.map(el => {
                try {
                    if (typeof (el) === 'string')
                        return UUID2hex(el);
                    else if (el.UID)
                        return UUID2hex(el.UID);
                    else
                        return null;
                } catch (e) { bodyErrors.push(e); }
            });
        } else {
            try {
                if (typeof (deleting) === 'string')
                    deletes = [UUID2hex(deleting)];
                else if (deleting.UID)
                    deletes = [UUID2hex(deleting.UID)];
            } catch (e) { bodyErrors.push(e); }
        }

        if (bodyErrors.length > 0) {
            res.json({ success: false, message: 'invalid body supplied', errors: bodyErrors });
            return;
        }

        const persons = await query(`SELECT  Entry.UIDBelongsTo AS UID, Entry.UID AS UIDentry
                FROM ObjectBase AS Entry INNER JOIN Links ON (Entry.UID=Links.UID AND Links.Type IN ('member','memberA','member0') )
                WHERE Links.UIDTarget=? AND (Entry.UIDBelongsTo IN (?) OR Entry.UID IN (?)) AND Entry.Type ='entry'`,
            [UIDemail, deletes, deletes], { log: false, cast: ['json', 'UUID'] });

        await query(`DELETE Entry,Links 
            FROM ObjectBase AS Entry INNER JOIN Links ON (Entry.UID=Links.UID AND Links.Type IN ('member','memberA','member0') )
            WHERE Links.UIDTarget=? AND (Entry.UIDBelongsTo IN (?) OR Entry.UID IN (?)) AND Entry.Type ='entry'`, [UIDemail, deletes, deletes]);

        addUpdateList(HEX2uuid(UIDemail));
        publishEvent(`/remove/email/entry${HEX2uuid(UIDemail)}`, {
            organization: req.session.root,
            data: persons.map(p => p.UIDentry)
        });
        res.json({ success: true, result: { deleted: persons } });
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Get email listing (persons in email)
 * @param {Object} req - Express request object
 * @returns {Promise<Array>} List of persons
 */
export const getListing = async (req) => {
    if (!isValidUID(req.params.UID)) {
        throw new Error('you have to supply an UID');
    }
    
    const UID = UUID2hex(req.params.UID);
    const time = req.query.Timestamp ? `FOR SYSTEM_TIME AS OF TIMESTAMP'${mysqlTime(req.query.Timestamp)}'` : '';

    const result = await query(`
        SELECT Entry.UIDBelongsTo AS UID,  ObjectBase.Title,  Entry.Data, Entry.UID AS UIDEntry,Member.Data AS MemberData,Member.Display
        FROM ObjectBase AS Entry 
        INNER JOIN ObjectBase ON (ObjectBase.UIDBelongsTo=Entry.UIDBelongsTo AND ObjectBase.Type IN ('person','extern')) 
        INNER JOIN ObjectBase AS OVisible ON(OVisible.UIDBelongsTo=Entry.UIDBelongsTo AND OVisible.Type IN ('person','job','guest','extern') )
        INNER JOIN Visible ON (Visible.UID=OVisible.UID)
        INNER JOIN Member ON (Member.UID=ObjectBase.UID)
        INNER JOIN Links ON (Links.UID=Entry.UID AND Links.Type='memberA') 
        WHERE Links.UIDTarget=?  
        AND Visible.UIDUser=?
        AND Entry.Type='entry'
        GROUP BY Entry.UID
        ORDER BY JSON_VALUE(Member.Data,'$.lastName'),JSON_VALUE(Member.Data,'$.firstName')
    `, [UID, UUID2hex(req.session.user)], { log: false, cast: ['json', 'UUID'] });

    return result;
};

/**
 * Get persons in email (with pagination support)
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const getEmailPersons = async (req, res) => {
    try {
        // if we want the data paginated- the user has supplied the __page parameter
        if (!req.query.__page) {
            // normal api request
            const result = await getListing(req);
            res.json({ success: true, result: result });
            return;
        }
        // paginated api request
        res.json(await paginateList(req, getListing));
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Get emails for a person
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const getEmailsForPerson = async (req, res) => {
    try {
        const UIDperson = UUID2hex(req.params.UID);
        const user = UUID2hex(req.session.user);
        const result = await query(`SELECT ObjectBase.UID, ObjectBase.Title, ObjectBase.Display, ObjectBase.Type FROM ObjectBase 
            INNER JOIN Links ON (Links.UIDTarget=ObjectBase.UID AND Links.Type IN ('member','memberA'))
            INNER JOIN ObjectBase AS Entry ON (Entry.UID=Links.UID)
            INNER JOIN Visible ON (Visible.UID=ObjectBase.UID)
            WHERE ObjectBase.Type ='email' AND Entry.UIDBelongsTo=? AND Visible.UIDUser=? 
            GROUP BY ObjectBase.UID`, [UIDperson, user], { cast: ['UUID', 'json'] });
        res.json({ success: true, result });
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Apply filter to email
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const applyFilterToEmail = async (req, res) => {
    try {
        const emailUID = UUID2hex(req.params.email);
        const sourceUID = UUID2hex(req.params.source);
        
        if (!isObjectAdmin(req, emailUID)) {
            res.json({ success: false, message: 'user not authorized for this E-Mail' });
            return;
        }

        const sources = await query(`SELECT Data,Type FROM ObjectBase WHERE UID=?`, [sourceUID]);
        const addStatus = req.query.status ? (['added', 'sent', 'error'].includes(req.query.status) ? req.query.status : 'added') : 'added';
        
        if (sources.length === 0) {
            res.json({ success: false, message: 'filter source not found' });
            return;
        }

        const emails = await query(`SELECT Member.Data,ObjectBase.Type 
            FROM ObjectBase 
            INNER JOIN Member ON (Member.UID=ObjectBase.UID)
            WHERE ObjectBase.UID=? AND ObjectBase.Type='email'`, [emailUID]);
        
        if (emails.length === 0) {
            res.json({ success: false, message: 'email not found' });
            return;
        }

        const filter = req.body;
        const types = [...Object.keys(filter), 'entry'];
        const all = await query(`SELECT ObjectBase.Type, ObjectBase.UIDBelongsTo AS UID,ObjectBase.Data AS ExtraData,Member.Data, MainBase.Data AS MainBaseData,
                                ObjectBase.Title,ObjectBase.UIDBelongsTo,Member.Display,Member.SortName,Entry.UID AS UIDEntry, UIDV1() AS UIDnew
                        FROM ObjectBase
                        INNER JOIN ObjectBase AS MainBase ON (MainBase.UID=ObjectBase.UIDBelongsTo AND MainBase.Type IN('person','extern'))
                        INNER JOIN Member AS Member ON (Member.UID=MainBase.UID)
                        INNER JOIN Links
                        ON (ObjectBase.UID = Links.UID AND Links.Type IN ('member','memberA') AND ObjectBase.Type NOT IN ('filter','list'))

                        LEFT JOIN (
                            SELECT Entry.UID, Entry.UIDBelongsTo FROM ObjectBase AS Entry INNER JOIN Links AS ELink
                            ON (Entry.UID=ELink.UID)
                            AND ELink.UIDTarget=? AND Entry.Type='entry'
                        ) AS Entry ON (Entry.UIDBelongsTo=ObjectBase.UIDBelongsTo)
                        INNER JOIN Visible ON (Visible.UID=MainBase.UID)

                        WHERE Links.UIDTarget=?  AND ObjectBase.Type IN (?) AND Visible.UIDUser=?`,
            [emailUID, sourceUID, types, UUID2hex(req.session.user)], {
            cast: ['json'],
            log: false,
            filter: Object.keys(filter).length ?
                (row) => (matchObject({ ...row, Type: row.Type === 'entry' ? 'person' : row.Type }, filter)) : null,
            group: (result, current) => {
                const index = result.findIndex(el => el.UIDBelongsTo.equals(current.UIDBelongsTo));
                if (index >= 0) {
                    return result;
                }
                return [...result, current];
            }
        });

        let toBeAdded = all.filter(o => o.UIDEntry === null);
        
        if (toBeAdded.length) {
            await query(`INSERT INTO ObjectBase (UID,Type,UIDBelongsTo,Title,Data) VALUES (?,'entry',?,?,?)`,
                toBeAdded.map(o => ([o.UIDnew, o.UID, o.Title, JSON.stringify({ status: addStatus, address: null })])), { batch: true });

            await query(`INSERT IGNORE INTO Links (UID,Type,UIDTarget) VALUES (?,'memberA',?)`,
                toBeAdded.map(o => [o.UIDnew, emailUID]), { batch: true });
            
            addUpdateEntry(emailUID, { reloadList: true });
        }
        
        res.json({ success: true });
    } catch (e) {
        errorLoggerUpdate(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Check if user is email admin
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const checkEmailAdmin = async (req, res) => {
    try {
        const admin = await isListAdmin(req, req.params.UID);
        res.json({ success: true, result: admin });
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Share email (delegate to list share function)
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const shareEmail = (req, res) => putShare(req, res, 'email');

/**
 * Delete email share (delegate to list share function)
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const deleteEmailShare = (req, res) => deleteShare(req, res, 'email');

/**
 * Get all email shares (delegate to list share function)
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const getAllEmailShares = (req, res) => getSharesAll(req, res, 'email');

/**
 * Get email shares for specific list (delegate to list share function)
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const getEmailShares = (req, res) => getShares(req, res, 'email');

/**
 * Get shared email data (delegate to list shared function)
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const getSharedEmailData = async (req, res) => {
    try {
        // if we want the data paginated- the user has supplied the __page parameter
        if (!req.query.__page) {
            // normal api request
            const result = await getListing(req);
            res.json({ success: true, result: result });
            return;
        }
        // paginated api request
        res.json(await paginateList(req, getShared));
    } catch (e) {
        errorLoggerRead(e);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
};

/**
 * Get email share details (delegate to list share function)
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
export const getEmailShare = getShare;