-
-
Notifications
You must be signed in to change notification settings - Fork 964
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Add functionality for creating recurring events (#1807)
* create recurring event helpers and refactor * remove unnecessary file * add documentation and comments * add tests * fix comments * update generateRecurrenceRuleString function * speed up code * fix formatting * format fix * restore package.json * fix failing test * better implementation and faster code * minor refactoring * minor correction * fix imports * return single recurring instance * fix failing test * Revert "return single recurring instance" This reverts commit 60353fd. * Reapply "return single recurring instance" This reverts commit fc12a99. * test commit * update test * fix test * convert to UTC dates * test commit with minor change * limit instance generation date according to the recurrence frequency * adjust for different timezones * remove unnecessary code * remove unnecessary formatting * change variable names
- Loading branch information
Showing
27 changed files
with
1,333 additions
and
210 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
101 changes: 101 additions & 0 deletions
101
src/helpers/event/createEventHelpers/createRecurringEvent.ts
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 |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import type mongoose from "mongoose"; | ||
import type { InterfaceEvent } from "../../../models"; | ||
import { Event } from "../../../models"; | ||
import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; | ||
import { | ||
generateRecurrenceRuleString, | ||
getRecurringInstanceDates, | ||
createRecurrenceRule, | ||
generateRecurringEventInstances, | ||
} from "../recurringEventHelpers"; | ||
|
||
/** | ||
* This function creates the instances of a recurring event upto a certain date. | ||
* @param args - payload of the createEvent mutation | ||
* @param creatorId - _id of the creator | ||
* @param organizationId - _id of the organization the events belongs to | ||
* @remarks The following steps are followed: | ||
* 1. Create a default recurrenceRuleData. | ||
* 2. Generate a recurrence rule string based on the recurrenceRuleData. | ||
* 3. Create a baseRecurringEvent on which recurring instances would be based. | ||
* 4. Get the dates for recurring instances. | ||
* 5. Create a recurrenceRule document. | ||
* 6. Generate recurring instances according to the recurrence rule. | ||
* @returns Created recurring event instance | ||
*/ | ||
|
||
export const createRecurringEvent = async ( | ||
args: MutationCreateEventArgs, | ||
creatorId: string, | ||
organizationId: string, | ||
session: mongoose.ClientSession | ||
): Promise<InterfaceEvent> => { | ||
const { data } = args; | ||
let { recurrenceRuleData } = args; | ||
|
||
if (!recurrenceRuleData) { | ||
// create a default weekly recurrence rule | ||
recurrenceRuleData = { | ||
frequency: "WEEKLY", | ||
}; | ||
} | ||
|
||
// generate a recurrence rule string which would be used to generate rrule object | ||
// and get recurrence dates | ||
const recurrenceRuleString = generateRecurrenceRuleString( | ||
recurrenceRuleData, | ||
data?.startDate, | ||
data?.endDate | ||
); | ||
|
||
// create a base recurring event first, based on which all the | ||
// recurring instances would be dynamically generated | ||
const baseRecurringEvent = await Event.create( | ||
[ | ||
{ | ||
...data, | ||
recurring: true, | ||
isBaseRecurringEvent: true, | ||
creatorId, | ||
admins: [creatorId], | ||
organization: organizationId, | ||
}, | ||
], | ||
{ session } | ||
); | ||
|
||
// get the dates for the recurringInstances, and the date of the last instance | ||
// to be generated in this operation (rest would be generated dynamically during query) | ||
const recurringInstanceDates = getRecurringInstanceDates( | ||
recurrenceRuleString, | ||
data.startDate, | ||
data.endDate | ||
); | ||
|
||
// get the date for the latest created instance | ||
const latestInstanceDate = | ||
recurringInstanceDates[recurringInstanceDates.length - 1]; | ||
// create a recurrenceRule document that would contain the recurrence pattern | ||
const recurrenceRule = await createRecurrenceRule( | ||
recurrenceRuleString, | ||
data.startDate, | ||
data.endDate, | ||
organizationId, | ||
baseRecurringEvent[0]?._id.toString(), | ||
latestInstanceDate, | ||
session | ||
); | ||
|
||
// generate the recurring instances and get an instance back | ||
const recurringEventInstance = await generateRecurringEventInstances({ | ||
data, | ||
baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), | ||
recurrenceRuleId: recurrenceRule?._id.toString(), | ||
recurringInstanceDates, | ||
creatorId, | ||
organizationId, | ||
session, | ||
}); | ||
|
||
return recurringEventInstance; | ||
}; |
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 |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import type mongoose from "mongoose"; | ||
import type { InterfaceEvent } from "../../../models"; | ||
import { Event, EventAttendee, User } from "../../../models"; | ||
import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; | ||
import { cacheEvents } from "../../../services/EventCache/cacheEvents"; | ||
|
||
/** | ||
* This function generates a single non-recurring event. | ||
* @param args - the arguments provided for the createEvent mutation. | ||
* @param creatorId - _id of the current user. | ||
* @param organizationId - _id of the current organization. | ||
* @remarks The following steps are followed: | ||
* 1. Create an event document. | ||
* 2. Associate the event with the user | ||
* 3. Cache the event. | ||
* @returns The created event. | ||
*/ | ||
|
||
export const createSingleEvent = async ( | ||
args: MutationCreateEventArgs, | ||
creatorId: string, | ||
organizationId: string, | ||
session: mongoose.ClientSession | ||
): Promise<InterfaceEvent> => { | ||
// create the single event | ||
const createdEvent = await Event.create( | ||
[ | ||
{ | ||
...args.data, | ||
creatorId, | ||
admins: [creatorId], | ||
organization: organizationId, | ||
}, | ||
], | ||
{ session } | ||
); | ||
|
||
// associate event with the user | ||
await EventAttendee.create( | ||
[ | ||
{ | ||
userId: creatorId, | ||
eventId: createdEvent[0]?._id, | ||
}, | ||
], | ||
{ session } | ||
); | ||
await User.updateOne( | ||
{ | ||
_id: creatorId, | ||
}, | ||
{ | ||
$push: { | ||
eventAdmin: createdEvent[0]?._id, | ||
createdEvents: createdEvent[0]?._id, | ||
registeredEvents: createdEvent[0]?._id, | ||
}, | ||
}, | ||
{ session } | ||
); | ||
|
||
// cache the event | ||
await cacheEvents([createdEvent[0]]); | ||
|
||
return createdEvent[0]; | ||
}; |
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 |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { createSingleEvent } from "./createSingleEvent"; | ||
export { createRecurringEvent } from "./createRecurringEvent"; |
65 changes: 65 additions & 0 deletions
65
src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts
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 |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import type mongoose from "mongoose"; | ||
import { rrulestr } from "rrule"; | ||
import type { InterfaceRecurrenceRule } from "../../../models/RecurrenceRule"; | ||
import { RecurrenceRule } from "../../../models/RecurrenceRule"; | ||
import { | ||
RECURRENCE_FREQUENCIES, | ||
RECURRENCE_WEEKDAYS, | ||
} from "../../../constants"; | ||
|
||
/** | ||
* This function generates the recurrenceRule document. | ||
* @param recurrenceRuleString - the rrule string containing the rules that the instances would follow. | ||
* @param recurrenceStartDate - start date of recurrence. | ||
* @param recurrenceEndDate - end date of recurrence. | ||
* @param organizationId - _id of the current organization. | ||
* @param baseRecurringEventId - _id of the base recurring event. | ||
* @param latestInstanceDate - start date of the last instance generated during this operation. | ||
* @remarks The following steps are followed: | ||
* 1. Create an rrule object from the rrule string. | ||
* 2. Get the fields for the RecurrenceRule document. | ||
* 3. Create the RecurrenceRuleDocument. | ||
* @returns The recurrence rule document. | ||
*/ | ||
|
||
export const createRecurrenceRule = async ( | ||
recurrenceRuleString: string, | ||
recurrenceStartDate: Date, | ||
recurrenceEndDate: Date | null, | ||
organizationId: string, | ||
baseRecurringEventId: string, | ||
latestInstanceDate: Date, | ||
session: mongoose.ClientSession | ||
): Promise<InterfaceRecurrenceRule> => { | ||
const recurrenceRuleObject = rrulestr(recurrenceRuleString); | ||
|
||
const { freq, byweekday } = recurrenceRuleObject.options; | ||
|
||
const weekDays: string[] = []; | ||
if (byweekday) { | ||
for (const weekday of byweekday) { | ||
weekDays.push(RECURRENCE_WEEKDAYS[weekday]); | ||
} | ||
} | ||
|
||
const frequency = RECURRENCE_FREQUENCIES[freq]; | ||
|
||
const recurrenceRule = await RecurrenceRule.create( | ||
[ | ||
{ | ||
organizationId, | ||
baseRecurringEventId, | ||
recurrenceRuleString, | ||
startDate: recurrenceStartDate, | ||
endDate: recurrenceEndDate, | ||
frequency, | ||
count: recurrenceRuleObject.options.count, | ||
weekDays, | ||
latestInstanceDate, | ||
}, | ||
], | ||
{ session } | ||
); | ||
|
||
return recurrenceRule[0].toObject(); | ||
}; |
Oops, something went wrong.