const eventParser = require("./eventParser"); const { client: gqlClient } = require("../../graphql-client/graphql-client"); const queries = require("../../graphql-client/queries"); const { isEmpty, isFunction } = require("lodash"); const { getMatchingScenarios } = require("./scenarioMapperr"); const scenarioParser = async (req) => { // Destructure required fields from the request body const { event, trigger, table } = req.body; if (!event?.data || !trigger || !table) { throw new Error("Missing required request fields: event data, trigger, or table."); } // Step 1: Parse the changes from the event const eventData = await eventParser({ newData: event.data.new, oldData: event.data.old, trigger, table, jobIdField: `req.body.event.new.jobid` }); // Step 2: Query jobWatchers for this job const watcherData = await gqlClient.request(queries.GET_JOB_WATCHERS, { jobid: eventData.jobId }); const jobWatchers = watcherData?.job_watchers_aggregate?.nodes?.map((watcher) => watcher.user_email); if (isEmpty(jobWatchers)) return; // Step 3: Infer bodyshop information from the job and validate const bodyShopId = watcherData?.job?.bodyshop?.id; const bodyShopName = watcherData?.job?.bodyshop?.shopname; if (!bodyShopId || !bodyShopName) { throw new Error("No bodyshop data found for this job."); } // Step 4: Get matching scenarios based on eventData and jobWatchers const matchingScenarios = getMatchingScenarios({ ...eventData, jobWatchers, bodyShopId, bodyShopName }); if (isEmpty(matchingScenarios)) return; // Prepare the final scenario data const finalScenarioData = { ...eventData, jobWatchers, bodyShopId, bodyShopName, matchingScenarios }; // Step 5: Query associations (notification_settings) for each watcher // Filter by both useremail and shopid const associationsData = await gqlClient.request(queries.GET_NOTIFICATION_ASSOCIATIONS, { emails: jobWatchers, shopid: bodyShopId }); if (isEmpty(associationsData?.associations)) return; // Step 6: For each matching scenario, add a scenarioWatchers property // that includes only the jobWatchers with at least one notification method enabled. // Each watcher object is formatted as: { user, email, app, fcm } finalScenarioData.matchingScenarios.forEach((scenario) => { scenario.scenarioWatchers = associationsData.associations .filter((assoc) => { // Retrieve the settings object for this scenario (it now contains app, email, and fcm) const settings = assoc.notification_settings && assoc.notification_settings[scenario.key]; // Only include this association if at least one notification channel is enabled return settings && (settings.app || settings.email || settings.fcm); }) .map((assoc) => { const settings = assoc.notification_settings[scenario.key]; return { // Use assoc.user if available, otherwise fallback to assoc.useremail as the identifier user: assoc.user || assoc.useremail, // The email field here is the user's email notification setting (boolean) email: settings.email, app: settings.app, fcm: settings.fcm }; }); }); // Step 7: Call builder functions for each matching scenario (fire-and-forget) // Only invoke a builder if its scenario has at least one watcher finalScenarioData.matchingScenarios.forEach((scenario) => { if (!isEmpty(scenario.scenarioWatchers) && isFunction(scenario.builder)) { scenario .builder(finalScenarioData) .catch((error) => console.error(`Error in builder for scenario '${scenario.key}':`, error)); } }); }; module.exports = scenarioParser;