/** * @module autoAddWatchers * @description * This module handles automatically adding watchers to new jobs based on the notifications_autoadd * boolean field in the associations table and the notification_followers JSON field in the bodyshops table. * It ensures users are not added twice and logs the process. */ const { client: gqlClient } = require("../graphql-client/graphql-client"); const queries = require("../graphql-client/queries"); const { isEmpty } = require("lodash"); // If true, the user who commits the action will NOT receive notifications; if false, they will. const FILTER_SELF_FROM_WATCHERS = process.env?.FILTER_SELF_FROM_WATCHERS !== "false"; /** * Adds watchers to a new job based on notifications_autoadd and notification_followers. * * @param {Object} req - The request object containing event data and logger. * @returns {Promise} Resolves when watchers are added or if no action is needed. * @throws {Error} If critical data (e.g., jobId, shopId) is missing. */ const autoAddWatchers = async (req) => { const { event, trigger } = req.body; const { logger } = req; // Validate that this is an INSERT event if (trigger?.name !== "notifications_jobs_autoadd" || event.op !== "INSERT" || event.data.old) { logger.log("Invalid event for auto-add watchers, skipping", "info", "notifications"); return; } const jobId = event?.data?.new?.id; const shopId = event?.data?.new?.shopid; const roNumber = event?.data?.new?.ro_number || "unknown"; if (!jobId || !shopId) { throw new Error(`Missing jobId (${jobId}) or shopId (${shopId}) for auto-add watchers`); } const hasuraUserRole = event?.session_variables?.["x-hasura-role"]; const hasuraUserId = event?.session_variables?.["x-hasura-user-id"]; try { // Fetch auto-add users and notification followers const autoAddData = await gqlClient.request(queries.GET_AUTOADD_NOTIFICATION_USERS, { shopId }); // Get users with notifications_autoadd: true const autoAddUsers = autoAddData?.associations?.map((assoc) => ({ email: assoc.useremail, associationId: assoc.id })) || []; // Get users from notification_followers (array of association IDs) const notificationFollowers = autoAddData?.bodyshops_by_pk?.notification_followers || []; let followerEmails = []; if (notificationFollowers.length > 0) { // Fetch associations for notification_followers const followerAssociations = await gqlClient.request(queries.GET_NOTIFICATION_ASSOCIATIONS, { emails: [], // Filter by association IDs shopid: shopId }); followerEmails = followerAssociations.associations .filter((assoc) => notificationFollowers.includes(assoc.id)) .map((assoc) => ({ email: assoc.useremail, associationId: assoc.id })); } // Combine and deduplicate emails (use email as the unique key) const usersToAdd = [...autoAddUsers, ...followerEmails].reduce((acc, user) => { if (!acc.some((u) => u.email === user.email)) { acc.push(user); } return acc; }, []); if (isEmpty(usersToAdd)) { logger.log(`No users to auto-add for jobId "${jobId}" (RO: ${roNumber})`, "info", "notifications"); return; } // Check existing watchers to avoid duplicates const existingWatchersData = await gqlClient.request(queries.GET_JOB_WATCHERS, { jobid: jobId }); const existingWatcherEmails = existingWatchersData?.job_watchers?.map((w) => w.user_email) || []; // Filter out already existing watchers and optionally the user who created the job const newWatchers = usersToAdd .filter((user) => !existingWatcherEmails.includes(user.email)) .filter((user) => { if (FILTER_SELF_FROM_WATCHERS && hasuraUserRole === "user") { // Fetch user email for hasuraUserId to compare const userData = existingWatchersData?.job_watchers?.find((w) => w.user?.authid === hasuraUserId); return userData ? user.email !== userData.user_email : true; } return true; }) .map((user) => ({ jobid: jobId, user_email: user.email })); if (isEmpty(newWatchers)) { logger.log( `No new watchers to add after filtering for jobId "${jobId}" (RO: ${roNumber})`, "info", "notifications" ); return; } // Insert new watchers await gqlClient.request(queries.INSERT_JOB_WATCHERS, { watchers: newWatchers }); logger.log( `Added ${newWatchers.length} auto-add watchers for jobId "${jobId}" (RO: ${roNumber})`, "info", "notifications", null, { addedEmails: newWatchers.map((w) => w.user_email) } ); } catch (error) { logger.log("Error adding auto-add watchers", "error", "notifications", null, { message: error?.message, stack: error?.stack, jobId, roNumber }); throw error; // Re-throw to ensure the error is logged in the handler } }; module.exports = { autoAddWatchers };