11const { generateEventData } = require ( './lib/generateEventData' ) ;
22
3- module . exports = ( cron , fetch ) => {
4-
5- // Check to see if any recurring events are happening today,
6- // and if so, check to see if an event has already been created
7- // for it. If not, create one.
8-
9- let EVENTS ;
10- let RECURRING_EVENTS ;
11- let TODAY_DATE ;
12- let TODAY ;
13- const URL = process . env . NODE_ENV === 'prod' ? 'https://www.vrms.io' : `http://localhost:${ process . env . BACKEND_PORT } ` ;
14-
15- const headerToSend = process . env . CUSTOM_REQUEST_HEADER ;
16- const fetchEvents = async ( ) => {
17- try {
18- const res = await fetch ( `${ URL } /api/events/` , {
19- headers : {
20- "x-customrequired-header" : headerToSend
21- }
22- } ) ;
23-
24- EVENTS = await res . json ( ) ;
25-
26- // return EVENTS;
27- } catch ( error ) {
28- console . log ( error ) ;
29- } ;
30- } ;
31-
32- const fetchRecurringEvents = async ( ) => {
33- try {
34- const res = await fetch ( `${ URL } /api/recurringevents/` , {
35- headers : {
36- "x-customrequired-header" : headerToSend
37- }
38- } ) ;
39- RECURRING_EVENTS = await res . json ( ) ;
40-
41- // return resJson;
42- } catch ( error ) {
43- console . log ( error ) ;
44- } ;
45- } ;
46-
47- async function filterAndCreateEvents ( ) {
48- TODAY_DATE = new Date ( ) ;
49- TODAY = TODAY_DATE . getDay ( ) ;
50- console . log ( "Date: " , TODAY_DATE , "Day: " , TODAY ) ;
51- const recurringEvents = RECURRING_EVENTS ;
52- // console.log("Today Day: ", TODAY);
53- // Filter recurring events where the event date is today
54- if ( recurringEvents && recurringEvents . length > 0 ) {
55- const filteredEvents = recurringEvents . filter ( event => {
56- const eventDay = new Date ( event . date ) . getDay ( ) ;
57- // console.log("Event Day: ", eventDay);
58- return ( eventDay === TODAY ) ;
59- } ) ;
60- // For each recurring event, check to see if an event already
61- // exists for it and do something if true/false. Can't use
62- // forEach function with async/await.
63- for ( filteredEvent of filteredEvents ) {
64- const eventExists = await checkIfEventExists ( filteredEvent . name ) ;
65-
66- if ( eventExists ) {
67- //Do nothing
68- console . log ( "➖ Not going to run ceateEvent" ) ;
69- } else {
70- // Create new event
71- const eventToCreate = generateEventData ( filteredEvent ) ;
72-
73- const created = await createEvent ( eventToCreate ) ;
74- console . log ( "➕" , created ) ;
75- } ;
76- } ;
77- } ;
78- } ;
79-
80- async function checkIfEventExists ( eventName ) {
81- const events = EVENTS ;
82- // const today = new Date();
83-
84- if ( events && events . length > 0 ) {
85- const filteredEvents = events . filter ( event => {
86- const eventDate = new Date ( event . date ) ;
87- const year = eventDate . getFullYear ( ) ;
88- const month = eventDate . getMonth ( ) ;
89- const date = eventDate . getDate ( ) ;
90-
91- const yearToday = TODAY_DATE . getFullYear ( ) ;
92- const monthToday = TODAY_DATE . getMonth ( ) ;
93- const dateToday = TODAY_DATE . getDate ( ) ;
94-
95- return ( year === yearToday && month === monthToday && date === dateToday && eventName === event . name ) ;
96- } ) ;
97- console . log ( "Events already created: " , filteredEvents ) ;
98- return filteredEvents . length > 0 ? true : false ;
99- } ;
100- } ;
101-
102- const createEvent = async ( event ) => {
103- if ( event ) {
104- const jsonEvent = JSON . stringify ( event ) ;
105- const options = {
106- method : "POST" ,
107- headers : {
108- "Content-Type" : "application/json" ,
109- "x-customrequired-header" : headerToSend
110- } ,
111- body : jsonEvent
112- }
113-
114- console . log ( 'Running createEvent: ' , jsonEvent ) ;
115-
116- try {
117- const response = await fetch ( `${ URL } /api/events/` , options ) ;
118- const resJson = await response . json ( ) ;
119- return resJson ;
120- } catch ( error ) {
121- console . log ( error ) ;
122- } ;
123- } ;
124- } ;
125-
126- async function runTask ( ) {
127- console . log ( "Creating today's events" ) ;
128-
129- await fetchEvents ( ) ;
130- await fetchRecurringEvents ( ) ;
131- await filterAndCreateEvents ( ) ;
132-
133- console . log ( "Today's events are created" ) ;
134-
135- } ;
136-
137- const scheduledTask = cron . schedule ( '*/30 * * * *' , ( ) => {
138- runTask ( ) ;
3+ /**
4+ * Utility to fetch data from an API endpoint.
5+ * @param {string } endpoint - The API endpoint to fetch data from.
6+ * @param {string } URL - The base URL for API requests.
7+ * @param {string } headerToSend - Custom request header.
8+ * @returns {Promise<Array> } - Resolves to the fetched data or an empty array on failure.
9+ */
10+ const fetchData = async ( endpoint , URL , headerToSend , fetch ) => {
11+ try {
12+ const res = await fetch ( `${ URL } ${ endpoint } ` , {
13+ headers : { 'x-customrequired-header' : headerToSend } ,
14+ } ) ;
15+ if ( ! res ?. ok ) throw new Error ( `Failed to fetch: ${ endpoint } ` ) ;
16+ return await res . json ( ) ;
17+ } catch ( error ) {
18+ console . error ( `Error fetching ${ endpoint } :` , error ) ;
19+ return [ ] ;
20+ }
21+ } ;
22+
23+ /**
24+ * Checks if two dates are on the same day in UTC.
25+ * @param {Date } eventDate - Event date.
26+ * @param {Date } todayDate - Today's data.
27+ * @returns {boolean } - True if both dates are on the same UTC day.
28+ */
29+ const isSameUTCDate = ( eventDate , todayDate ) => {
30+ return (
31+ eventDate . getUTCFullYear ( ) === todayDate . getUTCFullYear ( ) &&
32+ eventDate . getUTCMonth ( ) === todayDate . getUTCMonth ( ) &&
33+ eventDate . getUTCDate ( ) === todayDate . getUTCDate ( )
34+ ) ;
35+ } ;
36+
37+ /**
38+ * Checks if an event with the given name already exists for today's date.
39+ * @param {string } recurringEventName - The name of the recurring event to check.
40+ * @param {Date } today - Today's date in UTC.
41+ * @returns {boolean } - True if the event exists, false otherwise.
42+ */
43+ const doesEventExist = ( recurringEventName , today , events ) =>
44+ events . some ( ( event ) => {
45+ const eventDate = new Date ( event . date ) ;
46+ return isSameUTCDate ( eventDate , today ) && event . name === recurringEventName ;
47+ } ) ;
48+
49+ /**
50+ * Creates a new event by making a POST request to the events API.
51+ * @param {Object } event - The event data to create.
52+ * @returns {Promise<Object|null> } - The created event data or null on failure.
53+ */
54+ const createEvent = async ( event , URL , headerToSend , fetch ) => {
55+ if ( ! event ) return null ;
56+
57+ try {
58+ const res = await fetch ( `${ URL } /api/events/` , {
59+ method : 'POST' ,
60+ headers : {
61+ 'Content-Type' : 'application/json' ,
62+ 'x-customrequired-header' : headerToSend ,
63+ } ,
64+ body : JSON . stringify ( event ) ,
13965 } ) ;
140- return scheduledTask ;
141- } ;
66+ if ( ! res . ok ) throw new Error ( 'Failed to create event' ) ;
67+ return await res . json ( ) ;
68+ } catch ( error ) {
69+ console . error ( 'Error creating event:' , error ) ;
70+ return null ;
71+ }
72+ } ;
73+
74+ /**
75+ * Filters recurring events happening today and creates new events if they do not already exist.
76+ * Adjusts for Daylight Saving Time (DST) by converting stored UTC dates to Los Angeles time.
77+ * @param {Array } events - The list of existing events.
78+ * @param {Array } recurringEvents - The list of recurring events to check.
79+ * @param {string } URL - The base URL for API requests.
80+ * @param {string } headerToSend - Custom header for authentication or request tracking.
81+ * @param {Function } fetch - Fetch function for making API calls.
82+ * @returns {Promise<void> } - A promise that resolves when all events are processed.
83+ */
84+ const filterAndCreateEvents = async ( events , recurringEvents , URL , headerToSend , fetch ) => {
85+ const today = new Date ( ) ;
86+ const todayUTCDay = today . getUTCDay ( ) ;
87+ // filter recurring events for today and not already existing
88+ const eventsToCreate = recurringEvents . filter ( ( recurringEvent ) => {
89+ // we're converting the stored UTC event date to local time to compare the system DOW with the event DOW
90+ const localEventDate = adjustToLosAngelesTime ( recurringEvent . date ) ;
91+ return (
92+ localEventDate . getUTCDay ( ) === todayUTCDay &&
93+ ! doesEventExist ( recurringEvent . name , today , events )
94+ ) ;
95+ } ) ;
96+
97+ for ( const event of eventsToCreate ) {
98+ // convert to local time for DST correction...
99+ const correctedStartTime = adjustToLosAngelesTime ( event . startTime ) ;
100+ const timeCorrectedEvent = {
101+ ...event ,
102+ // ... then back to UTC for DB
103+ date : correctedStartTime . toISOString ( ) ,
104+ startTime : correctedStartTime . toISOString ( ) ,
105+ } ;
106+ // map/generate all event data with adjusted date, startTime
107+ const eventToCreate = generateEventData ( timeCorrectedEvent ) ;
108+
109+ const createdEvent = await createEvent ( eventToCreate , URL , headerToSend , fetch ) ;
110+ if ( createdEvent ) console . log ( 'Created event:' , createdEvent ) ;
111+ }
112+ } ;
113+
114+ /**
115+ * Adjusts an event date to Los_Angeles time, accounting for DST offsets.
116+ * @param {Date } eventDate - The event date to adjust.
117+ * @returns {Date } - The adjusted event date.
118+ */
119+ const adjustToLosAngelesTime = ( eventDate ) => {
120+ const tempDate = new Date ( eventDate ) ;
121+ const losAngelesOffsetHours = new Intl . DateTimeFormat ( 'en-US' , {
122+ timeZone : 'America/Los_Angeles' ,
123+ timeZoneName : 'shortOffset' ,
124+ } )
125+ . formatToParts ( tempDate )
126+ . find ( ( part ) => part . type === 'timeZoneName' )
127+ . value . slice ( 3 ) ;
128+ const offsetMinutes = parseInt ( losAngelesOffsetHours , 10 ) * 60 ;
129+ return new Date ( tempDate . getTime ( ) + offsetMinutes * 60000 ) ;
130+ } ;
131+
132+ /**
133+ * Executes the task of fetching existing events and recurring events,
134+ * filtering those that should occur today, and creating them if needed.
135+ * @param {Function } fetch - Fetch function for making API requests.
136+ * @param {string } URL - The base URL for API requests.
137+ * @param {string } headerToSend - Custom header for authentication or request tracking.
138+ * @returns {Promise<void> } - A promise that resolves when all tasks are completed.
139+ */
140+ const runTask = async ( fetch , URL , headerToSend ) => {
141+ console . log ( "Creating today's events..." ) ;
142+ const [ events , recurringEvents ] = await Promise . all ( [
143+ fetchData ( '/api/events/' , URL , headerToSend , fetch ) ,
144+ fetchData ( '/api/recurringevents/' , URL , headerToSend , fetch ) ,
145+ ] ) ;
146+
147+ await filterAndCreateEvents ( events , recurringEvents , URL , headerToSend , fetch ) ;
148+ console . log ( "Today's events have been created." ) ;
149+ } ;
150+
151+ /**
152+ * Schedules the runTask function to execute periodically using a cron job.
153+ * @param {Object } cron - The cron scheduling library.
154+ * @param {Function } fetch - Fetch function for making API requests.
155+ * @param {string } URL - The base URL for API requests.
156+ * @param {string } headerToSend - Custom header for authentication or request tracking.
157+ * @returns {Object } - The scheduled cron job instance.
158+ */
159+ const scheduleTask = ( cron , fetch , URL , headerToSend ) => {
160+ return cron . schedule ( '*/30 * * * *' , ( ) => {
161+ runTask ( fetch , URL , headerToSend ) . catch ( ( error ) => console . error ( 'Error running task:' , error ) ) ;
162+ } ) ;
163+ } ;
164+
165+ /**
166+ * Wrapper function to initialize the worker with dependencies in app.js
167+ * @param {Object } cron - The cron scheduling library.
168+ * @param {Function } fetch - Fetch function for making API requests.
169+ * @returns {Object } - The scheduled cron job instance.
170+ */
171+ const createRecurringEvents = ( cron , fetch ) => {
172+ const URL =
173+ process . env . NODE_ENV === 'prod'
174+ ? 'https://www.vrms.io'
175+ : `http://localhost:${ process . env . BACKEND_PORT } ` ;
176+ const headerToSend = process . env . CUSTOM_REQUEST_HEADER ;
177+
178+ return scheduleTask ( cron , fetch , URL , headerToSend ) ;
179+ } ;
180+
181+ module . exports = {
182+ createRecurringEvents,
183+ fetchData,
184+ adjustToLosAngelesTime,
185+ isSameUTCDate,
186+ doesEventExist,
187+ createEvent,
188+ filterAndCreateEvents,
189+ runTask,
190+ scheduleTask,
191+ } ;
0 commit comments