From 2d97dd6a018fefb2c198b6dd3e5d023a591b8597 Mon Sep 17 00:00:00 2001 From: Ganeshswaminathan K Date: Mon, 13 Oct 2025 22:10:11 -0500 Subject: [PATCH 1/6] Seperate docker container for cron jobs --- backend/app.js | 9 - backend/cron.js | 10 + backend/package.json | 1 + backend/workers/createRecurringEvents.js | 83 +++--- docker-compose.yml | 17 ++ yarn.lock | 347 ----------------------- 6 files changed, 70 insertions(+), 397 deletions(-) create mode 100644 backend/cron.js diff --git a/backend/app.js b/backend/app.js index af91fb691..6c797f8f8 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2,8 +2,6 @@ // Load in all of our node modules. Their uses are explained below as they are called. const express = require('express'); -const cron = require('node-cron'); -const fetch = require('node-fetch'); const morgan = require('morgan'); const cookieParser = require('cookie-parser'); @@ -51,13 +49,6 @@ app.use(cookieParser()); // HTTP Request Logger app.use(morgan('dev')); -// WORKERS -const runOpenCheckinWorker = require('./workers/openCheckins')(cron, fetch); -const runCloseCheckinWorker = require('./workers/closeCheckins')(cron, fetch); - -const { createRecurringEvents } = require('./workers/createRecurringEvents'); -const runCreateRecurringEventsWorker = createRecurringEvents(cron, fetch); - // const runSlackBot = require("./workers/slackbot")(fetch); // MIDDLEWARE diff --git a/backend/cron.js b/backend/cron.js new file mode 100644 index 000000000..42ebe5b23 --- /dev/null +++ b/backend/cron.js @@ -0,0 +1,10 @@ +/* eslint-disable no-unused-vars */ +const cron = require('node-cron'); +const fetch = require('node-fetch'); + +// WORKERS +const runOpenCheckinWorker = require('./workers/openCheckins')(cron, fetch); +const runCloseCheckinWorker = require('./workers/closeCheckins')(cron, fetch); + +const { createRecurringEvents } = require('./workers/createRecurringEvents'); +const runCreateRecurringEventsWorker = createRecurringEvents(cron, fetch); diff --git a/backend/package.json b/backend/package.json index 73a3826c8..899060e15 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,6 +10,7 @@ "test:watch": "jest --watch", "start": "node server.js", "dev": "nodemon server.js", + "cron": "nodemon cron.js", "client": "npm run start --prefix client", "heroku-postbuild": "cd client && npm install && npm run build", "clone-or-sync-projects": "node ./scripts/cloneOrSyncCollections.js", diff --git a/backend/workers/createRecurringEvents.js b/backend/workers/createRecurringEvents.js index 0516f1b12..8b16d1c2f 100644 --- a/backend/workers/createRecurringEvents.js +++ b/backend/workers/createRecurringEvents.js @@ -1,6 +1,7 @@ const { generateEventData } = require('./lib/generateEventData'); -/** +//API CALLS to GET and POST +/** GET * Utility to fetch data from an API endpoint. * @param {string} endpoint - The API endpoint to fetch data from. * @param {string} URL - The base URL for API requests. @@ -20,6 +21,31 @@ const fetchData = async (endpoint, URL, headerToSend, fetch) => { } }; +/** POST + * Creates a new event by making a POST request to the events API. + * @param {Object} event - The event data to create. + * @returns {Promise} - The created event data or null on failure. + */ +const createEvent = async (event, URL, headerToSend, fetch) => { + if (!event) return null; + + try { + const res = await fetch(`${URL}/api/events/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-customrequired-header': headerToSend, + }, + body: JSON.stringify(event), + }); + if (!res.ok) throw new Error('Failed to create event'); + return await res.json(); + } catch (error) { + console.error('Error creating event:', error); + return null; + } +}; + /** * Checks if two dates are on the same day in UTC. * @param {Date} eventDate - Event date. @@ -47,28 +73,21 @@ const doesEventExist = (recurringEventName, today, events) => }); /** - * Creates a new event by making a POST request to the events API. - * @param {Object} event - The event data to create. - * @returns {Promise} - The created event data or null on failure. + * Adjusts an event date to Los_Angeles time, accounting for DST offsets. + * @param {Date} eventDate - The event date to adjust. + * @returns {Date} - The adjusted event date. */ -const createEvent = async (event, URL, headerToSend, fetch) => { - if (!event) return null; - - try { - const res = await fetch(`${URL}/api/events/`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-customrequired-header': headerToSend, - }, - body: JSON.stringify(event), - }); - if (!res.ok) throw new Error('Failed to create event'); - return await res.json(); - } catch (error) { - console.error('Error creating event:', error); - return null; - } +const adjustToLosAngelesTime = (eventDate) => { + const tempDate = new Date(eventDate); + const losAngelesOffsetHours = new Intl.DateTimeFormat('en-US', { + timeZone: 'America/Los_Angeles', + timeZoneName: 'shortOffset', + }) + .formatToParts(tempDate) + .find((part) => part.type === 'timeZoneName') + .value.slice(3); + const offsetMinutes = parseInt(losAngelesOffsetHours, 10) * 60; + return new Date(tempDate.getTime() + offsetMinutes * 60000); }; /** @@ -111,24 +130,6 @@ const filterAndCreateEvents = async (events, recurringEvents, URL, headerToSend, } }; -/** - * Adjusts an event date to Los_Angeles time, accounting for DST offsets. - * @param {Date} eventDate - The event date to adjust. - * @returns {Date} - The adjusted event date. - */ -const adjustToLosAngelesTime = (eventDate) => { - const tempDate = new Date(eventDate); - const losAngelesOffsetHours = new Intl.DateTimeFormat('en-US', { - timeZone: 'America/Los_Angeles', - timeZoneName: 'shortOffset', - }) - .formatToParts(tempDate) - .find((part) => part.type === 'timeZoneName') - .value.slice(3); - const offsetMinutes = parseInt(losAngelesOffsetHours, 10) * 60; - return new Date(tempDate.getTime() + offsetMinutes * 60000); -}; - /** * Executes the task of fetching existing events and recurring events, * filtering those that should occur today, and creating them if needed. @@ -172,7 +173,7 @@ const createRecurringEvents = (cron, fetch) => { const URL = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' - : `http://localhost:${process.env.BACKEND_PORT}`; + : `http://backend:${process.env.BACKEND_PORT}`; const headerToSend = process.env.CUSTOM_REQUEST_HEADER; return scheduleTask(cron, fetch, URL, headerToSend); diff --git a/docker-compose.yml b/docker-compose.yml index b98a819bf..48b01d037 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,23 @@ services: networks: - gateway + cron: + build: + context: ./backend + dockerfile: Dockerfile.api + target: api-development + command: yarn run cron + volumes: + - ./backend:/srv/backend + - backend_node_modules:/srv/backend/node_modules + restart: on-failure + networks: + - gateway + depends_on: + - backend + env_file: + - ./backend/.env + client: build: context: ./client diff --git a/yarn.lock b/yarn.lock index c98db04f8..0ac712be9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,14 +2,6 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - "@babel/runtime@^7.23.4": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" @@ -61,28 +53,6 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@emnapi/core@^1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.4.3.tgz#9ac52d2d5aea958f67e52c40a065f51de59b77d6" - integrity sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g== - dependencies: - "@emnapi/wasi-threads" "1.0.2" - tslib "^2.4.0" - -"@emnapi/runtime@^1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.3.tgz#c0564665c80dc81c448adac23f9dfbed6c838f7d" - integrity sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ== - dependencies: - tslib "^2.4.0" - -"@emnapi/wasi-threads@1.0.2", "@emnapi/wasi-threads@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz#977f44f844eac7d6c138a415a123818c655f874c" - integrity sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA== - dependencies: - tslib "^2.4.0" - "@emotion/is-prop-valid@1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" @@ -185,45 +155,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== -"@isaacs/fs-minipass@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" - integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== - dependencies: - minipass "^7.0.4" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.24": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@mui/icons-material@^5.14.19": version "5.14.19" resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.14.19.tgz#6e0f4e9d89f99517d3c0ee65dee7ac97753755af" @@ -231,15 +162,6 @@ dependencies: "@babel/runtime" "^7.23.4" -"@napi-rs/wasm-runtime@^0.2.10": - version "0.2.11" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz#192c1610e1625048089ab4e35bc0649ce478500e" - integrity sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA== - dependencies: - "@emnapi/core" "^1.4.3" - "@emnapi/runtime" "^1.4.3" - "@tybys/wasm-util" "^0.9.0" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -268,123 +190,6 @@ dependencies: any-observable "^0.3.0" -"@tailwindcss/node@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.1.10.tgz#7a53a224cdd79a926ed990bbf97c74de9dadf595" - integrity sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ== - dependencies: - "@ampproject/remapping" "^2.3.0" - enhanced-resolve "^5.18.1" - jiti "^2.4.2" - lightningcss "1.30.1" - magic-string "^0.30.17" - source-map-js "^1.2.1" - tailwindcss "4.1.10" - -"@tailwindcss/oxide-android-arm64@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz#ad0f3cbfa219e1ee5fc8ad7170885feda397c4e3" - integrity sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ== - -"@tailwindcss/oxide-darwin-arm64@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz#d8d744f93310b45ce16420a9addd1c4329848929" - integrity sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ== - -"@tailwindcss/oxide-darwin-x64@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz#476490d1f95592a09801a53b48466e5065d7553f" - integrity sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ== - -"@tailwindcss/oxide-freebsd-x64@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz#7b7ccb813592209216ed39187eb8510ce6b4fc9d" - integrity sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g== - -"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz#9f223c7994da846b9f3c70ac0b5713371c9b3b32" - integrity sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ== - -"@tailwindcss/oxide-linux-arm64-gnu@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz#58412e6a359a83144b30b415f637a52c8207f311" - integrity sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA== - -"@tailwindcss/oxide-linux-arm64-musl@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz#3ed868b801a27e8cd35a615855bc94fd2786a6e8" - integrity sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ== - -"@tailwindcss/oxide-linux-x64-gnu@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz#aca15cc4cf9dcd687eda0f5cd2bc1f4bfb485562" - integrity sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA== - -"@tailwindcss/oxide-linux-x64-musl@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz#0c77d1e94e499a9f85c80013e6052dd98d3cfee4" - integrity sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA== - -"@tailwindcss/oxide-wasm32-wasi@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz#6e749424db4f6e076371a66da7c4daf1fcd4f9df" - integrity sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q== - dependencies: - "@emnapi/core" "^1.4.3" - "@emnapi/runtime" "^1.4.3" - "@emnapi/wasi-threads" "^1.0.2" - "@napi-rs/wasm-runtime" "^0.2.10" - "@tybys/wasm-util" "^0.9.0" - tslib "^2.8.0" - -"@tailwindcss/oxide-win32-arm64-msvc@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz#e1663b5a95425f0f458f616399ed9f6707d4a786" - integrity sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA== - -"@tailwindcss/oxide-win32-x64-msvc@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz#de3d4e8b38c31caf2522ad0c6f0efdeb5034fc95" - integrity sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA== - -"@tailwindcss/oxide@4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.1.10.tgz#b8ad6ae678b54bb533c2074092aadebac0a6d8fe" - integrity sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q== - dependencies: - detect-libc "^2.0.4" - tar "^7.4.3" - optionalDependencies: - "@tailwindcss/oxide-android-arm64" "4.1.10" - "@tailwindcss/oxide-darwin-arm64" "4.1.10" - "@tailwindcss/oxide-darwin-x64" "4.1.10" - "@tailwindcss/oxide-freebsd-x64" "4.1.10" - "@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.10" - "@tailwindcss/oxide-linux-arm64-gnu" "4.1.10" - "@tailwindcss/oxide-linux-arm64-musl" "4.1.10" - "@tailwindcss/oxide-linux-x64-gnu" "4.1.10" - "@tailwindcss/oxide-linux-x64-musl" "4.1.10" - "@tailwindcss/oxide-wasm32-wasi" "4.1.10" - "@tailwindcss/oxide-win32-arm64-msvc" "4.1.10" - "@tailwindcss/oxide-win32-x64-msvc" "4.1.10" - -"@tailwindcss/vite@^4.1.10": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@tailwindcss/vite/-/vite-4.1.10.tgz#9ffa396a3f85d31f53eeaa4bac33eb0286bc955d" - integrity sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A== - dependencies: - "@tailwindcss/node" "4.1.10" - "@tailwindcss/oxide" "4.1.10" - tailwindcss "4.1.10" - -"@tybys/wasm-util@^0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355" - integrity sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw== - dependencies: - tslib "^2.4.0" - "@types/sinonjs__fake-timers@^6.0.1": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" @@ -1441,11 +1246,6 @@ check-more-types@2.24.0, check-more-types@^2.24.0: resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= -chownr@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" - integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -1817,11 +1617,6 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" -detect-libc@^2.0.3, detect-libc@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" - integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -1898,14 +1693,6 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.18.1: - version "5.18.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf" - integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2522,11 +2309,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.2.4: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -2960,11 +2742,6 @@ iterator.prototype@^1.1.4: reflect.getprototypeof "^1.0.8" set-function-name "^2.0.2" -jiti@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" - integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== - joi@^17.1.1: version "17.2.1" resolved "https://registry.yarnpkg.com/joi/-/joi-17.2.1.tgz#e5140fdf07e8fecf9bc977c2832d1bdb1e3f2a0a" @@ -3092,74 +2869,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lightningcss-darwin-arm64@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz#3d47ce5e221b9567c703950edf2529ca4a3700ae" - integrity sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ== - -lightningcss-darwin-x64@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz#e81105d3fd6330860c15fe860f64d39cff5fbd22" - integrity sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA== - -lightningcss-freebsd-x64@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz#a0e732031083ff9d625c5db021d09eb085af8be4" - integrity sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig== - -lightningcss-linux-arm-gnueabihf@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz#1f5ecca6095528ddb649f9304ba2560c72474908" - integrity sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q== - -lightningcss-linux-arm64-gnu@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz#eee7799726103bffff1e88993df726f6911ec009" - integrity sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw== - -lightningcss-linux-arm64-musl@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz#f2e4b53f42892feeef8f620cbb889f7c064a7dfe" - integrity sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ== - -lightningcss-linux-x64-gnu@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz#2fc7096224bc000ebb97eea94aea248c5b0eb157" - integrity sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw== - -lightningcss-linux-x64-musl@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz#66dca2b159fd819ea832c44895d07e5b31d75f26" - integrity sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ== - -lightningcss-win32-arm64-msvc@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz#7d8110a19d7c2d22bfdf2f2bb8be68e7d1b69039" - integrity sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA== - -lightningcss-win32-x64-msvc@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz#fd7dd008ea98494b85d24b4bea016793f2e0e352" - integrity sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg== - -lightningcss@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.30.1.tgz#78e979c2d595bfcb90d2a8c0eb632fe6c5bfed5d" - integrity sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg== - dependencies: - detect-libc "^2.0.3" - optionalDependencies: - lightningcss-darwin-arm64 "1.30.1" - lightningcss-darwin-x64 "1.30.1" - lightningcss-freebsd-x64 "1.30.1" - lightningcss-linux-arm-gnueabihf "1.30.1" - lightningcss-linux-arm64-gnu "1.30.1" - lightningcss-linux-arm64-musl "1.30.1" - lightningcss-linux-x64-gnu "1.30.1" - lightningcss-linux-x64-musl "1.30.1" - lightningcss-win32-arm64-msvc "1.30.1" - lightningcss-win32-x64-msvc "1.30.1" - listr-silent-renderer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" @@ -3272,13 +2981,6 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" -magic-string@^0.30.17: - version "0.30.17" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" - integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" @@ -3335,18 +3037,6 @@ minimist@^1.1.3, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -minipass@^7.0.4, minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -minizlib@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.2.tgz#f33d638eb279f664439aa38dc5f91607468cb574" - integrity sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA== - dependencies: - minipass "^7.1.2" - mkdirp@^0.5.1, mkdirp@^0.5.4: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -3354,11 +3044,6 @@ mkdirp@^0.5.1, mkdirp@^0.5.4: dependencies: minimist "^1.2.5" -mkdirp@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" - integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== - moment@^2.27.0: version "2.29.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" @@ -4411,28 +4096,6 @@ symbol-observable@^1.1.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== -tailwindcss@4.1.10: - version "4.1.10" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.10.tgz#515741b0a79316d1971d182f7fbc435b68679373" - integrity sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA== - -tapable@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.2.tgz#ab4984340d30cb9989a490032f086dbb8b56d872" - integrity sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg== - -tar@^7.4.3: - version "7.4.3" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" - integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== - dependencies: - "@isaacs/fs-minipass" "^4.0.0" - chownr "^3.0.0" - minipass "^7.1.2" - minizlib "^3.0.1" - mkdirp "^3.0.1" - yallist "^5.0.0" - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -4488,11 +4151,6 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.4.0, tslib@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -4751,11 +4409,6 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" - integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== - yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" From 4d8a9926e0006e57e382d003adb9a8b4d3050712 Mon Sep 17 00:00:00 2001 From: Ganesh Swaminathan K <64898189+Ganeshswaminathan1912@users.noreply.github.com> Date: Sat, 25 Oct 2025 15:29:45 -0500 Subject: [PATCH 2/6] Time comparison logic fix in recurring events --- backend/workers/createRecurringEvents.js | 68 +++++++++++++++++------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/backend/workers/createRecurringEvents.js b/backend/workers/createRecurringEvents.js index 8b16d1c2f..02785aae3 100644 --- a/backend/workers/createRecurringEvents.js +++ b/backend/workers/createRecurringEvents.js @@ -103,30 +103,56 @@ const adjustToLosAngelesTime = (eventDate) => { const filterAndCreateEvents = async (events, recurringEvents, URL, headerToSend, fetch) => { const today = new Date(); const todayUTCDay = today.getUTCDay(); - // filter recurring events for today and not already existing - const eventsToCreate = recurringEvents.filter((recurringEvent) => { + const allLocalDays = []; + const check = []; + const exist = []; + // // filter recurring events for today and not already existing + const eventsToCreate = recurringEvents?.filter((recurringEvent) => { // we're converting the stored UTC event date to local time to compare the system DOW with the event DOW - const localEventDate = adjustToLosAngelesTime(recurringEvent.date); + const localEventDate = new Date(recurringEvent.date); + allLocalDays.push(localEventDate.getUTCDay()); + check.push(localEventDate.getUTCDay() === todayUTCDay); + exist.push(!doesEventExist(recurringEvent.name, today, events)); return ( localEventDate.getUTCDay() === todayUTCDay && !doesEventExist(recurringEvent.name, today, events) ); }); + console.log( + 'Event date\n', + today, + '\nToday\n', + todayUTCDay, + '\nAll days\n', + allLocalDays, + '\nDay vs All Days comparison (Bool)\n', + check, + '\nEvent exist or not (Bool)\n', + exist, + '\nList of events to create\n', + eventsToCreate, + ); - for (const event of eventsToCreate) { - // convert to local time for DST correction... - const correctedStartTime = adjustToLosAngelesTime(event.startTime); - const timeCorrectedEvent = { - ...event, - // ... then back to UTC for DB - date: correctedStartTime.toISOString(), - startTime: correctedStartTime.toISOString(), - }; - // map/generate all event data with adjusted date, startTime - const eventToCreate = generateEventData(timeCorrectedEvent); + //Check if event exists + if (!eventsToCreate || eventsToCreate?.length === 0) { + return 'No events for today.'; + } else { + for (const event of eventsToCreate) { + // convert to local time for DST correction... + const correctedStartTime = adjustToLosAngelesTime(event.startTime); + const timeCorrectedEvent = { + ...event, + // ... then back to UTC for DB + date: correctedStartTime.toISOString(), + startTime: correctedStartTime.toISOString(), + }; + // map/generate all event data with adjusted date, startTime + const eventToCreate = generateEventData(timeCorrectedEvent); - const createdEvent = await createEvent(eventToCreate, URL, headerToSend, fetch); - if (createdEvent) console.log('Created event:', createdEvent); + const createdEvent = await createEvent(eventToCreate, URL, headerToSend, fetch); + if (createdEvent) console.log('Created event:', createdEvent); + } + return "Today's events have been created."; } }; @@ -145,8 +171,14 @@ const runTask = async (fetch, URL, headerToSend) => { fetchData('/api/recurringevents/', URL, headerToSend, fetch), ]); - await filterAndCreateEvents(events, recurringEvents, URL, headerToSend, fetch); - console.log("Today's events have been created."); + const checkAndCreateEvents = await filterAndCreateEvents( + events, + recurringEvents, + URL, + headerToSend, + fetch, + ); + console.log(checkAndCreateEvents); }; /** From 837216b1fcdc8331f9062a4e84d1f5fdf59c1fe3 Mon Sep 17 00:00:00 2001 From: Ganesh Swaminathan K <64898189+Ganeshswaminathan1912@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:44:31 -0600 Subject: [PATCH 3/6] Optimized api requests for recurring events creation and for open and close checkins added - avoiding memory crash --- backend/app.js | 12 ++ backend/controllers/event.controller.js | 14 +- backend/cron.js | 10 -- backend/package.json | 1 - backend/routers/events.router.js | 8 +- backend/workers/closeCheckins.js | 177 ++++++++++++----------- backend/workers/createRecurringEvents.js | 55 +++---- backend/workers/openCheckins.js | 154 +++++++++++--------- docker-compose.yml | 17 --- 9 files changed, 240 insertions(+), 208 deletions(-) delete mode 100644 backend/cron.js diff --git a/backend/app.js b/backend/app.js index 6c797f8f8..1c302e05a 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,8 +1,11 @@ + // app.js - Entry point for our application // Load in all of our node modules. Their uses are explained below as they are called. const express = require('express'); const morgan = require('morgan'); +const cron = require('node-cron'); +const fetch = require('node-fetch'); const cookieParser = require('cookie-parser'); const customRequestHeaderName = 'x-customrequired-header'; @@ -49,6 +52,15 @@ app.use(cookieParser()); // HTTP Request Logger app.use(morgan('dev')); +// WORKERS +const runOpenCheckinWorker = require('./workers/openCheckins'); +runOpenCheckinWorker(cron, fetch); + +const runCloseCheckinWorker = require('./workers/closeCheckins'); +runCloseCheckinWorker(cron, fetch); + +const { createRecurringEvents } = require('./workers/createRecurringEvents'); +createRecurringEvents(cron, fetch); // const runSlackBot = require("./workers/slackbot")(fetch); // MIDDLEWARE diff --git a/backend/controllers/event.controller.js b/backend/controllers/event.controller.js index e0e52af2d..8fc7d0e38 100644 --- a/backend/controllers/event.controller.js +++ b/backend/controllers/event.controller.js @@ -9,6 +9,7 @@ EventController.event_list = async function (req, res) { const events = await Event.find(query).populate('project'); return res.status(200).send(events); } catch (err) { + console.error('[event.controller]', err); return res.sendStatus(400); } }; @@ -20,6 +21,7 @@ EventController.event_by_id = async function (req, res) { const events = await Event.findById(EventId).populate('project'); return res.status(200).send(events); } catch (err) { + console.error('[event.controller]', err); return res.sendStatus(400); } }; @@ -28,9 +30,15 @@ EventController.create = async function (req, res) { const { body } = req; try { - const event = await Event.create(body); - return res.status(201).send(event); + if (Array.isArray(body)) { + const events = await Event.insertMany(body); + return res.status(201).send(events); + } else { + const event = await Event.create(body); + return res.status(201).send(event); + } } catch (err) { + console.error('[event.controller]', err); return res.sendStatus(400); } }; @@ -42,6 +50,7 @@ EventController.destroy = async function (req, res) { const event = await Event.findByIdAndDelete(EventId); return res.status(200).send(event); } catch (err) { + console.error('[event.controller]', err); return res.sendStatus(400); } }; @@ -53,6 +62,7 @@ EventController.update = async function (req, res) { const event = await Event.findByIdAndUpdate(EventId, req.body); return res.status(200).send(event); } catch (err) { + console.error('[event.controller]', err); return res.sendStatus(400); } }; diff --git a/backend/cron.js b/backend/cron.js deleted file mode 100644 index 42ebe5b23..000000000 --- a/backend/cron.js +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable no-unused-vars */ -const cron = require('node-cron'); -const fetch = require('node-fetch'); - -// WORKERS -const runOpenCheckinWorker = require('./workers/openCheckins')(cron, fetch); -const runCloseCheckinWorker = require('./workers/closeCheckins')(cron, fetch); - -const { createRecurringEvents } = require('./workers/createRecurringEvents'); -const runCreateRecurringEventsWorker = createRecurringEvents(cron, fetch); diff --git a/backend/package.json b/backend/package.json index 899060e15..73a3826c8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,7 +10,6 @@ "test:watch": "jest --watch", "start": "node server.js", "dev": "nodemon server.js", - "cron": "nodemon cron.js", "client": "npm run start --prefix client", "heroku-postbuild": "cd client && npm install && npm run build", "clone-or-sync-projects": "node ./scripts/cloneOrSyncCollections.js", diff --git a/backend/routers/events.router.js b/backend/routers/events.router.js index 7614b2012..7a81e1938 100644 --- a/backend/routers/events.router.js +++ b/backend/routers/events.router.js @@ -1,4 +1,4 @@ -const express = require("express"); +const express = require('express'); const router = express.Router(); const { Event } = require('../models/event.model'); @@ -13,12 +13,14 @@ router.get('/:EventId', EventController.event_by_id); router.delete('/:EventId', EventController.destroy); +router.patch('/batchUpdate', EventController.update); + router.patch('/:EventId', EventController.update); // TODO: Refactor and remove -router.get("/nexteventbyproject/:id", (req, res) => { +router.get('/nexteventbyproject/:id', (req, res) => { Event.find({ project: req.params.id }) - .populate("project") + .populate('project') .then((events) => { res.status(200).json(events[events.length - 1]); }) diff --git a/backend/workers/closeCheckins.js b/backend/workers/closeCheckins.js index 9d04cd7da..4a5141521 100644 --- a/backend/workers/closeCheckins.js +++ b/backend/workers/closeCheckins.js @@ -1,86 +1,99 @@ module.exports = (cron, fetch) => { + // Check to see if any events are about to start, + // and if so, open their respective check-ins + + const url = + process.env.NODE_ENV === 'prod' + ? 'https://www.vrms.io' + : `http://localhost:${process.env.BACKEND_PORT}`; + const headerToSend = process.env.CUSTOM_REQUEST_HEADER; + + async function fetchEvents() { + try { + const res = await fetch(`${url}/api/events`, { + headers: { + 'x-customrequired-header': headerToSend, + }, + }); + const resJson = await res.json(); + + return resJson; + } catch (error) { + console.log(error); + } + } + + async function updateEvents(eventsToUpdate) { + try { + const res = await fetch(`${url}/api/events/batchUpdate`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'x-customrequired-header': headerToSend, + }, + body: JSON.stringify(eventsToUpdate), + }); + if (!res.ok) throw new Error('Failed to update event'); + return await res.json(); + } catch (error) { + console.error('Error updating event:', error); + return null; + } + } + async function sortAndFilterEvents() { + const events = await fetchEvents(); + + // Get current time and set to date variable + const now = Date.now(); + + // Filter events if event date is after now but before thirty minutes from now + if (events && events.length > 0) { + const sortedEvents = events.filter((event) => { + if (!event.date) { + // handle if event date is null/undefined + // false meaning don't include in sortedEvents + return false; + } + // Calculate three hours from now + const threeHoursFromStartTime = new Date(event.date).getTime() + 10800000; + if (Number.isNaN(threeHoursFromStartTime)) return false; + return now >= threeHoursFromStartTime && event.checkInReady === true; + }); + + // console.log('Sorted events: ', sortedEvents); + return sortedEvents; + } + } + + async function closeCheckins(events) { + if (events && events.length > 0) { + console.log('Closing check-ins'); + // console.log('Closing event: ', event); + const batchEventsToUpdate = events.map((e) => ({ + _id: e._id, + checkInReady: false, + })); + const updatedEvents = await updateEvents(batchEventsToUpdate); + if (updatedEvents) console.log('Updated events:', updatedEvents); + console.log('Check-ins closed'); + } else { + console.log('No open events to close'); + } + } + + async function runTask() { + const eventsToClose = await sortAndFilterEvents().catch((err) => { + console.log(err); + }); - // Check to see if any events are about to start, - // and if so, open their respective check-ins - - const url = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : `http://localhost:${process.env.BACKEND_PORT}`; - const headerToSend = process.env.CUSTOM_REQUEST_HEADER; - - async function fetchEvents() { - try { - const res = await fetch(`${url}/api/events`, { - headers: { - "x-customrequired-header": headerToSend - } - }); - const resJson = await res.json(); - - return resJson; - } catch(error) { - console.log(error); - }; - }; - - async function sortAndFilterEvents() { - const events = await fetchEvents(); - - // Filter events if event date is after now but before thirty minutes from now - if (events && events.length > 0) { - - const sortedEvents = events.filter(event => { - if (!event.date) { - // handle if event date is null/undefined - // false meaning don't include in sortedEvents - return false - } - - const currentTimeISO = new Date().toISOString(); - const threeHoursFromStartTime = new Date(event.date).getTime() + 10800000; - const threeHoursISO = new Date(threeHoursFromStartTime).toISOString(); - - return (currentTimeISO > threeHoursISO) && (event.checkInReady === true); - }); - - // console.log('Sorted events: ', sortedEvents); - return sortedEvents; - }; - }; - - async function closeCheckins(events) { - if(events && events.length > 0) { - events.forEach(async event => { - // console.log('Closing event: ', event); - - await fetch(`${url}/api/events/${event._id}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - "x-customrequired-header": headerToSend - }, - body: JSON.stringify({ checkInReady: false }) - }) - .catch(err => { - console.log(err); - }); - }); - }; - }; - - async function runTask() { - console.log("Closing check-ins"); - - const eventsToClose = await sortAndFilterEvents() - .catch(err => {console.log(err)}); - - await closeCheckins(eventsToClose) - .catch(err => {console.log(err)}); - - console.log("Check-ins closed"); - }; - - const scheduledTask = cron.schedule('*/30 * * * *', () => { - runTask(); + await closeCheckins(eventsToClose).catch((err) => { + console.log(err); }); + } + + const scheduledTask = cron.schedule('*/30 * * * *', () => { + runTask(); + }); - return scheduledTask; -}; \ No newline at end of file + return scheduledTask; +}; diff --git a/backend/workers/createRecurringEvents.js b/backend/workers/createRecurringEvents.js index 02785aae3..01dbad0c5 100644 --- a/backend/workers/createRecurringEvents.js +++ b/backend/workers/createRecurringEvents.js @@ -26,7 +26,7 @@ const fetchData = async (endpoint, URL, headerToSend, fetch) => { * @param {Object} event - The event data to create. * @returns {Promise} - The created event data or null on failure. */ -const createEvent = async (event, URL, headerToSend, fetch) => { +const createEvents = async (event, URL, headerToSend, fetch) => { if (!event) return null; try { @@ -103,40 +103,43 @@ const adjustToLosAngelesTime = (eventDate) => { const filterAndCreateEvents = async (events, recurringEvents, URL, headerToSend, fetch) => { const today = new Date(); const todayUTCDay = today.getUTCDay(); - const allLocalDays = []; - const check = []; - const exist = []; + //2025-11-25 + // const allLocalDays = []; + // const dateCheck = []; + // const eventNameExist = []; // // filter recurring events for today and not already existing const eventsToCreate = recurringEvents?.filter((recurringEvent) => { // we're converting the stored UTC event date to local time to compare the system DOW with the event DOW - const localEventDate = new Date(recurringEvent.date); - allLocalDays.push(localEventDate.getUTCDay()); - check.push(localEventDate.getUTCDay() === todayUTCDay); - exist.push(!doesEventExist(recurringEvent.name, today, events)); + const localEventDate = adjustToLosAngelesTime(recurringEvent.date); + //Logs for checking + // allLocalDays.push(localEventDate.getUTCDay()); + // dateCheck.push(localEventDate.getUTCDay() === todayUTCDay); + // eventNameExist.push(!doesEventExist(recurringEvent.name, today, events)); return ( localEventDate.getUTCDay() === todayUTCDay && !doesEventExist(recurringEvent.name, today, events) ); }); - console.log( - 'Event date\n', - today, - '\nToday\n', - todayUTCDay, - '\nAll days\n', - allLocalDays, - '\nDay vs All Days comparison (Bool)\n', - check, - '\nEvent exist or not (Bool)\n', - exist, - '\nList of events to create\n', - eventsToCreate, - ); + // console.log( + // 'Event date\n', + // today, + // '\nToday\n', + // todayUTCDay, + // '\nAll days\n', + // allLocalDays, + // '\nDay vs All Days comparison (Bool)\n', + // dateCheck, + // '\nEvent exist or not (Bool)\n', + // eventNameExist, + // '\nList of events to create\n', + // eventsToCreate, + // ); //Check if event exists if (!eventsToCreate || eventsToCreate?.length === 0) { return 'No events for today.'; } else { + const batchEvents = []; for (const event of eventsToCreate) { // convert to local time for DST correction... const correctedStartTime = adjustToLosAngelesTime(event.startTime); @@ -148,10 +151,10 @@ const filterAndCreateEvents = async (events, recurringEvents, URL, headerToSend, }; // map/generate all event data with adjusted date, startTime const eventToCreate = generateEventData(timeCorrectedEvent); - - const createdEvent = await createEvent(eventToCreate, URL, headerToSend, fetch); - if (createdEvent) console.log('Created event:', createdEvent); + batchEvents.push(eventToCreate); } + const createdEvents = await createEvents(batchEvents, URL, headerToSend, fetch); + if (createdEvents) console.log('Created events:', createdEvents); return "Today's events have been created."; } }; @@ -217,7 +220,7 @@ module.exports = { adjustToLosAngelesTime, isSameUTCDate, doesEventExist, - createEvent, + createEvents, filterAndCreateEvents, runTask, scheduleTask, diff --git a/backend/workers/openCheckins.js b/backend/workers/openCheckins.js index 18d917d92..6bbb74111 100644 --- a/backend/workers/openCheckins.js +++ b/backend/workers/openCheckins.js @@ -1,81 +1,101 @@ module.exports = (cron, fetch) => { + // Check to see if any events are about to start, + // and if so, open their respective check-ins - // Check to see if any events are about to start, - // and if so, open their respective check-ins + const url = + process.env.NODE_ENV === 'prod' + ? 'https://www.vrms.io' + : `http://localhost:${process.env.BACKEND_PORT}`; + const headerToSend = process.env.CUSTOM_REQUEST_HEADER; - const url = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : `http://localhost:${process.env.BACKEND_PORT}`; - const headerToSend = process.env.CUSTOM_REQUEST_HEADER; + async function fetchEvents() { + try { + const res = await fetch(`${url}/api/events`, { + headers: { + 'x-customrequired-header': headerToSend, + }, + }); + const resJson = await res.json(); - async function fetchEvents() { - try { - const res = await fetch(`${url}/api/events`, { - headers: { - "x-customrequired-header": headerToSend - } - }); - const resJson = await res.json(); + return resJson; + } catch (error) { + console.log(error); + } + } - return resJson; - } catch(error) { - console.log(error); - }; - }; + async function updateEvents(eventsToUpdate) { + try { + const res = await fetch(`${url}/api/events/batchUpdate`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'x-customrequired-header': headerToSend, + }, + body: JSON.stringify(eventsToUpdate), + }); + if (!res.ok) throw new Error('Failed to update event'); + return await res.json(); + } catch (error) { + console.error('Error updating event:', error); + return null; + } + } - async function sortAndFilterEvents(currentTime, thirtyMinutes) { - const events = await fetchEvents(); + async function sortAndFilterEvents() { + const events = await fetchEvents(); - // Filter events if event date is after now but before thirty minutes from now - if (events && events.length > 0) { - const sortedEvents = events.filter(event => { - return (event.date >= currentTime) && (event.date <= thirtyMinutes) && (event.checkInReady === false); - }) - // console.log('Sorted events: ', sortedEvents); - return sortedEvents; - }; - }; + // Get current time and set to date variable + const now = Date.now(); - async function openCheckins(events) { - if(events && events.length > 0) { - events.forEach(event => { - // console.log('Opening event: ', event); + // Calculate thirty minutes from now + const thirtyMinutesFromNow = now + 1800000; - fetch(`${url}/api/events/${event._id}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - "x-customrequired-header": headerToSend - }, - body: JSON.stringify({ checkInReady: true }) - }) - .then(res => { - const response = res; - }) - .catch(err => { - console.log(err); - }); - }); - }; - }; + // Filter events if event date is after now but before thirty minutes from now + if (events && events.length > 0) { + const sortedEvents = events.filter((event) => { + if (!event.date) { + // handle if event date is null/undefined + // false meaning don't include in sortedEvents + return false; + } + const startMs = new Date(event.date).getTime(); + if (Number.isNaN(startMs)) return false; + return startMs >= now && startMs <= thirtyMinutesFromNow && event.checkInReady === false; + }); + // console.log('Sorted events: ', sortedEvents); + return sortedEvents; + } + } - async function runTask() { - console.log("Opening check-ins"); + async function openCheckins(events) { + if (events && events.length > 0) { + console.log('Opening check-ins'); + // console.log('Opening event: ', event); + const batchEventsToUpdate = events.map((e) => ({ + _id: e._id, + checkInReady: true, + })); + const updatedEvents = await updateEvents(batchEventsToUpdate); + if (updatedEvents) console.log('Updated events:', updatedEvents); + console.log('Check-ins opened'); + } else { + console.log('No scheduled events to open'); + } + } - // Get current time and set to date variable - const currentTimeISO = new Date().toISOString(); - - // Calculate thirty minutes from now - const thirtyMinutesFromNow = new Date().getTime() + 1800000; - const thirtyMinutesISO = new Date(thirtyMinutesFromNow).toISOString(); - - const eventsToOpen = await sortAndFilterEvents(currentTimeISO, thirtyMinutesISO); - await openCheckins(eventsToOpen); - - console.log("Check-ins opened"); - }; + async function runTask() { + const eventsToOpen = await sortAndFilterEvents().catch((err) => { + console.log(err); + }); - const scheduledTask = cron.schedule('*/30 * * * *', () => { - runTask(); + await openCheckins(eventsToOpen).catch((err) => { + console.log(err); }); + } + + const scheduledTask = cron.schedule('*/30 * * * *', () => { + runTask(); + }); - return scheduledTask; -}; \ No newline at end of file + return scheduledTask; +}; diff --git a/docker-compose.yml b/docker-compose.yml index 48b01d037..b98a819bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,23 +20,6 @@ services: networks: - gateway - cron: - build: - context: ./backend - dockerfile: Dockerfile.api - target: api-development - command: yarn run cron - volumes: - - ./backend:/srv/backend - - backend_node_modules:/srv/backend/node_modules - restart: on-failure - networks: - - gateway - depends_on: - - backend - env_file: - - ./backend/.env - client: build: context: ./client From 9b08f326716b605df559071eefabc33499139399 Mon Sep 17 00:00:00 2001 From: Ganesh Swaminathan K <64898189+Ganeshswaminathan1912@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:42:19 -0600 Subject: [PATCH 4/6] Fixed bugs after Optimizing --- backend/controllers/event.controller.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/backend/controllers/event.controller.js b/backend/controllers/event.controller.js index 8fc7d0e38..ca6b275ba 100644 --- a/backend/controllers/event.controller.js +++ b/backend/controllers/event.controller.js @@ -56,11 +56,23 @@ EventController.destroy = async function (req, res) { }; EventController.update = async function (req, res) { - const { EventId } = req.params; + const { body } = req; try { - const event = await Event.findByIdAndUpdate(EventId, req.body); - return res.status(200).send(event); + if (Array.isArray(body)) { + const ops = body.map((e) => ({ + updateOne: { + filter: { _id: e._id }, + update: { $set: { checkInReady: e.checkInReady } }, + }, + })); + const events = await Event.bulkWrite(ops); + return res.status(200).send(events); + } else { + const { EventId } = req.params; + const event = await Event.findByIdAndUpdate(EventId, req.body); + return res.status(200).send(event); + } } catch (err) { console.error('[event.controller]', err); return res.sendStatus(400); From ec71b2a6e312055369e8319cb4c6195349404055 Mon Sep 17 00:00:00 2001 From: Ganesh Swaminathan K <64898189+Ganeshswaminathan1912@users.noreply.github.com> Date: Sat, 31 Jan 2026 18:56:08 -0600 Subject: [PATCH 5/6] Fix: use localhost instead of container hostname for tests --- backend/workers/createRecurringEvents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/workers/createRecurringEvents.js b/backend/workers/createRecurringEvents.js index 01dbad0c5..4f84dd516 100644 --- a/backend/workers/createRecurringEvents.js +++ b/backend/workers/createRecurringEvents.js @@ -208,7 +208,7 @@ const createRecurringEvents = (cron, fetch) => { const URL = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' - : `http://backend:${process.env.BACKEND_PORT}`; + : `http://localhost:${process.env.BACKEND_PORT}`; const headerToSend = process.env.CUSTOM_REQUEST_HEADER; return scheduleTask(cron, fetch, URL, headerToSend); From 88603dd419c4b700cbf28002db10cbc3906b3272 Mon Sep 17 00:00:00 2001 From: Ganesh Swaminathan K <64898189+Ganeshswaminathan1912@users.noreply.github.com> Date: Sun, 1 Feb 2026 19:07:59 -0600 Subject: [PATCH 6/6] Test: update recurring events tests for batched POST behavior --- backend/workers/createRecurringEvents.js | 8 ++++---- backend/workers/createRecurringEvents.test.js | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/backend/workers/createRecurringEvents.js b/backend/workers/createRecurringEvents.js index 4f84dd516..d5fce7dff 100644 --- a/backend/workers/createRecurringEvents.js +++ b/backend/workers/createRecurringEvents.js @@ -23,11 +23,11 @@ const fetchData = async (endpoint, URL, headerToSend, fetch) => { /** POST * Creates a new event by making a POST request to the events API. - * @param {Object} event - The event data to create. + * @param {Object} eventArray - The events array data to create. * @returns {Promise} - The created event data or null on failure. */ -const createEvents = async (event, URL, headerToSend, fetch) => { - if (!event) return null; +const createEvents = async (eventArray, URL, headerToSend, fetch) => { + if (!eventArray) return null; try { const res = await fetch(`${URL}/api/events/`, { @@ -36,7 +36,7 @@ const createEvents = async (event, URL, headerToSend, fetch) => { 'Content-Type': 'application/json', 'x-customrequired-header': headerToSend, }, - body: JSON.stringify(event), + body: JSON.stringify(eventArray), }); if (!res.ok) throw new Error('Failed to create event'); return await res.json(); diff --git a/backend/workers/createRecurringEvents.test.js b/backend/workers/createRecurringEvents.test.js index 479a8d969..2f905ef48 100644 --- a/backend/workers/createRecurringEvents.test.js +++ b/backend/workers/createRecurringEvents.test.js @@ -3,7 +3,7 @@ const { adjustToLosAngelesTime, isSameUTCDate, doesEventExist, - createEvent, + createEvents, filterAndCreateEvents, runTask, scheduleTask, @@ -189,7 +189,7 @@ describe('createRecurringEvents Module Tests', () => { expect(fetch).toHaveBeenCalledWith( `${mockURL}/api/events/`, expect.objectContaining({ - body: JSON.stringify(expectedEvent), + body: JSON.stringify([expectedEvent]), }), ); @@ -222,7 +222,7 @@ describe('createRecurringEvents Module Tests', () => { expect(fetch).toHaveBeenCalledWith( `${mockURL}/api/events/`, expect.objectContaining({ - body: JSON.stringify(expectedEvent), + body: JSON.stringify([expectedEvent]), }), ); @@ -256,7 +256,7 @@ describe('createRecurringEvents Module Tests', () => { expect(fetch).toHaveBeenCalledWith( `${mockURL}/api/events/`, expect.objectContaining({ - body: JSON.stringify(expectedEvent), + body: JSON.stringify([expectedEvent]), }), ); @@ -289,7 +289,7 @@ describe('createRecurringEvents Module Tests', () => { expect(fetch).toHaveBeenCalledWith( `${mockURL}/api/events/`, expect.objectContaining({ - body: JSON.stringify(expectedEvent), + body: JSON.stringify([expectedEvent]), }), ); @@ -330,15 +330,16 @@ describe('createRecurringEvents Module Tests', () => { }); }); - describe('createEvent', () => { + describe('createEvents', () => { it('should create a new event via POST request', async () => { const mockEvent = { name: 'Event 1', date: '2023-11-02T19:00:00Z' }; + const mockEventArray = [mockEvent]; fetch.mockResolvedValueOnce({ ok: true, json: jest.fn().mockResolvedValue({ id: 1, ...mockEvent }), }); - const result = await createEvent(mockEvent, mockURL, mockHeader, fetch); + const result = await createEvents(mockEventArray, mockURL, mockHeader, fetch); expect(fetch).toHaveBeenCalledWith(`${mockURL}/api/events/`, { method: 'POST', @@ -346,7 +347,7 @@ describe('createRecurringEvents Module Tests', () => { 'Content-Type': 'application/json', 'x-customrequired-header': mockHeader, }, - body: JSON.stringify(mockEvent), + body: JSON.stringify(mockEventArray), }); expect(result).toEqual({ id: 1, ...mockEvent }); }); @@ -354,7 +355,7 @@ describe('createRecurringEvents Module Tests', () => { it('should return null if event creation fails', async () => { fetch.mockRejectedValueOnce(new Error('Network error')); - const result = await createEvent(null, mockURL, mockHeader, fetch); + const result = await createEvents(null, mockURL, mockHeader, fetch); expect(result).toBeNull(); });