Skip to content

Commit 4e8f118

Browse files
authored
Merge pull request #1851 from kurtmgray/1839-date-fix
1839 date fix
2 parents e2ec0fe + 740cf01 commit 4e8f118

File tree

4 files changed

+565
-138
lines changed

4 files changed

+565
-138
lines changed

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"eslint-plugin-jsx-a11y": "^6.3.1",
3131
"eslint-plugin-react": "^7.20.6",
3232
"jest": "^26.4.0",
33+
"mockdate": "^3.0.5",
3334
"nodemon": "^2.0.2",
3435
"prettier": "^2.1.1",
3536
"pretty-quick": "^3.0.2",
Lines changed: 188 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,191 @@
11
const { 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

Comments
 (0)