/** * @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. * * NOTE: Bodyshop notification_followers is fetched directly from the DB (Hasura) to avoid stale Redis cache. */ const { client: gqlClient } = require("../graphql-client/graphql-client"); const { isEmpty } = require("lodash"); const { GET_BODYSHOP_WATCHERS_BY_ID, GET_JOB_WATCHERS_MINIMAL, GET_NOTIFICATION_WATCHERS, INSERT_JOB_WATCHERS } = require("../graphql-client/queries"); // 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, bail if (trigger?.name !== "notifications_jobs_autoadd" || event.op !== "INSERT" || event.data.old) { return; } const jobId = event?.data?.new?.id; const shopId = event?.data?.new?.shopid; const roNumber = event?.data?.new?.ro_number || "unknown"; const createdUserEmail = event?.data?.new?.created_user_email || "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 bodyshop data directly from DB (avoid Redis staleness) const bodyshopResponse = await gqlClient.request(GET_BODYSHOP_WATCHERS_BY_ID, { id: shopId }); const bodyshopData = bodyshopResponse?.bodyshops_by_pk; const notificationFollowersRaw = bodyshopData?.notification_followers; const notificationFollowers = Array.isArray(notificationFollowersRaw) ? [...new Set(notificationFollowersRaw.filter((id) => id))] // de-dupe + remove falsy : []; // Execute queries in parallel const [notificationData, existingWatchersData] = await Promise.all([ gqlClient.request(GET_NOTIFICATION_WATCHERS, { shopId, employeeIds: notificationFollowers, createdUserEmail }), gqlClient.request(GET_JOB_WATCHERS_MINIMAL, { jobid: jobId }) ]); // Get users with notifications_autoadd: true const autoAddUsers = notificationData?.associations?.map((assoc) => ({ email: assoc.useremail, associationId: assoc.id })) || []; // Get users from notification_followers (employee IDs -> employee emails) const followerEmails = notificationData?.employees ?.filter((e) => e.user_email) ?.map((e) => ({ email: e.user_email, associationId: null })) || []; // Combine and deduplicate emails (use email as the unique key) const usersToAdd = [...autoAddUsers, ...followerEmails].reduce((acc, user) => { if (user?.email && !acc.some((u) => u.email === user.email)) { acc.push(user); } return acc; }, []); if (isEmpty(usersToAdd)) { return; } // Check existing watchers to avoid duplicates 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") { 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)) { return; } // Insert new watchers await gqlClient.request(INSERT_JOB_WATCHERS, { watchers: newWatchers }); } catch (error) { logger.log("Error adding auto-add watchers", "error", "notifications", null, { message: error?.message, stack: error?.stack, jobId, shopId, roNumber }); throw error; // Re-throw to ensure the error is logged in the handler } }; module.exports = { autoAddWatchers };