generated from hackforla/.github-hackforla-base-repo-template
-
-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1851 from kurtmgray/1839-date-fix
1839 date fix
- Loading branch information
Showing
4 changed files
with
565 additions
and
138 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,141 +1,191 @@ | ||
const { generateEventData } = require('./lib/generateEventData'); | ||
|
||
module.exports = (cron, fetch) => { | ||
|
||
// Check to see if any recurring events are happening today, | ||
// and if so, check to see if an event has already been created | ||
// for it. If not, create one. | ||
|
||
let EVENTS; | ||
let RECURRING_EVENTS; | ||
let TODAY_DATE; | ||
let TODAY; | ||
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 fetchEvents = async () => { | ||
try { | ||
const res = await fetch(`${URL}/api/events/`, { | ||
headers: { | ||
"x-customrequired-header": headerToSend | ||
} | ||
}); | ||
|
||
EVENTS = await res.json(); | ||
|
||
// return EVENTS; | ||
} catch(error) { | ||
console.log(error); | ||
}; | ||
}; | ||
|
||
const fetchRecurringEvents = async () => { | ||
try { | ||
const res = await fetch(`${URL}/api/recurringevents/`, { | ||
headers: { | ||
"x-customrequired-header": headerToSend | ||
} | ||
}); | ||
RECURRING_EVENTS = await res.json(); | ||
|
||
// return resJson; | ||
} catch(error) { | ||
console.log(error); | ||
}; | ||
}; | ||
|
||
async function filterAndCreateEvents() { | ||
TODAY_DATE = new Date(); | ||
TODAY = TODAY_DATE.getDay(); | ||
console.log("Date: ", TODAY_DATE, "Day: ", TODAY); | ||
const recurringEvents = RECURRING_EVENTS; | ||
// console.log("Today Day: ", TODAY); | ||
// Filter recurring events where the event date is today | ||
if (recurringEvents && recurringEvents.length > 0) { | ||
const filteredEvents = recurringEvents.filter(event => { | ||
const eventDay = new Date(event.date).getDay(); | ||
// console.log("Event Day: ", eventDay); | ||
return (eventDay === TODAY); | ||
}); | ||
// For each recurring event, check to see if an event already | ||
// exists for it and do something if true/false. Can't use | ||
// forEach function with async/await. | ||
for (filteredEvent of filteredEvents) { | ||
const eventExists = await checkIfEventExists(filteredEvent.name); | ||
|
||
if (eventExists) { | ||
//Do nothing | ||
console.log("➖ Not going to run ceateEvent"); | ||
} else { | ||
// Create new event | ||
const eventToCreate = generateEventData(filteredEvent); | ||
|
||
const created = await createEvent(eventToCreate); | ||
console.log("➕", created); | ||
}; | ||
}; | ||
}; | ||
}; | ||
|
||
async function checkIfEventExists(eventName) { | ||
const events = EVENTS; | ||
// const today = new Date(); | ||
|
||
if (events && events.length > 0) { | ||
const filteredEvents = events.filter(event => { | ||
const eventDate = new Date(event.date); | ||
const year = eventDate.getFullYear(); | ||
const month = eventDate.getMonth(); | ||
const date = eventDate.getDate(); | ||
|
||
const yearToday = TODAY_DATE.getFullYear(); | ||
const monthToday = TODAY_DATE.getMonth(); | ||
const dateToday = TODAY_DATE.getDate(); | ||
|
||
return (year === yearToday && month === monthToday && date === dateToday && eventName === event.name); | ||
}); | ||
console.log("Events already created: ", filteredEvents); | ||
return filteredEvents.length > 0 ? true : false; | ||
}; | ||
}; | ||
|
||
const createEvent = async (event) => { | ||
if(event) { | ||
const jsonEvent = JSON.stringify(event); | ||
const options = { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
"x-customrequired-header": headerToSend | ||
}, | ||
body: jsonEvent | ||
} | ||
|
||
console.log('Running createEvent: ', jsonEvent); | ||
|
||
try { | ||
const response = await fetch(`${URL}/api/events/`, options); | ||
const resJson = await response.json(); | ||
return resJson; | ||
} catch (error) { | ||
console.log(error); | ||
}; | ||
}; | ||
}; | ||
|
||
async function runTask() { | ||
console.log("Creating today's events"); | ||
|
||
await fetchEvents(); | ||
await fetchRecurringEvents(); | ||
await filterAndCreateEvents(); | ||
|
||
console.log("Today's events are created"); | ||
|
||
}; | ||
|
||
const scheduledTask = cron.schedule('*/30 * * * *', () => { | ||
runTask(); | ||
/** | ||
* 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. | ||
* @param {string} headerToSend - Custom request header. | ||
* @returns {Promise<Array>} - Resolves to the fetched data or an empty array on failure. | ||
*/ | ||
const fetchData = async (endpoint, URL, headerToSend, fetch) => { | ||
try { | ||
const res = await fetch(`${URL}${endpoint}`, { | ||
headers: { 'x-customrequired-header': headerToSend }, | ||
}); | ||
if (!res?.ok) throw new Error(`Failed to fetch: ${endpoint}`); | ||
return await res.json(); | ||
} catch (error) { | ||
console.error(`Error fetching ${endpoint}:`, error); | ||
return []; | ||
} | ||
}; | ||
|
||
/** | ||
* Checks if two dates are on the same day in UTC. | ||
* @param {Date} eventDate - Event date. | ||
* @param {Date} todayDate - Today's data. | ||
* @returns {boolean} - True if both dates are on the same UTC day. | ||
*/ | ||
const isSameUTCDate = (eventDate, todayDate) => { | ||
return ( | ||
eventDate.getUTCFullYear() === todayDate.getUTCFullYear() && | ||
eventDate.getUTCMonth() === todayDate.getUTCMonth() && | ||
eventDate.getUTCDate() === todayDate.getUTCDate() | ||
); | ||
}; | ||
|
||
/** | ||
* Checks if an event with the given name already exists for today's date. | ||
* @param {string} recurringEventName - The name of the recurring event to check. | ||
* @param {Date} today - Today's date in UTC. | ||
* @returns {boolean} - True if the event exists, false otherwise. | ||
*/ | ||
const doesEventExist = (recurringEventName, today, events) => | ||
events.some((event) => { | ||
const eventDate = new Date(event.date); | ||
return isSameUTCDate(eventDate, today) && event.name === recurringEventName; | ||
}); | ||
|
||
/** | ||
* Creates a new event by making a POST request to the events API. | ||
* @param {Object} event - The event data to create. | ||
* @returns {Promise<Object|null>} - 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), | ||
}); | ||
return scheduledTask; | ||
}; | ||
if (!res.ok) throw new Error('Failed to create event'); | ||
return await res.json(); | ||
} catch (error) { | ||
console.error('Error creating event:', error); | ||
return null; | ||
} | ||
}; | ||
|
||
/** | ||
* Filters recurring events happening today and creates new events if they do not already exist. | ||
* Adjusts for Daylight Saving Time (DST) by converting stored UTC dates to Los Angeles time. | ||
* @param {Array} events - The list of existing events. | ||
* @param {Array} recurringEvents - The list of recurring events to check. | ||
* @param {string} URL - The base URL for API requests. | ||
* @param {string} headerToSend - Custom header for authentication or request tracking. | ||
* @param {Function} fetch - Fetch function for making API calls. | ||
* @returns {Promise<void>} - A promise that resolves when all events are processed. | ||
*/ | ||
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) => { | ||
// 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); | ||
return ( | ||
localEventDate.getUTCDay() === todayUTCDay && | ||
!doesEventExist(recurringEvent.name, today, events) | ||
); | ||
}); | ||
|
||
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); | ||
} | ||
}; | ||
|
||
/** | ||
* 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. | ||
* @param {Function} fetch - Fetch function for making API requests. | ||
* @param {string} URL - The base URL for API requests. | ||
* @param {string} headerToSend - Custom header for authentication or request tracking. | ||
* @returns {Promise<void>} - A promise that resolves when all tasks are completed. | ||
*/ | ||
const runTask = async (fetch, URL, headerToSend) => { | ||
console.log("Creating today's events..."); | ||
const [events, recurringEvents] = await Promise.all([ | ||
fetchData('/api/events/', URL, headerToSend, fetch), | ||
fetchData('/api/recurringevents/', URL, headerToSend, fetch), | ||
]); | ||
|
||
await filterAndCreateEvents(events, recurringEvents, URL, headerToSend, fetch); | ||
console.log("Today's events have been created."); | ||
}; | ||
|
||
/** | ||
* Schedules the runTask function to execute periodically using a cron job. | ||
* @param {Object} cron - The cron scheduling library. | ||
* @param {Function} fetch - Fetch function for making API requests. | ||
* @param {string} URL - The base URL for API requests. | ||
* @param {string} headerToSend - Custom header for authentication or request tracking. | ||
* @returns {Object} - The scheduled cron job instance. | ||
*/ | ||
const scheduleTask = (cron, fetch, URL, headerToSend) => { | ||
return cron.schedule('*/30 * * * *', () => { | ||
runTask(fetch, URL, headerToSend).catch((error) => console.error('Error running task:', error)); | ||
}); | ||
}; | ||
|
||
/** | ||
* Wrapper function to initialize the worker with dependencies in app.js | ||
* @param {Object} cron - The cron scheduling library. | ||
* @param {Function} fetch - Fetch function for making API requests. | ||
* @returns {Object} - The scheduled cron job instance. | ||
*/ | ||
const createRecurringEvents = (cron, fetch) => { | ||
const URL = | ||
process.env.NODE_ENV === 'prod' | ||
? 'https://www.vrms.io' | ||
: `http://localhost:${process.env.BACKEND_PORT}`; | ||
const headerToSend = process.env.CUSTOM_REQUEST_HEADER; | ||
|
||
return scheduleTask(cron, fetch, URL, headerToSend); | ||
}; | ||
|
||
module.exports = { | ||
createRecurringEvents, | ||
fetchData, | ||
adjustToLosAngelesTime, | ||
isSameUTCDate, | ||
doesEventExist, | ||
createEvent, | ||
filterAndCreateEvents, | ||
runTask, | ||
scheduleTask, | ||
}; |
Oops, something went wrong.